pygments review
Pygments 2.21.0 turns source text into lexical tokens, then sends those tokens to an HTML, ANSI-terminal, LaTeX, image, or other formatter. It can pick a lexer from an alias, filename, MIME type, or content guess, but it never builds an AST or proves that code is valid. This release adds BitBake, Caddyfile, CEL, and PureScript lexers, introduces the Night Owl style, accepts Python 3.15, and removes catastrophic backtracking from XML detection. Our sandbox import finished in 0.01 seconds; the package remains regex-based and ships no `py.typed` marker.
Pygments 2.21.0 installed in 0.3 seconds, occupied 6 MB as one package, imported in 0.01 seconds, and produced 0 known vulnerability findings in our sandbox. Install it for multi-language Python-side rendering, but choose a parser for code structure and isolate every untrusted snippet behind a timeout.
We installed it
| Install | ✓ · 0.3s | 1 package on disk · 6 MB |
| Import | ✓ | import pygments in 0.01s · pure Python · requires Python >=3.9 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does pygments install cleanly?
Yes. In a fresh container with an empty cache, pip install pygments finished in 0.3s, leaving 1 package and 6 MB on disk. pip-audit reported no known vulnerabilities.
What does pygments need to run?
Python >=3.9, and nothing compiled: it is pure Python. In our run import pygments succeeded in 0.01s.
pygments or rich: which should you use?
rich: Choose Rich 15.0 for a terminal UI with tracebacks, tables, progress, and syntax panels. It still uses Pygments for code coloring. Pygments 2.21.0 installed in 0.3 seconds, occupied 6 MB as one package, imported in 0.01 seconds, and produced 0 known vulnerability findings in our sandbox.
When should you not use pygments?
You need an AST, symbol table, structural query, or refactoring support, because Pygments 2.21.0 only assigns lexical token types
Use it if
- Use Pygments 2.21.0 in a Python documentation service that must color source from many languages on the server
- You need one token stream rendered as classed HTML, ANSI terminal text, LaTeX, SVG, or an image
- Your HTML output needs selectable styles, linked line anchors, start-line offsets, or emphasis on chosen source lines
- You want installed, trusted Python packages to register niche lexers, formatters, filters, or styles through Pygments entry points
- You need an AST, symbol table, structural query, or refactoring support, because Pygments 2.21.0 only assigns lexical token types
- User-submitted text cannot be moved into a timed worker with capped concurrency. The maintainers warn that hostile regex cases can exhaust time or memory
- Strict type checking requires an inline `py.typed` marker, which our 2.21.0 package inspection did not find
- A browser highlights one known language and there is no Python backend. Our install adds a 6 MB server-side package for work a client highlighter can do locally
- Generated HTML must stay byte-for-byte identical across upgrades. Lexer repairs and formatter changes in 2.21.0 alter token spans and rendered characters
Setup reality
We installed Pygments 2.21.0 in our fresh Python 3.12 Bookworm sandbox with 3 CPUs, 8 GB of RAM, no cache, and an unprivileged user. Installation succeeded in 0.3 seconds, leaving one package and 6 MB on disk. pip-audit found 0 known vulnerabilities, while import pygments completed in 0.01 seconds. The measurement setup counted one direct dependency, found pure Python, required Python 3.9+, and found no py.typed marker. Its license field came back unknown, although PyPI declares BSD-2-Clause.
Pygments needs no credentials or config file. Pin 2.21.0 if generated HTML is committed or snapshot-tested, because lexer repairs change token boundaries and this release changed how HtmlFormatter renders quote characters. Prefer get_lexer_by_name() when the caller knows the language. Filename and content lookup can raise ClassNotFound. The command-line docs call content guessing unreliable, so fall back to TextLexer instead of assigning an uncertain grammar.
HtmlFormatter emits a fragment with class names by default. Generate CSS from the same formatter, style, and selector, or the spans will have no matching rules. noclasses=True repeats inline declarations, while full=True returns an HTML 4 document. Version 2.21.0 leaves quote characters literal in highlighted code. Pygments escapes code markup, but your page template must still treat titles and surrounding values as untrusted.
Untrusted snippets need a process boundary. The Pygments README says a pathological regular expression or broken matcher can run indefinitely or consume substantial memory. Maintainers recommend a short timeout and a cap on concurrent processes. This applies even though 2.21.0 never executes the submitted program. Entry-point plugins run Python in the host process, and -x custom lexers execute a local file. For redirected stdout, skip the ANSI formatter or check isatty() first.
Patterns
Render a classed HTML fragment render-html-fragment
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name
formatter = HtmlFormatter(cssclass="code")
lexer = get_lexer_by_name("python")
html = highlight(source, lexer, formatter)`HtmlFormatter` wraps the result in `<div class="code"><pre>...` and emits token classes. Fragment mode does not attach their CSS.
Generate CSS for Night Owl emit-matching-css
from pathlib import Path
from pygments.formatters import HtmlFormatter
formatter = HtmlFormatter(style="night-owl", cssclass="code")
css = formatter.get_style_defs(".code")
Path("pygments.css").write_text(css, encoding="utf-8")Night Owl arrived in Pygments 2.21.0. Keep the rendering style, `cssclass`, and CSS selector together so the emitted classes match these rules.
Use an explicit language with a plain-text fallback select-lexer-by-alias
from pygments.lexers import TextLexer, get_lexer_by_name
from pygments.util import ClassNotFound
try:
lexer = get_lexer_by_name(user_language, stripnl=False)
except ClassNotFound:
lexer = TextLexer(stripnl=False)An unknown alias raises `ClassNotFound`. `TextLexer` preserves the source without pretending that Pygments recognized its language.
Use the filename and source together select-lexer-by-file
from pygments.lexers import get_lexer_for_filename
lexer = get_lexer_for_filename(path.name, source, stripnl=False)Passing `source` lets Pygments break ties between lexers that share a filename pattern. No matching pattern still raises `ClassNotFound`.
Keep ANSI codes out of redirected output render-terminal-safely
import sys
from pygments import highlight
from pygments.formatters import Terminal256Formatter
from pygments.lexers import PythonLexer
if sys.stdout.isatty():
output = highlight(source, PythonLexer(), Terminal256Formatter(style="night-owl"))
else:
output = source
sys.stdout.write(output)`Terminal256Formatter` writes ANSI escape sequences. The `isatty()` branch keeps pipes and generated files as plain source text.
Produce HTML without a separate stylesheet embed-inline-styles
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import JsonLexer
html = highlight(payload, JsonLexer(), HtmlFormatter(
noclasses=True,
style="default",
))`noclasses=True` repeats style attributes on token spans. Pygments documents the resulting size increase for larger code samples.
Number lines and give them stable anchors add-line-links
from pygments.formatters import HtmlFormatter
formatter = HtmlFormatter(
linenos="inline",
linenostart=40,
lineanchors="src",
anchorlinenos=True,
)The first displayed line is 40, and `lineanchors="src"` creates IDs such as `src-40`. Anchor line numbers only when the page needs direct links.
Emphasize selected source lines mark-changed-lines
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import DiffLexer
formatter = HtmlFormatter(hl_lines=[2, 5])
html = highlight(diff_text, DiffLexer(), formatter)`hl_lines` is always 1-based relative to the input, regardless of `linenostart`. The generated stylesheet supplies the `.hll` background rule.
Write a standalone highlighted document write-full-html
from pathlib import Path
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import PythonLexer
page = highlight(source, PythonLexer(), HtmlFormatter(
full=True,
title="example.py",
linenos="table",
))
Path("example.html").write_text(page, encoding="utf-8")`full=True` emits a complete HTML 4 document with embedded style rules. Table line numbers use a separate cell and can expose font-alignment differences.
Collect comments from lexical tokens inspect-token-stream
from pygments import lex
from pygments.lexers import PythonLexer
from pygments.token import Comment
comments = [
value
for token_type, value in lex(source, PythonLexer())
if token_type in Comment
]Token membership includes child types such as `Comment.Single`. This stream has no syntax tree, scope information, or symbol identity.
Style TODO markers inside comments flag-code-tags
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import PythonLexer
lexer = PythonLexer()
lexer.add_filter("codetagify", codetags=["TODO", "FIXME"])
html = highlight(source, lexer, HtmlFormatter())The `codetagify` filter retags matching words only in comments and docstrings. Its output uses the `Comment.Special` style.
Create a full HTML file from the shell run-command-line
pygmentize -l python -f html \
-O full,style=night-owl,linenos=1 \
-o example.html example.pyThe `-l` and `-f` flags avoid lexer and formatter inference. `-o` writes HTML to the named file instead of sending terminal-formatted text to stdout.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| rich | PyPI | Choose Rich 15.0 for a terminal UI with tracebacks, tables, progress, and syntax panels. It still uses Pygments for code coloring. |
| tree-sitter | PyPI | Choose tree-sitter 0.26 when incremental syntax trees and structural queries matter more than ready-made HTML or terminal formatters. |
| pygments-markdown-lexer | PyPI | Add pygments-markdown-lexer when specialized Markdown tokenization is the missing piece. It extends Pygments and does not replace it. |
More utils guides
lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

