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. |
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nota2md-0.4.1.tar.gz.
File metadata
- Download URL: nota2md-0.4.1.tar.gz
- Upload date:
- Size: 46.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00245cd52c0d0cfd4a9d1934acb20e58db4c910baf64f31a3ff32ebe89502543
|
|
| MD5 |
ff2b36d3c7284853c632df2a9ed4275a
|
|
| BLAKE2b-256 |
e3547fbb708c31603745a03447a953eb71d95bb05968534b4813d4152986516d
|
File details
Details for the file nota2md-0.4.1-py3-none-any.whl.
File metadata
- Download URL: nota2md-0.4.1-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c58a7e0b28d1dd3e76a18baa84e0618d9d59ba1b3f6dbf9820bb05d828fd5365
|
|
| MD5 |
375a0ffe879285955da1d530acee1f81
|
|
| BLAKE2b-256 |
cc73bee1a826e5ba9b399faf7668b9bd6e2ce4c7635d774d9c586a49b91c4343
|