Skip to main content
LatinCy Readers

PyPI version Python versions License: MIT Ruff

Corpus readers for Latin and Ancient Greek texts with LatinCy NLP integration.

latincy-readers provides unified access to classical texts—from the Tesserae corpus to Perseus to the Patristic Text Archive and more—with automatic NLP processing via LatinCy spaCy models.

Installation

# Install the package
pip install latincy-readers

# With sentence vector search support
pip install latincy-readers[vectors]

# For development (editable install)
git clone https://github.com/latincy/latincy-readers.git
cd latincy-readers
pip install -e ".[dev]"

Models

LatinCy NLP models are hosted on Hugging Face and installed separately (mirroring spaCy's pattern for language models). Install whichever you need:

# Latin model (la_core_web_lg)
pip install https://huggingface.co/latincy/la_core_web_lg/resolve/main/la_core_web_lg-3.9.8-py3-none-any.whl

# Ancient Greek model (grc_dep_web_lg)
pip install https://huggingface.co/latincy/grc_dep_web_lg/resolve/main/grc_dep_web_lg-3.8.4-py3-none-any.whl

You can skip model installation if you only need raw text iteration (AnnotationLevel.NONE) or rule-based sentence splitting (AnnotationLevel.MINIMAL). All other levels require a model.

Quick Start

from latincyreaders import TesseraeReader, AnnotationLevel

# Auto-download corpus on first use
reader = TesseraeReader()

# Or specify a custom path
reader = TesseraeReader("/path/to/tesserae/corpus")

# Iterate over documents as spaCy Docs
for doc in reader.docs():
    print(f"{doc._.fileid}: {len(list(doc.sents))} sentences")

# Search for sentences containing specific forms
for result in reader.find_sents(forms=["Caesar", "Caesarem"]):
    print(f"{result['citation']}: {result['sentence']}")

# Get raw text (no NLP processing)
for text in reader.texts():
    print(text[:100])

Readers

Reader Format Auto-Download Description
TesseraeReader .tess Yes LatinCy Latin Tesserae corpus
GreekTesseraeReader .tess Yes LatinCy Greek Tesserae corpus
PlaintextReader .txt No Plain text files
LatinLibraryReader .txt Yes Latin Library corpus
TEIReader .xml No TEI-XML documents
PerseusReader .xml No Perseus Digital Library TEI
CamenaReader .xml Yes CAMENA Neo-Latin corpus
DigilibLTReader .xml No digilibLT Late-Antique Latin TEI corpus
PTAReader .xml Yes Patristic Text Archive (Greek & Latin)
CSELReader .xml No Corpus Scriptorum Ecclesiasticorum Latinorum
EDHReader .xml No Epigraphic Database Heidelberg EpiDoc inscriptions
FormulaeReader .xml No Formulae–Litterae–Chartae TEI charters
EpistolaeReader .html.md No Epistolae medieval women's Latin letters
ProjectGutenbergReader .txt Yes (fetch) Project Gutenberg plain-text files
WikiSourceReader .wiki Yes (fetch) Latin Wikisource wikitext
TxtdownReader .txtd No Txtdown format: citations, critical markup, speaker & cross-source quotation
UDReader .conllu No Universal Dependencies CoNLL-U
LatinUDReader .conllu Yes All 6 Latin UD treebanks

Auto-Download

Readers with auto-download support will automatically fetch the corpus on first use:

# Downloads to ~/latincy_data/lat_text_tesserae/texts if not found
reader = TesseraeReader()

# Disable auto-download
reader = TesseraeReader(auto_download=False)

# Use environment variable for custom location
# export TESSERAE_PATH=/custom/path
reader = TesseraeReader()

# Manual download to specific location
TesseraeReader.download("/path/to/destination")

Ancient Greek (GreekTesseraeReader)

Read Ancient Greek texts from the LatinCy Greek Tesserae corpus using LatinCy Greek NLP models:

from latincyreaders import GreekTesseraeReader, AnnotationLevel

# Auto-download Greek Tesserae corpus on first use
reader = GreekTesseraeReader()

# Use TOKENIZE level (no Greek model needed)
reader = GreekTesseraeReader(annotation_level=AnnotationLevel.TOKENIZE)

# Iterate over citation lines
for citation, text in reader.texts_by_line():
    print(f"{citation}: {text[:60]}...")

# Search for Greek words
for fid, cit, text, matches in reader.search(r"Ἀχιλ"):
    print(f"{cit}: found {matches}")

# Environment variable for custom location
# export GRC_TESSERAE_PATH=/custom/path
reader = GreekTesseraeReader()

Universal Dependencies Treebanks

Access gold-standard linguistic annotations from Latin UD treebanks:

from latincyreaders import LatinUDReader, PROIELReader

# See available treebanks
LatinUDReader.available_treebanks()
# {'proiel': 'Vulgate, Caesar, Cicero, Palladius',
#  'perseus': 'Classical texts from Perseus Digital Library',
#  'ittb': 'Index Thomisticus (Thomas Aquinas)',
#  'llct': 'Late Latin Charter Treebank',
#  'udante': "Dante's Latin works",
#  'circse': 'CIRCSE Latin treebank'}

# Use a specific treebank
reader = PROIELReader()

# Iterate sentences with UD annotations
for sent in reader.ud_sents():
    print(f"{sent._.citation}: {sent.text}")

# Access full UD token data
for token in doc:
    ud = token._.ud  # dict with all 10 CoNLL-U columns
    print(f"{token.text}: {ud['upos']} {ud['feats']}")

# Read from all treebanks at once
reader = LatinUDReader()
LatinUDReader.download_all()  # Download all 6 treebanks

Note: Unlike other readers, UDReader constructs spaCy Docs directly from gold UD annotations rather than running the spaCy NLP pipeline.

Core API

All readers provide a consistent interface:

reader.fileids()              # List available files
reader.texts(fileids=...)     # Raw text strings (generator)
reader.docs(fileids=...)      # spaCy Doc objects (generator)
reader.sents(fileids=...)     # Sentence spans (generator)
reader.tokens(fileids=...)    # Token objects (generator)
reader.metadata(fileids=...)  # File metadata (generator)

FileSelector: Fluent File Filtering

Use the select() method for complex file queries combining filename patterns and metadata:

# Filter by filename pattern (regex)
vergil_docs = reader.select().match(r"vergil\..*")

# Filter by metadata
epics = reader.select().where(genre="epic")

# Multiple conditions (AND)
vergil_epics = reader.select().where(author="Vergil", genre="epic")

# Match any of multiple values
major_authors = reader.select().where(author__in=["Vergil", "Ovid", "Horace"])

# Date ranges
augustan = reader.select().date_range(-50, 50)

# Chain multiple filters
selection = (reader.select()
    .match(r".*aen.*")
    .where(genre="epic")
    .date_range(-50, 50))

# Use with docs(), sents(), etc.
for doc in reader.docs(selection):
    print(doc._.fileid)

# Preview results
print(selection.preview(5))
print(f"Found {len(selection)} files")

Search API

# Fast regex search (no NLP)
reader.search(pattern=r"\bbell\w+")

# Form-based sentence search
reader.find_sents(forms=["amor", "amoris"])

# Lemma-based search (requires NLP)
reader.find_sents(lemma="amo")

# spaCy Matcher patterns
reader.find_sents(matcher_pattern=[{"POS": "ADJ"}, {"POS": "NOUN"}])

Text Analysis

# Build a concordance (word -> citations mapping)
conc = reader.concordance(basis="lemma")
print(conc["amor"])  # ['<catull. 1.1>', '<verg. aen. 4.1>', ...]

# Keyword in Context
for hit in reader.kwic("amor", window=5, by_lemma=True):
    print(f"{hit['left']} [{hit['match']}] {hit['right']}")
    print(f"  -- {hit['citation']}")

# N-grams
for ngram in reader.ngrams(n=2, basis="lemma"):
    print(ngram)  # "qui do", "do lepidus", ...

# Skip-grams (n-grams with gaps)
for sg in reader.skipgrams(n=2, k=1):
    print(sg)

Find semantically similar sentences across the corpus using sentence-level embeddings. Requires the vectors extra (pip install latincyreaders[vectors]).

from latincyreaders import TesseraeReader
from latincyreaders.cache.vectors import SentenceVectorConfig, SentenceVectorStore

reader = TesseraeReader()

# Build a vector index (saved to ~/latincy_data/vectors/<collection>/)
cfg = SentenceVectorConfig(collection="tesserae")
store = SentenceVectorStore(cfg)
store.build(reader)

# Semantic search
results = store.similar_to_sent("arma virumque cano", reader.nlp, top_k=5)
for r in results:
    print(f"[{r['score']:.3f}] {r['citation']}: {r['text'][:80]}")

# Or use the reader shortcut
results = reader.find_similar("amor", top_k=5, config=cfg)

# Auto-build on first query (builds index if none exists)
results = reader.find_similar("amor", auto_build=True)

# Find sentences similar to one already in the index
results = store.similar_to_doc_sent("vergil.aeneid.part.1.tess", 0, top_k=5)

# Index statistics
print(store.stats())
# {'collection': 'tesserae', 'sentences': 15800, 'vector_dim': 300, ...}

Vectors are stored as memory-mapped NumPy arrays for efficient search without external dependencies. See notebooks/vector-search-demo.ipynb for a full walkthrough.

Document Caching

Documents are cached by default for better performance when accessing the same file multiple times:

# Caching enabled by default
reader = TesseraeReader()

# Disable caching
reader = TesseraeReader(cache=False)

# Configure cache size
reader = TesseraeReader(cache_maxsize=256)

# Check cache statistics
print(reader.cache_stats())  # {'hits': 5, 'misses': 3, 'size': 3, 'maxsize': 128}

# Clear the cache
reader.clear_cache()

Persistent Disk Cache

For large corpora, enable persistent caching to avoid re-running the NLP pipeline across sessions. Cached documents are stored as .spacy DocBin files in ~/.latincy_cache/<collection>/ by default:

from latincyreaders import TesseraeReader
from latincyreaders.cache.disk import CacheConfig

# Enable disk caching for the Tesserae corpus
config = CacheConfig(persist=True, collection="tesserae")
reader = TesseraeReader(model_name="la_core_web_lg", cache_config=config)

# First call runs NLP and caches to disk
doc = next(reader.docs(fileids="vergil.aeneid.part.1.tess"))

# Subsequent calls load from cache (~100x faster)
doc = next(reader.docs(fileids="vergil.aeneid.part.1.tess"))

# Custom cache location
config = CacheConfig(
    persist=True,
    collection="tesserae",
    cache_dir="/path/to/cache",
)

# Time-to-live (auto-expire after N days)
config = CacheConfig(persist=True, collection="tesserae", ttl_days=30)

Model-version awareness

Cached annotations are stamped with the model that produced them (model_name + model_version). The DocBin cache is treated as a stamp-keyed ephemeral layer: if you upgrade the LatinCy model (say la_core_web_lg 3.9.4 → 3.9.6), the stamp no longer matches, so the stale blob is discarded and the document is transparently re-annotated with the new model. You never get sentence boundaries or tags from an old model without knowing it.

The canonical .conlluc store (community-corrected, shipped with a corpus) is more valuable and is not thrown away automatically. Instead, loading a .conlluc built by a different model emits a warning so you can decide whether to rebuild:

import warnings
from latincyreaders.warnings import AnnotationModelMismatchWarning

# ... reading docs whose canonical annotations predate your active model ...
# AnnotationModelMismatchWarning: canonical annotations for 'vergil.aen.tess'
#   were built with la_core_web_lg 3.9.4; active model is la_core_web_lg 3.9.6.
#   Boundaries/tags may be stale. Rebuild the canonical store (or delete the
#   .conlluc to regenerate) if you need annotations from the active model.

# Escalate to an error if a version mismatch should never pass silently:
warnings.simplefilter("error", AnnotationModelMismatchWarning)

Cache entries written before v1.8.0 carry no stamp and are rebuilt once on first access — no manual cache deletion needed.

Correcting Annotations

NLP output has errors. You can lock in corrections that make the cache trustworthy and double as a training-ready record of human judgements. Corrections are a small JSON overlay on the compact DocBin base cache, linked by durable opaque token ids:

from latincyreaders import TesseraeReader, AnnotationLevel
from latincyreaders.cache.disk import CacheConfig

reader = TesseraeReader(
    annotation_level=AnnotationLevel.FULL,
    cache_config=CacheConfig(persist=True, collection="tesserae"),
)

doc = next(reader.docs("vergil.aeneid.part.1.tess"))
tok = doc[0]

# Lock in a lemma fix, keyed by the token's durable id.
reader.correct(
    "vergil.aeneid.part.1.tess", tok._.token_id,
    "lemma", "arma", evidence="clearly the noun here",
)

# Every later read overlays the gold value; the token is flagged.
doc = next(reader.docs("vergil.aeneid.part.1.tess"))
assert doc[0].lemma_ == "arma"
assert doc[0]._.corrected is True

Corrections (fields: lemma, upos, xpos, feats, deprel) are stored outside the DocBin cache — in ~/latincy_data/corrections/<collection>/ by default, or a directory you pass as corrections_dir — so they survive clear_cache() and full re-annotation. The base DocBin is never mutated: it stays "silver," and each correction records the machine value it overrode. When a model upgrade or source edit re-tokenizes a text, corrections are re-pointed onto the new tokenization (verified by surface form); anything that can't be placed safely is quarantined to corrections_unresolved.log for manual review rather than mis-applied.

Annotation Levels

All linguistic annotations are provided by LatinCy spaCy-based pipelines. The full pipeline provides POS tagging, lemmatization, morphological analysis, and named entity recognition—but this can be slow for large corpora. If you don't need all annotations, you can get significant performance gains by selecting a lighter annotation level:

from latincyreaders import AnnotationLevel

# Full pipeline: POS, lemma, morphology, NER (default)
reader = TesseraeReader(annotation_level=AnnotationLevel.FULL)

# Basic: tokenization + sentence boundaries only
reader = TesseraeReader(annotation_level=AnnotationLevel.BASIC)

# Tokenization only (no sentence boundaries)
reader = TesseraeReader(annotation_level=AnnotationLevel.TOKENIZE)

# No NLP at all - use texts() for raw strings
for text in reader.texts():
    print(text)

Metadata Management

from latincyreaders import MetadataManager, MetadataSchema

# Load and merge metadata from JSON files
manager = MetadataManager("/path/to/corpus")

# Access metadata
meta = manager.get("vergil.aen.tess")
print(meta["author"], meta["date"])

# Filter files by metadata
for fileid in manager.filter_by(author="Vergil", genre="epic"):
    print(fileid)

# Date range filtering
for fileid in manager.filter_by_range("date", -50, 50):
    print(fileid)

# Validate metadata against a schema
schema = MetadataSchema(
    required={"author": str, "title": str},
    optional={"date": int, "genre": str}
)
manager = MetadataManager("/path/to/corpus", schema=schema)
result = manager.validate()
if not result.is_valid:
    print(result.errors)

Corpora Supported

CLI Tools

Tools in cli/:

# Sentence search
python cli/reader_search.py --lemmas Caesar --limit 100
python cli/reader_search.py --forms Caesar Caesarem --limit 100
python cli/reader_search.py --pattern "\\bTheb\\w+" --output thebes.tsv

# Vector search — build and query sentence vector indices
python cli/vector_search.py build
python cli/vector_search.py build --collection vergil --fileids "vergil.*"
python cli/vector_search.py query "arma virumque cano" --top-k 10
python cli/vector_search.py stats

Bibliography

Method

  • Bird, S., E. Loper, and E. Klein. 2009. Natural Language Processing with Python. O'Reilly: Sebastopol, CA.
  • Bengfort, Benjamin, Rebecca Bilbro, and Tony Ojeda. 2018. Applied Text Analysis with Python: Enabling Language-Aware Data Products with Machine Learning. O'Reilly: Sebastopol, CA.

Collections

  • Tesserae Latin Corpus
    • Coffee, Neil, Jean-Pierre Koenig, Shakthi Poornima, Roelant Ossewaarde, Christopher Forstall, and Sarah Jacobson. 2012. "Intertextuality in the Digital Age." Transactions of the American Philological Association 142 (2): 383–422. https://doi.org/10.1353/apa.2012.0010.
    • Tesserae Project. 2026. LatinCy Tesserae Latin Corpus. Version 0.7.0. Edited by Patrick J. Burns and Classical Language Toolkit. https://github.com/latincy/lat_text_tesserae.
  • Tesserae Greek Corpus
  • Perseus Digital Library TEI
  • Latin Library
  • CAMENA Neo-Latin
  • digilibLT
  • Patristic Text Archive
  • Corpus Scriptorum Ecclesiasticorum Latinorum
  • Epigraphic Database Heidelberg
  • Formulae–Litterae–Chartae
  • Epistolae
  • Project Gutenberg
  • Latin Wikisource
  • Universal Dependencies Latin Treebanks
    • Nivre, Joakim, Marie-Catherine de Marneffe, Filip Ginter, Jan Hajič, Christopher D. Manning, Sampo Pyysalo, Sebastian Schuster, Francis Tyers, and Daniel Zeman. 2020. "Universal Dependencies v2: An Evergrowing Multilingual Treebank Collection." In Proceedings of the Twelfth Language Resources and Evaluation Conference, 4034–4043. Marseille: European Language Resources Association. https://aclanthology.org/2020.lrec-1.497/.
    • Gamba, Federica, and Daniel Zeman. 2023. "Universalising Latin Universal Dependencies: a harmonisation of Latin treebanks in UD." In Proceedings of the Sixth Workshop on Universal Dependencies (UDW, GURT/SyntaxFest 2023), 7–16. Washington, D.C.: Association for Computational Linguistics. https://aclanthology.org/2023.udw-1.2/.
    • PROIEL
      • Haug, Dag T. T., and Marius L. Jøhndal. 2008. "Creating a Parallel Treebank of the Old Indo-European Bible Translations." In Proceedings of the Second Workshop on Language Technology for Cultural Heritage Data (LaTeCH 2008), edited by Caroline Sporleder and Kiril Ribarov, 27–34.
    • Perseus
      • Bamman, David, and Gregory Crane. 2011. "The Ancient Greek and Latin Dependency Treebanks." In Language Technology for Cultural Heritage, edited by Caroline Sporleder, Antal van den Bosch, and Kalliopi Zervanou, 79–98. Berlin, Heidelberg: Springer. https://doi.org/10.1007/978-3-642-20227-8_5.
    • ITTB
      • Cecchini, Flavio Massimiliano, Marco Passarotti, Paola Marongiu, and Daniel Zeman. 2018. "Challenges in Converting the Index Thomisticus Treebank into Universal Dependencies." In Proceedings of the Second Workshop on Universal Dependencies (UDW 2018), 27–36. Brussels. https://doi.org/10.18653/v1/W18-6004.
    • LLCT
      • Cecchini, Flavio Massimiliano, Timo Korkiakangas, and Marco Passarotti. 2020. "A New Latin Treebank for Universal Dependencies: Charters between Ancient Latin and Romance Languages." In Proceedings of the Twelfth Language Resources and Evaluation Conference, 933–942. Marseille: European Language Resources Association. https://aclanthology.org/2020.lrec-1.117/.
    • UDante
      • Cecchini, Flavio Massimiliano, Rachele Sprugnoli, Giovanni Moretti, and Marco Passarotti. 2020. "UDante: First Steps Towards the Universal Dependencies Treebank of Dante's Latin Works." In Proceedings of the Seventh Italian Conference on Computational Linguistics, edited by Johanna Monti, Felice Dell'Orletta, and Fabio Tamburini. CEUR Workshop Proceedings 2769. https://ceur-ws.org/Vol-2769/paper_14.pdf.
    • CIRCSE

Developed by Patrick J. Burns with Claude Code in 2026.

Metadata

Release files for latincy-readers 1.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for latincy-readers 1.9.0
File Size Uploaded
latincy_readers-1.9.0.tar.gz 131.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for latincy-readers 1.9.0
File Interpreter ABI Platform
latincy_readers-1.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 283.7 kB

Release files / latincy_readers-1.9.0.tar.gz

Download URL latincy_readers-1.9.0.tar.gz
Size 131.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1a3a34380042456e6c1456068c0da62a10a05b29a4fb89d3f8d6d4c4f9f7dbfd
BLAKE2b-256 checksum
How to use checksums
3ec103f7af72dc4a331127af8d62a4bc8ea0c581deef216f25195916569a7a5f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / latincy_readers-1.9.0-py3-none-any.whl

Download URL latincy_readers-1.9.0-py3-none-any.whl
Size 151.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
286629fe96e2b84418e3f1029ad709765402df8059b5e35cc1ff2586585d63c9
BLAKE2b-256 checksum
How to use checksums
2fd3c733c802f7068044334602e44569fa09ed33c3c3a8529bd6b3cfca439842
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

1.9.0 This release

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.4.0

2 release files

1.2.0

2 release 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