Skip to main content

mdq-py

CI License: MIT

Python reference implementation of MDQ, a file format to write questions and exams in plain Markdown:

What is the capital of Brazil?

* [ ] Rio de Janeiro
  > It was the capital until 1960.
* [*] Brasília
* [ ] São Paulo

mdq-py provides:

  • A parser from MDQ Markdown (and from YAML/JSON) to Pydantic models.
  • A linter that reports problems the schema cannot express.
  • Scoring of student responses.
  • Converters to and from GIFT, Aiken and Moodle XML.
  • The mdq command line tool.

The syntax of each question type is in the specification.

Installation

Requires Python 3.13 or later. The package is not on PyPI yet. Install it from the repository:

# CLI only
uv tool install "git+https://github.com/fabiommendes/mdq-py"

# As a library
uv add "git+https://github.com/fabiommendes/mdq-py"

Command line

mdq new multiple-choice               # scaffold multiple-choice.mdq.md
mdq new numeric --complete            # scaffold with every feature of the type
mdq validate question.mdq.md          # print errors and warnings
mdq validate question.mdq.md --level strict   # also print info diagnostics
mdq show exam.mdq.md                  # render the parsed document
mdq show exam.mdq.md --no-answer-key  # preview it as a student sees it
mdq export question.mdq.md --format gift
mdq import question.gift -o question.mdq.md

mdq validate exits with a non-zero code if the document has errors:

$ mdq validate capital.mdq.md
OK    capital.mdq.md

import and export support aiken, gift and moodle-xml. The format is inferred from the file extension if --format is not given. Run mdq --help or mdq <command> --help for all options.

Library

mdq.parse() returns a validated model, or raises InvalidDocument:

from pathlib import Path

import mdq

question = mdq.parse("""
What is the capital of Brazil?

* [ ] Rio de Janeiro
* [*] Brasília
""")

print(type(question).__name__)  # MultipleChoiceQuestion
print(question.to_dict())

A str is read as MDQ source. Pass a Path to read a file, or a mapping to load an already parsed document. The format argument selects "mdq", "yaml" or "json" explicitly.

mdq.load() never raises for a problem in the document. It returns the document together with every diagnostic, which is useful for editors and linters:

loaded = mdq.load(Path("exam.mdq.md"))
for diagnostic in loaded.diagnostics:
    print(diagnostic.severity, diagnostic.code, diagnostic.message)

exam = loaded.validate(raise_on="warning")

Other useful parts of the API:

  • ids="fill" in parse()/load(), or document.with_ids(), derives the missing ids of questions and choices.
  • question.score_response(response) scores a student response.
  • question.render() serializes a model back to MDQ Markdown.
  • Exam.resolve() replaces the include blocks of an exam with the questions they refer to.
  • mdq.convert.import_question() and mdq.convert.export_question() convert from and to other formats.

Development

The test suite reads the specification, the schema and the shared examples from the parent mdq-spec repository, where this package lives in the mdq-py/ directory. Clone that repository to run the tests; a standalone clone of mdq-py cannot run them.

git clone https://github.com/fabiommendes/mdq-spec
cd mdq-spec/mdq-py
uv sync
uv run pytest          # tests
uv run mypy mdq        # type check
uv run ruff check      # lint

This is the reference implementation: new features are specified in mdq-spec, implemented here, and then ported to mdq-js. See AGENTS.md for the coding conventions.

See the changelog and the contributing guide.

License

MIT

Release files for mdq 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdq 0.1.0
File Size Uploaded
mdq-0.1.0.tar.gz 126.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mdq 0.1.0
File Interpreter ABI Platform
mdq-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 271.5 kB

Release files / mdq-0.1.0.tar.gz

Download URL mdq-0.1.0.tar.gz
Size 126.7 kB
Tags Source
SHA-256 checksum
How to use checksums
4920efacb74bfc82de9943ef53f96c504253fe64c20a06778324fb51d424fcfb
BLAKE2b-256 checksum
How to use checksums
4b7d5903429ff6987b7e561c2cf5b09a5aadaf2275d0ff999d52398501be11e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / mdq-0.1.0-py3-none-any.whl

Download URL mdq-0.1.0-py3-none-any.whl
Size 144.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
459728c7efa193709c369640427400cbbdf3ba3c903954dedae3cf213520fb23
BLAKE2b-256 checksum
How to use checksums
e52aa927b8b4d6bda643ff49415bdf89133d70f055c4a64521544b34f0740242
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page