Skip to main content

marc-lint

CI PyPI version Python versions codecov License: MIT

Python port of the Perl MARC::Lint module for validating MARC21 bibliographic records.

Now added new functionality including added leader and control validation and improved article validation based on language.

Features

  • ✅ Comprehensive MARC21 validation
  • ✅ Leader and control field (008) validation
  • ✅ ISBN/ISSN validation using industry-standard algorithms
  • ✅ Language and geographic code validation
  • ✅ Article/non-filing indicator validation
  • ✅ Batch processing with per-record identification
  • ✅ Command-line tool with JSON output support
  • ✅ Python 3.10+ support
  • ✅ Type hints and comprehensive test coverage

Installation

pip install marc-lint

Or with uv:

uv pip install marc-lint

Quick Start

Command Line Interface

Lint a MARC file and display warnings:

marc-lint records.mrc

Example output:

--- Record ocm12345678 ---
  020: Subfield a has bad checksum, 123456789X.
  245: Must end with . (period).

--- Record ocm87654321 ---
  022: Subfield a has bad checksum, 1234-5678.

============================================================
Processed 2 record(s)
Found 3 warning(s) in 2 record(s)

CLI Options

# Output as JSON (useful for automation)
marc-lint records.mrc --format json

# Quiet mode (warnings only, no summary)
marc-lint records.mrc --quiet

# Use record index as ID when 001 field is missing
marc-lint records.mrc --use-index

# Combine options
marc-lint records.mrc -f json -q

JSON Output Format

marc-lint records.mrc --format json
[
  {
    "record_id": "ocm12345678",
    "is_valid": false,
    "warnings": [
      {
        "field": "020",
        "message": "has bad checksum, 123456789X.",
        "subfield": "a",
        "position": null,
        "record_id": "ocm12345678"
      }
    ]
  },
  {
    "record_id": "ocm87654321",
    "is_valid": true,
    "warnings": []
  }
]

Exit Codes

Code Meaning
0 No warnings found
1 Warnings found
2 Error reading file

Use in CI/CD pipelines:

marc-lint catalog.mrc && echo "All records valid!"

Python Library - Single Record

from marc_lint import MarcLint
from pymarc import MARCReader

# Create a linter instance
linter = MarcLint()

# Process MARC records one at a time
with open('records.mrc', 'rb') as fh:
    reader = MARCReader(fh)
    for record in reader:
        warnings = linter.check_record(record)
        
        if warnings:
            print(f"Record has {len(warnings)} warnings:")
            for warning in warnings:
                print(f"  - {warning}")

Python Library - Batch Processing

For processing multiple records with per-record identification:

from marc_lint import MarcLint
from pymarc import MARCReader

linter = MarcLint()

with open('records.mrc', 'rb') as fh:
    reader = MARCReader(fh)
    records = list(reader)

# Process all records at once
results = linter.check_records(records, use_index_as_id=True)

for result in results:
    if not result.is_valid:
        print(f"Record {result.record_id}:")
        for warning in result.warnings:
            print(f"  - {warning}")

# Summary statistics
total_warnings = sum(len(r.warnings) for r in results)
invalid_records = sum(1 for r in results if not r.is_valid)
print(f"Found {total_warnings} warnings in {invalid_records} of {len(results)} records")

Python Library - Structured Warnings

For automation and API integration, use structured warnings:

from marc_lint import MarcLint
from pymarc import MARCReader

linter = MarcLint()

with open('records.mrc', 'rb') as fh:
    reader = MARCReader(fh)
    for record in reader:
        linter.check_record(record)
        
        # Get structured warning objects
        for warning in linter.warnings_structured():
            print(f"Record: {warning.record_id}")
            print(f"Field: {warning.field}")
            print(f"Message: {warning.message}")
            if warning.subfield:
                print(f"Subfield: {warning.subfield}")
            if warning.position is not None:
                print(f"Position: {warning.position + 1}")

Python Library - JSON Output

Export warnings as JSON for APIs:

import json
from marc_lint import MarcLint
from pymarc import MARCReader

linter = MarcLint()

with open('records.mrc', 'rb') as fh:
    reader = MARCReader(fh)
    records = list(reader)

results = linter.check_records(records)

# Convert to JSON
output = [
    {
        "record_id": r.record_id,
        "is_valid": r.is_valid,
        "warnings": [w.to_dict() for w in r.warnings]
    }
    for r in results
]
print(json.dumps(output, indent=2))

Example JSON output:

[
  {
    "field": "020",
    "message": "has bad checksum, 123456789X.",
    "subfield": "a",
    "position": null,
    "record_id": "ocm12345678"
  }
]

Python Library - Filtering Warnings

# Filter warnings by field
isbn_warnings = [
    w for w in linter.warnings_structured() 
    if w.field == "020"
]

# Filter by subfield
subfield_a_warnings = [
    w for w in linter.warnings_structured() 
    if w.subfield == "a"
]

# Filter by record
results = linter.check_records(records)
for result in results:
    leader_warnings = [w for w in result.warnings if w.field == "LDR"]

See STRUCTURED_WARNINGS.md for detailed documentation on structured warnings.

Validation Rules

Leader Validation

Validates leader positions:

  • Position 05: Record status
  • Position 06: Type of record
  • Position 07: Bibliographic level
  • Position 08: Type of control
  • Position 09: Character coding scheme
  • Position 17: Encoding level
  • Position 18: Descriptive cataloging form
  • Position 19: Multipart resource record level

Control Field 008 Validation

Validates:

  • Field length (40 characters)
  • Type of date (position 06)
  • Date 1 and Date 2 (positions 07-14)
  • Country code (positions 15-17)
  • Language code (positions 35-37)
  • Modified record indicator (position 38)
  • Cataloging source (position 39)

Supported Fields

  • 020: ISBN validation with checksum verification
  • 022: ISSN validation with checksum verification
  • 041: Language codes (ISO 639-2)
  • 043: Geographic area codes (MARC Geographic Areas)
  • 130, 240, 630, 730, 830: Non-filing indicator validation
  • 245: Comprehensive title validation (punctuation, indicators, subfield order)
  • 880: Alternate graphic representation validation
  • Plus general field/subfield/indicator validation for all tags

Architecture

Files

  • marc_lint.linter.py – main MarcLint class, equivalent and extension to MARC::Lint.
  • marc_lint.code_data.py – translation of the code tables from MARC::Lint::CodeData (language, geographic area, country codes).
  • marc_lint.field_rules.py – equivalent to __DATA__ and _read_rules in MARC::Lint.
  • marc_lint.warning.py – MarcWarning class for structured warnings.
  • marc_lint.cli.py – command-line interface.

Key behavior mirrored from the Perl module:

  • Per-record checks in MarcLint.check_record: leader validation, 1XX-count, required 245, field repeatability, indicator validity, subfield legality and repeatability, and control-field rules.
  • Tag-specific methods check_020, check_022, check_041, check_043, check_245, plus _check_article for handling non-filing indicators.
  • Rules for tags are built in _read_rules from RULES_DATA, which is a equivalent to __DATA__ table in Lint.pm.

Development

Setup

# Clone the repository
git clone https://github.com/coliin8/marclint.git
cd marclint

# Install dependencies (including dev dependencies)
uv sync --all-groups

# Activate the virtual environment (optional, uv run handles this automatically)
source .venv/bin/activate

Common Development Tasks

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=src/marc_lint --cov-report=term-missing

# Run tests across all Python versions (3.10-3.14)
uv run tox

# Run linting
uv run tox -e lint

# Auto-format code
uv run tox -e format

# Run specific test file
uv run pytest tests/test_warning.py -v

# Run specific test
uv run pytest tests/test_warning.py::test_warning_basic_creation -v

# Run bump version of project
uv run bump-my-version bump patch

Code Quality

This project uses:

  • pytest for testing
  • pytest-cov for coverage reporting
  • ruff for linting and formatting (via tox)
  • tox for testing across multiple Python versions

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

License

MIT License - see LICENSE file for details.

This is a Python port of the original Perl MARC::Lint module.

Acknowledgments

  • Bryan Baldus, Ed Summers, and Dan Lester for the original Perl MARC::Lint module (2001-2011)
  • Library of Congress for MARC21 standards and documentation

Links

Metadata

Release files for marc-lint 0.0.6

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

Source distribution (sdist)

Source distribution for marc-lint 0.0.6
File Size Uploaded
marc_lint-0.0.6.tar.gz 41.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for marc-lint 0.0.6
File Interpreter ABI Platform
marc_lint-0.0.6-py3-none-any.whl Python 3 none any Details

Total release size: 84.1 kB

Release files / marc_lint-0.0.6.tar.gz

Download URL marc_lint-0.0.6.tar.gz
Size 41.3 kB
Tags Source
SHA-256 checksum
How to use checksums
698837783467748bd3001fe39283977a4faae890243f236cd52910c1061ac009
BLAKE2b-256 checksum
How to use checksums
c6aa6e0ce1d5af58e703ac73ffd8710c073792f63020b800550ae672ae5fa618
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 6, 2026.

Transparency log

Release files / marc_lint-0.0.6-py3-none-any.whl

Download URL marc_lint-0.0.6-py3-none-any.whl
Size 42.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d085662a9c95ed0c99f371a6e50490f2ce3ca7c3042316675330314d52346e64
BLAKE2b-256 checksum
How to use checksums
8fb4a5f3fa7b85c8bcbd275f947ee9403c3c13bb8d8917fb120e55aeba5da061
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.6 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

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