Skip to main content

nota2md

Three entry points, all re-exported off the package itself (from nota2md import ...), for Mexico's official gazette (DOF, Diario Oficial de la Federación) and the federal laws it publishes:

Entry point Given Returns/writes
legal_provisions a legal provision's codNota its Markdown, written to outdir/nota-{codNota}.md
normative_reconstruction a law's reform history (codNota list) its current text, written to outdir/ley-{codNota}.md
download_normative_history a collection name ("leyes", "reglamentos", "normas", "tratados") every instrument's reform history, in memory

They compose: download_normative_history gets you the codNota list normative_reconstruction needs, and normative_reconstruction gets you a law's current text the same way legal_provisions gets you a single legal provision's — built from nothing but the DOF's own legal provisions, one Markdown file at a time.

from nota2md import download_normative_history, legal_provisions, normative_reconstruction

leyes = download_normative_history("leyes")
cpeum = next(l for l in leyes if l["abrev"] == "cpeum")

dest = normative_reconstruction(cpeum["historial"], "output", cpeum["nombre"])
print(f"{cpeum['nombre']} -> {dest}")

legal_provisions — a single DOF legal provision as Markdown

Builds the Markdown of a single DOF legal provision, identified by its codNota.

Where dof2md converts a whole edition PDF and dofjson is a thin client for SIDOF's JSON service, legal_provisions ties them together to produce the Markdown for one legal provision, from any of three sources:

Source How When
HTML Converts the legal provision's cadenaContenido HTML directly (a DOF-tailored BeautifulSoup converter). The legal provision has digital text. Preferred: clean, already scoped to the one legal provision, no OCR.
Image Downloads the legal provision's scanned page image(s) via dofjson, OCRs them with dof2md/mineru, then slices out the one legal provision. Image-only legal provisions — or any legal provision, when you want the certified scanned original.
PDF Downloads the legal provision's own PDF (the edition PDF sliced to the legal provision's pages, via dofjson.download_nota_pdf), OCRs it with dof2md/mineru, then slices out the one legal provision. When you'd rather OCR a PDF than page images.

Both OCR paths (image and PDF) mirror the HTML path's output style (#/## headings, **bold**, *italic*, GitHub tables — dof2md rewrites mineru's HTML tables to Markdown), so a legal provision's Markdown looks much the same whichever source it came from.

Legal provisions SIDOF does not have

SIDOF is missing whole days of the gazette (see dofjson), and the legal provisions published on them have no SIDOF record at all — no cadenaContenido, and no codDiario or page numbers for the OCR paths to start from. When SIDOF answers {"Nota": []} for a codNota, legal_provisions looks the legal provision up on the DOF's own website instead, which serves the same HTML:

nota2md 4997808 --outdir output   # DOF 03-03-1999, a day SIDOF lost

The HTML path is the only one that can build these legal provisions; asking for --source image or --source pdf on one raises rather than fetching the wrong pages.

Cutting a legal provision out of its page

A scanned page (or a sliced PDF) usually holds more than one legal provision: it can begin with the tail of the previous legal provision and end with the start of the next. legal_provisions uses the per-day legal provision index — which lists every legal provision's title in order — to locate two boundaries in the OCR'd text (where this legal provision's title appears, and where the next legal provision's title appears) and keeps only what lies between. Matching is fuzzy (accent-folded, marker-stripped, difflib alignment) to tolerate OCR differences, and it also drops the next legal provision's organism header that the DOF prints above its title.

fetch_day_notes(date) (nota2md.builder) is the per-day index itself — a day's browsable legal provisions (title, codNota, codEdicion...), from SIDOF and, when SIDOF has nothing for that day, from the DOF's own website:

from nota2md.builder import fetch_day_notes
import datetime as dt

for nota in fetch_day_notes(dt.date(2026, 7, 15))["NotasMatutinas"]:
    print(nota["codNota"], nota["titulo"])

Usage

# HTML when available, otherwise OCR of the scanned page(s)
nota2md 5793655 --outdir output

# force the scanned-image + OCR path, sourcing the next legal provision's
# title from a saved notas index (avoids an extra request; works offline)
dofjson 2026-07-15 --outdir output          # writes 15072026-notas.json
nota2md 5793655 --source image --notas output/15072026-notas.json --outdir output

# force the PDF + OCR path (edition PDF sliced to the legal provision's pages)
nota2md 5793655 --source pdf --notas output/15072026-notas.json --outdir output

Programmatically:

from nota2md import legal_provisions

legal_provisions(5793655, "output")                 # -> output/nota-5793655.md

The HTML path needs only beautifulsoup4; the image and PDF paths additionally need dof2md (and mineru), imported lazily so the HTML path works without them.

normative_reconstruction — a law's current text from its DOF legal provisions

Builds a law's current (vigente) text from nothing but its DOF legal provisions: starts from the original publication and replays each reform decree's own "se reforma/adiciona/deroga el artículo N... para quedar como sigue" instruction on top of it, article by article — filling back in, from the article's own previous text, every fracción or inciso a reform elides with "..." instead of repeating. It never reads a law's official consolidated ("texto vigente") text; that exists separately (nota2md.texto_vigente) only as independent ground truth to check reconstructions against, in tests/test_leyes_44.py, over 43 real federal laws.

from nota2md import normative_reconstruction

# cpeum's own historial: [5592105, 5730586, ...] — oldest first, index 0 the
# original publication (see download_normative_history below for where a
# law's own historial list comes from).
dest = normative_reconstruction(
    [5592105, 5730586], "output", "LEY de Amnistía",
)
print(dest.read_text(encoding="utf-8"))   # -> output/ley-5592105.md

Each legal provision it needs is fetched through legal_provisions into the same outdir, as nota-{codNota}.md — a legal provision already there from an earlier call (this law's own previous run, or another law's sharing the same outdir) is read back from disk instead of fetched again.

The third argument, nombre_ley (as download_normative_history names it, e.g. "LEY de Amnistía"), scopes every legal provision to the one instrument among the several a single decree may touch — pass it whenever a legal provision is shared with another law's history, which leyesmx's data does not mark on its own. Left out, a legal provision is assumed to concern only this law, which holds for most of them but silently mixes in another law's articles for the rest.

download_normative_history — a law's reform history

Reads a Mexican legislative-history collection — laws, regulations, Normas Oficiales Mexicanas, international treaties — back from the historial-legislativo release that leyesmx publishes:

from nota2md import download_normative_history

leyes = download_normative_history("leyes")   # or "reglamentos", "normas", "tratados"
cpeum = next(l for l in leyes if l["abrev"] == "cpeum")
print(cpeum["nombre"], cpeum["reformas"], len(cpeum["historial"]))

Downloads that collection's tarball straight into memory — nothing touches disk — and returns one dict per instrument, merging its catalogue entry (name, reform count, dates...) with its own historial: the codNota of its reforms or decrees, oldest first, index 0 the original publication. That is exactly what normative_reconstruction expects as its own first argument.

Installation

pip install nota2md          # legal_provisions' HTML path, plus normative_reconstruction
                              # and download_normative_history
pip install nota2md[ocr]     # also pulls in dof2md, for legal_provisions' image/PDF OCR paths

dofjson and requests are hard dependencies and install automatically. For development in this monorepo, install the siblings editable instead so local edits are picked up:

pip install -e "packages/dofjson"
pip install -e "packages/dof2md"          # only needed for the image/PDF OCR paths
pip install -e "packages/nota2md[test]"

Development

pytest packages/nota2md

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nota2md-0.3.0.tar.gz (43.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nota2md-0.3.0-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file nota2md-0.3.0.tar.gz.

File metadata

  • Download URL: nota2md-0.3.0.tar.gz
  • Upload date:
  • Size: 43.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for nota2md-0.3.0.tar.gz
Algorithm Hash digest
SHA256 0a8dcda5432fec23f850d17266fa9b8ddd315b72befa74ea5cf5fcb19d894808
MD5 d305f99c9ed2b4f16247c922c26bfe56
BLAKE2b-256 e093c061a02ad9629e07caa8bafa3761d4426e6a7cb54fa31e8a0e6040388c6d

See more details on using hashes here.

File details

Details for the file nota2md-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: nota2md-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for nota2md-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 78bc97bf929a1b8d36301ff6f3a100e39248157894b16144fe7e438d580b1566
MD5 9f9993aed02c9f7afe35308c8abef6df
BLAKE2b-256 490d4dba41b94152225e6714aed4cd4f64a34b62ea9dbaf4107c4351b2b07dbb

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.1

2 files

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 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