# Python API Reference
Complete Python API reference for MRRC.
!!! tip "Canonical Type Stubs"
The file `mrrc/_mrrc.pyi` is the ground-truth type reference for the
Python extension module. IDEs use it for autocompletion and type checking.
If this page and the stub file disagree, the stub file is authoritative.
## Core Classes
### Record
A MARC bibliographic record containing a leader, control fields, and data fields.
```python
from mrrc import Record, Field, Leader, Subfield
# Create a record with inline fields
record = Record(fields=[
Field("245", indicators=["1", "0"], subfields=[
Subfield("a", "Title"),
]),
])
# Or build incrementally
record = Record(Leader())
record.add_control_field("001", "123456789")
field = Field("245", "1", "0")
field.add_subfield("a", "Title")
record.add_field(field)
```
**Properties (read-only):**
All record accessors are properties (not methods), matching pymarc:
| `leader` | `Leader` | The record's leader |
| `title` | `str \| None` | Title from 245$a |
| `author` | `str \| None` | Author from 100/110/111 |
| `isbn` | `str \| None` | ISBN from 020$a |
| `issn` | `str \| None` | ISSN from 022$a |
| `publisher` | `str \| None` | Publisher from 260$b |
| `pubyear` | `str \| None` | Publication year (returns str) |
| `subjects` | `list[str]` | All subjects from 6XX$a |
| `location` | `str \| None` | Location from 852$a |
| `notes` | `list[str]` | All notes from 5XX |
| `series` | `str \| None` | Series from 490$a |
| `uniform_title` | `str \| None` | Uniform title from 130$a |
| `physical_description` | `str \| None` | Extent from 300$a |
| `sudoc` | `str \| None` | SuDoc classification from 086$a |
| `issn_title` | `str \| None` | ISSN title from 222$a |
| `issnl` | `str \| None` | ISSN-L from 024$a |
| `addedentries` | `list[Field]` | Added entries (7XX fields) |
| `physicaldescription` | `str \| None` | Alias for `physical_description` |
| `uniformtitle` | `str \| None` | Alias for `uniform_title` |
**Methods:**
| `add_control_field(tag, value)` | `None` | Add a control field (001-009) |
| `control_field(tag)` | `str \| None` | Get a control field value |
| `control_fields()` | `list[tuple[str, str]]` | Get all control fields |
| `add_field(*fields)` | `None` | Add one or more data fields |
| `add_ordered_field(*fields)` | `None` | Add fields in tag-sorted order |
| `add_grouped_field(*fields)` | `None` | Add fields after same tag group |
| `remove_field(*fields)` | `None` | Remove specific field objects |
| `remove_fields(*tags)` | `None` | Remove all fields matching tags |
| `fields()` | `list[Field]` | Get all data fields |
| `fields_by_tag(tag)` | `list[Field]` | Get fields matching a tag |
| `get(tag, default=None)` | `Field \| None` | Get first field (safe, returns None) |
| `get_fields(*tags)` | `list[Field]` | Get fields for multiple tags |
| `isbns()` | `list[str]` | Get all ISBNs |
| `authors()` | `list[str]` | Get all authors |
| `as_marc()` | `bytes` | Serialize to ISO 2709 binary |
| `as_marc21()` | `bytes` | Alias for `as_marc()` |
| `as_json(**kwargs)` | `str` | Serialize to pymarc-compatible MARC-in-JSON |
| `as_dict()` | `dict` | Convert to pymarc-compatible dict |
**Dictionary access:**
```python
# record['xxx'] raises KeyError if tag is missing
field = record['245'] # Returns Field or raises KeyError
# Use record.get() for safe access (returns None if missing)
field = record.get('245') # Returns Field or None
```
### Field
A MARC field — both control fields and data fields use this class.
```python
from mrrc import Field, Subfield
# Create a data field with indicators and subfields inline
field = Field("245", indicators=["1", "0"], subfields=[
Subfield("a", "Main title :"),
Subfield("b", "subtitle /"),
Subfield("c", "by Author."),
])
# Create a control field
field = Field("001", data="12345")
# Or build incrementally
field = Field("245", "1", "0")
field.add_subfield("a", "Main title :")
# Access subfields
print(field["a"]) # "Main title :"
for sf in field.subfields():
print(f"${sf.code} {sf.value}")
```
**Properties and Methods:**
| `tag` | `str` | 3-character field tag |
| `indicator1` | `str` | First indicator |
| `indicator2` | `str` | Second indicator |
| `data` | `str \| None` | Control field content (None for data fields) |
| `is_control_field()` | `bool` | True for control fields (001-009) |
| `add_subfield(code, value, pos=None)` | `None` | Add a subfield (optional positional insert) |
| `subfields()` | `list[Subfield]` | Get all subfields |
| `subfields_by_code(code)` | `list[str]` | Get values for a subfield code |
| `get_subfields(*codes)` | `list[str]` | Get values for one or more subfield codes |
| `value()` | `str` | Space-joined subfield values |
| `format_field()` | `str` | Human-readable text representation |
| `as_marc()` | `bytes` | Serialize to ISO 2709 binary |
| `as_marc21()` | `bytes` | Alias for `as_marc()` |
| `linkage_occurrence_num()` | `tuple[str, str] \| None` | Extract $6 linkage info |
| `convert_legacy_subfields(tag, *args)` | `Field` | Classmethod: create from flat list |
| `__getitem__(code)` | `str \| None` | Get first subfield value by code |
### ControlField
A backward-compatible subclass of `Field` for control fields. Prefer `Field(tag, data=value)` for new code.
```python
from mrrc import ControlField
# Still works for backward compatibility
cf = ControlField("001", "12345")
print(cf.data) # "12345"
print(cf.is_control_field()) # True
print(isinstance(cf, Field)) # True
```
### Subfield
A subfield within a MARC field.
```python
from mrrc import Subfield
sf = Subfield("a", "value")
print(sf.code) # "a"
print(sf.value) # "value"
```
**Properties:**
| `code` | `str` | Single-character subfield code |
| `value` | `str` | Subfield value (read/write) |
### Leader
The 24-byte MARC record header containing metadata.
```python
from mrrc import Leader
leader = Leader()
leader.record_type = "a" # Language material
leader.bibliographic_level = "m" # Monograph
leader.character_coding = "a" # UTF-8
```
**Properties:**
| `record_length` | 00-04 | Record length (5 digits) |
| `record_status` | 05 | Record status (n/c/d) |
| `record_type` | 06 | Type of record (a=language, c=music, etc.) |
| `bibliographic_level` | 07 | Bibliographic level (m=monograph, s=serial) |
| `control_record_type` | 08 | Type of control |
| `character_coding` | 09 | Character coding (space=MARC-8, a=UTF-8) |
| `indicator_count` | 10 | Indicator count (usually 2) |
| `subfield_code_count` | 11 | Subfield code count (usually 2) |
| `data_base_address` | 12-16 | Base address of data |
| `encoding_level` | 17 | Encoding level |
| `cataloging_form` | 18 | Descriptive cataloging form |
| `multipart_level` | 19 | Multipart resource record level |
| `reserved` | 20-23 | Entry map (usually "4500") |
## Reader/Writer Classes
### MARCReader
Reads MARC records from files with GIL-released I/O for parallelism.
```python
MARCReader(file_obj, to_unicode=True, permissive=False, recovery_mode="strict")
```
```python
from mrrc import MARCReader
# From file path (recommended for performance)
for record in MARCReader("records.mrc"):
print(record.title)
# From file object
with open("records.mrc", "rb") as f:
for record in MARCReader(f):
print(record.title)
# From bytes
data = open("records.mrc", "rb").read()
for record in MARCReader(data):
print(record.title)
```
**Input Types:**
| `str` or `Path` | File path (pure Rust I/O, best performance) |
| `bytes` or `bytearray` | In-memory data |
| File object | Python file-like object |
**Keyword Arguments:**
| `to_unicode` | `bool` | `True` | Accepted for pymarc compatibility. mrrc always converts MARC-8 to UTF-8; passing `False` emits a warning but has no effect. |
| `permissive` | `bool` | `False` | When `True`, yields `None` for records that fail to parse instead of raising, matching pymarc's permissive behavior. |
| `recovery_mode` | `str` | `"strict"` | Controls how malformed records are handled (see below). Cannot be combined with `permissive=True`. |
**Recovery Modes:**
Instead of skipping bad records entirely (like `permissive=True`), `recovery_mode` attempts to salvage valid fields from damaged records:
| `"strict"` | Raise on any malformation (default). |
| `"lenient"` | Attempt to recover, salvage valid fields from damaged records. |
| `"permissive"` | Very lenient — accept partial data even from severely malformed records. |
```python
# Skip bad records (pymarc-compatible)
for record in MARCReader("bad.mrc", permissive=True):
if record is None:
continue
print(record.title)
# Salvage partial data from malformed records
for record in MARCReader("bad.mrc", recovery_mode="lenient"):
print(f"Got {len(record.get_fields())} fields")
```
Note: `permissive=True` and `recovery_mode` other than `"strict"` cannot be
combined — they represent different error-handling strategies. Use `permissive=True`
for pymarc-compatible "skip bad records" behavior, or `recovery_mode` for mrrc's
"salvage what you can" approach.
**Thread Safety:**
- NOT thread-safe - each thread needs its own reader
- GIL released during record parsing for parallelism
- Use `ThreadPoolExecutor` with separate readers per thread
### MARCWriter
Writes MARC records to files.
```python
from mrrc import MARCWriter
with MARCWriter("output.mrc") as writer:
writer.write(record)
```
**Methods:**
| `write(record)` | Write a single record |
| `close()` | Close the writer (automatic with context manager) |
## Format Conversion
### Record Methods
```python
# JSON formats
json_str = record.to_json()
marcjson_str = record.to_marcjson()
# pymarc-compatible serialization
json_str = record.as_json() # pymarc MARC-in-JSON format
record_dict = record.as_dict() # pymarc-compatible dict
# MARCXML
xml_str = record.to_xml()
# Other XML-based formats
mods_str = record.to_mods()
dc_str = record.to_dublin_core()
# Binary (ISO 2709)
marc_bytes = record.as_marc() # returns bytes
marc_bytes = record.as_marc21() # alias
```
### Module Functions
```python
import mrrc
# Parse from JSON
record = mrrc.json_to_record(json_str)
# Parse from MARCXML
record = mrrc.xml_to_record(xml_str)
# Parse MARCXML collection (multiple records)
records = mrrc.xml_to_records(collection_xml_str)
# Parse from MODS XML
record = mrrc.mods_to_record(mods_xml)
# Parse MODS collection (multiple records)
records = mrrc.mods_collection_to_records(mods_collection_xml)
# Convert to CSV
csv_str = mrrc.record_to_csv(record)
csv_str = mrrc.records_to_csv(records)
# Convenience functions
records = mrrc.parse_xml_to_array(xml_str)
records = mrrc.parse_json_to_array(json_str)
mrrc.map_records(func, reader)
```
## Constants
```python
from mrrc import (
LEADER_LEN, # 24
DIRECTORY_ENTRY_LEN, # 12
END_OF_FIELD, # '\x1e'
END_OF_RECORD, # '\x1d'
SUBFIELD_INDICATOR, # '\x1f'
MARC_XML_NS, # MARCXML namespace URI
MARC_XML_SCHEMA, # MARCXML schema URI
)
```
## BIBFRAME Conversion
Convert MARC to BIBFRAME 2.0 RDF.
```python
from mrrc import marc_to_bibframe, BibframeConfig
# Basic conversion
config = BibframeConfig()
graph = marc_to_bibframe(record, config)
# With custom base URI
config.set_base_uri("http://library.example.org/")
graph = marc_to_bibframe(record, config)
# Serialize to different formats
turtle = graph.serialize("turtle")
rdfxml = graph.serialize("rdf-xml")
jsonld = graph.serialize("jsonld")
ntriples = graph.serialize("ntriples")
```
### BibframeConfig
| `set_base_uri(uri)` | Set base URI for generated entities |
### BibframeGraph
| `len(graph)` | `int` | Number of triples |
| `serialize(format)` | `str` | Serialize to RDF format |
## Exceptions
```python
from mrrc import MrrcException, MarcError
try:
for record in MARCReader("bad.mrc"):
pass
except MrrcException as e:
print(f"MRRC error: {e}")
except MarcError as e:
print(f"MARC error: {e}")
```
The exception hierarchy:
- `MrrcException` — base exception for all mrrc errors
- `MarcError` — MARC-specific errors (parsing, validation)
## See Also
- [Python Quickstart](../getting-started/quickstart-python.md)
- [Migration from pymarc](../guides/migration-from-pymarc.md)
- [Format Support](formats.md)