mrkeyoor.com_
Sat 19 Sept 23:45 UTC
PyPIUtilsupdated 19 Sept 2026

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.

Verdict

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

Lab card: what happened when we installed pygmentsScreenshot of pygments documentation
Install✓ · 0.3s1 package on disk · 6 MB
Importimport pygments in 0.01s · pure Python · requires Python >=3.9
Known vulns0(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

API stability5/5Pygments 2.x still centers on `highlight()`, lexer lookup helpers, formatter instances, token types, filters, styles, and plugin entry points. Version 2.21.0 adds languages and a style without replacing those calls. Exact output is a looser contract: this release repairs many lexer boundaries and changes HTML quote rendering, so teams that store generated markup should pin the version and review snapshot changes.
Docs4/5The official Pygments docs separate the Python API, command line, lexer catalog, formatter options, filters, styles, tokens, and plugin registration. `HtmlFormatter` documents details such as line-anchor IDs, table versus inline numbers, and the output cost of `noclasses=True`. The README gives direct timeout and concurrency advice for denial-of-service cases. Production guidance is spread across those pages, so a quick-start reader can miss the security boundary.
Maintenance4/5Pygments 2.21.0 was released on August 17, 2026, and GitHub records the latest push on the same date. The repository is unarchived and shows 663 open issues and pull requests. The release fixes catastrophic backtracking in XML detection, corrects dozens of language grammars, and repairs several formatters. That is active work, though each report joins a queue covering more than 500 languages and text formats.
Ecosystem5/5PyPI counted 204,457,300 Pygments downloads in the latest week, while GitHub reports 2,207 stars. The README lists support for more than 500 languages and text formats, and the package feeds documentation sites, code hosts, terminals, and Sphinx-style build systems. Entry points let installed distributions add lexers, formatters, filters, and styles that the normal lookup functions and `pygmentize` command can discover.

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
Skip it if

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.py

The `-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

PackageRegistryPick it when
richPyPIChoose Rich 15.0 for a terminal UI with tracebacks, tables, progress, and syntax panels. It still uses Pygments for code coloring.
tree-sitterPyPIChoose tree-sitter 0.26 when incremental syntax trees and structural queries matter more than ready-made HTML or terminal formatters.
pygments-markdown-lexerPyPIAdd 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.