Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,15 @@
"contributions": [
"doc"
]
},
{
"login": "amyheather",
"name": "Amy Heather",
"avatar_url": "https://avatars.githubusercontent.com/u/92166537?v=4",
"profile": "https://www.linkedin.com/in/amyheather",
"contributions": [
"blog"
]
}
],
"commitConvention": "angular",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pyopensci.github.io
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-55-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-56-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![Deploy Hugo site to Pages](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml/badge.svg)](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml)
[![DOI](https://zenodo.org/badge/174412809.svg)](https://zenodo.org/doi/10.5281/zenodo.10594115)
Expand Down Expand Up @@ -240,6 +240,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/slobentanzer"><img src="https://avatars.githubusercontent.com/u/13223629?v=4?s=100" width="100px;" alt="Sebastian Lobentanzer"/><br /><sub><b>Sebastian Lobentanzer</b></sub></a><br /><a href="https://github.com/pyOpenSci/pyopensci.github.io/pulls?q=is%3Apr+reviewed-by%3Aslobentanzer" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bwhitt7"><img src="https://avatars.githubusercontent.com/u/103079612?v=4?s=100" width="100px;" alt="Britney Whittington"/><br /><sub><b>Britney Whittington</b></sub></a><br /><a href="https://github.com/pyOpenSci/pyopensci.github.io/commits?author=bwhitt7" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://romaincaneill.fr"><img src="https://avatars.githubusercontent.com/u/18579092?v=4?s=100" width="100px;" alt="Romain Caneill"/><br /><sub><b>Romain Caneill</b></sub></a><br /><a href="https://github.com/pyOpenSci/pyopensci.github.io/commits?author=rcaneill" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/amyheather"><img src="https://avatars.githubusercontent.com/u/92166537?v=4?s=100" width="100px;" alt="Amy Heather"/><br /><sub><b>Amy Heather</b></sub></a><br /><a href="#blog-amyheather" title="Blogposts">📝</a></td>
</tr>
</tbody>
</table>
Expand Down
101 changes: 101 additions & 0 deletions content/blog/2026-07-10-lintquarto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: 'Maintain Python code quality in Quarto Markdown files using lintquarto'
date: '2026-07-10'
type: blog
excerpt: lintquarto is a package for running linters, formatters, static type checkers and code analysis tools on Python code in Quarto markdown (.qmd) files.
author: Amy Heather
blog_topic: software
comments: true
url: "/blog/lintquarto.html"
lastmod: '2026-07-10'
image:
src: images/blog/2026/cleaning.jpg
alt: "Person cleaning by a computer. Image source: Unsplash (free to use under the Unsplash Licence) https://unsplash.com/photos/person-in-blue-long-sleeve-shirt-sitting-beside-black-laptop-computer--9gPKrsbGmc."
---

If you write Python code inside Quarto Markdown files, you've probably noticed that your linter can't run on it. `lintquarto` fixes that. It is a command-line tool that wraps Python linters, formatters, static type checkers, and code analysis tools so they can run directly on `.qmd` files. It has recently been [accepted into the PyOpenSci ecosystem](https://github.com/pyOpenSci/software-submission/issues/257).

## How `lintquarto` works

The core approach is straightforward. `lintquarto` converts each `.qmd` file into a temporary Python file, then runs your chosen code quality tool on it. For formatters, `lintquarto` can apply changes back to the source `.qmd` file. For tools that only report issues, it captures the output and surfaces it with correct paths.

The converter handles Quarto-specific syntax that would otherwise trip up a Python linter, including:

* Quarto chunk options (e.g., `#| echo: false`).
* Include directives.
* Inline annotations (e.g., `# <1>`).

It also avoids false positives that arise because `.qmd` files are not standard Python modules (e.g., cell-based structure and document-level syntax).

By default, it runs only on executable code, but this can be configured to include non-executable code as well.

## Getting started

`lintquarto` can be installed from PyPI or conda-forge. For example, from PyPI:

```
pip install lintquarto
```

You should also install your desired code quality tools. For a one-step installation that includes `lintquarto` and all supported tools, use:

```
pip install lintquarto[all]
```

`lintquarto` is run from the command line. Its main arguments are:

* `-l` which linters, static type checkers or code quality tools to run.
* `-f` which formatters to run.
* `-p` files or directories to include.
* `-e` paths to exclude.

For example, to run `flake8` on all `.qmd` files in the current directory:

```
lintquarto -l flake8 -p .
```

To run `ruff format` on files in a specific directory, excluding the `examples/` folder:

```
lintquarto -f ruff-format -p pages -e pages/examples
```

As an alternative to passing flags on every run, you can declare your settings once in a `[tool.lintquarto]` section in your `pyproject.toml`. The arguments are equivalent to those used on the command line. For example:

```
[tool.lintquarto]
linters = [
"ruff",
"pycodestyle",
]
paths = [
"content/",
"dashboard/index.qmd",
]
```

You can then simply call:

```
lintquarto
```

## Supported tools

`lintquarto` supports a wide variety of code quality tools. To date, these include:

* **Linters:** `flake8`, `pycodestyle`, `pydoclint`, `pyflakes`, `pylint`, `ruff`.
* **Formatters:** `ruff`.
* **Static type checkers:** `mypy`, `pyrefly`, `pyright`, `basedpyright`, `pytype`.
* **Code analysis:** `vulture`, `radon`.

## Try it out

* **GitHub:** https://github.com/lintquarto/lintquarto
* **Documentation:** https://lintquarto.github.io/lintquarto/
* **PyPI:** https://pypi.org/project/lintquarto/
* **Conda:** https://anaconda.org/conda-forge/lintquarto

If you find a bug, have a feature request, or want to add support for a tool not yet covered, contributions are very welcome. Head over to the GitHub repository and open an issue or pull request.
Binary file added static/images/blog/2026/cleaning.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading