Skip to main content

nota2md

Five 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
reconstruct_legal_provisions a law's reform history (codNota list) its current text, written to outdir/ley-{codNota}.md
download_legal_provisions_provenance_ids a collection name ("leyes", "reglamentos", "normas", "tratados") every instrument's reform history, in memory
fetch_daily_legal_provisions a date that day's browsable legal provisions (title, codNota, codEdicion...)
download_legal_provisions_titles nothing (reads the whole notas-archivo release) every legal provision ever published, as codNota+titulo+fecha, written to a gzipped JSONL file

They compose: download_legal_provisions_provenance_ids gets you the codNota list reconstruct_legal_provisions needs, and reconstruct_legal_provisions 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_legal_provisions_provenance_ids, legal_provisions, reconstruct_legal_provisions

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

dest = reconstruct_legal_provisions(cpeum["historial"], "output", nombre_ley=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_daily_legal_provisions(date) 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 import fetch_daily_legal_provisions
import datetime as dt

for nota in fetch_daily_legal_provisions(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.

reconstruct_legal_provisions — 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 reconstruct_legal_provisions

# cpeum's own historial: [5592105, 5730586, ...] — oldest first, index 0 the
# original publication (see download_legal_provisions_provenance_ids below for where a
# law's own historial list comes from).
dest = reconstruct_legal_provisions(
    [5592105, 5730586], "output", nombre_ley="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.

nombre_ley (as download_legal_provisions_provenance_ids 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.

source, min_confidence and keep_pages are the same parameters legal_provisions itself takes, forwarded as-is to the call made for every legal provision in the history. The default here is source="html", not legal_provisions's own "auto": the article-merge this function does (_fusiona_articulo) was designed and checked against HTML-derived Markdown, so a legal provision missing cadenaContenido still fails by default. Passing source="image" or "pdf" OCRs it instead of failing, but the merge's behavior on OCR output has not been validated — review the result before trusting it.

download_legal_provisions_provenance_ids — 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_legal_provisions_provenance_ids

leyes = download_legal_provisions_provenance_ids("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 reconstruct_legal_provisions expects as its own first argument.

download_legal_provisions_titles — every legal provision ever published, as titles

Implemented in dofjson.titulos and re-exported here so it sits alongside the rest of nota2md's entry points. Builds a compact codNota + titulo + fecha + codOrgaUno dataset covering every legal provision published since 1917 (~1.2 million rows, a few tens of MB compressed), read straight from the notas-archivo release — nothing downloaded touches disk except the two result files:

from pathlib import Path
from nota2md import download_legal_provisions_titles

download_legal_provisions_titles(Path("titulos.jsonl.gz"))
dofjson --titulos --outdir output    # -> output/titulos.jsonl.gz

Installation

pip install nota2md          # legal_provisions' HTML path, plus reconstruct_legal_provisions
                              # and download_legal_provisions_provenance_ids
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.4.0.tar.gz (45.4 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.4.0-py3-none-any.whl (32.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for nota2md-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a56ac9887210a6dd1e0f89335f5d4b3c3cde965af7a72975e0946a29da7c24e2
MD5 b0931eea938fca637a05e036ee4f2d62
BLAKE2b-256 81a96396f454772b51ddddfe9d300a986f566d04e239d84e494ab2d6614b0e4b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nota2md-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 32.0 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.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e2c78e51560a414e88d37e828ce44f4a9d3ca6b683014b2d1d5fb6867e53b90
MD5 33cf7ff470e2ba65a2a69e6443570a3f
BLAKE2b-256 e2dc247e98a2764568cf860deeee62414a76330e5fdd5efa0dec14f0c9c3d648

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.1

2 files

This release

0.4.0 This release

2 files

0.3.0

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