mdq-py
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
mdqcommand 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"inparse()/load(), ordocument.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 theincludeblocks of an exam with the questions they refer to.mdq.convert.import_question()andmdq.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
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)
| File | Size | Uploaded | |
|---|---|---|---|
| mdq-0.1.0.tar.gz | 126.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|