Skip to content

v0.7.5

Choose a tag to compare

@github-actions github-actions released this 06 Apr 00:57
· 350 commits to main since this release

Added

  • Record.get(tag) for pymarc compatibility: Dict-like record.get('245') returns the first matching field or a default value, mirroring pymarc's Record.get(). Delegates to existing get_field().
  • Field.is_control_field() and ControlField.is_control_field() for pymarc compatibility: Returns False on data fields and True on control fields, matching pymarc's unified Field.is_control_field() API.
  • Record.__str__ and Record.__repr__: The Python Record wrapper now delegates to the Rust implementation instead of showing the default <mrrc.Record object at 0x...>. str(rec) returns Record(type=a) and repr(rec) returns <Record type=a fields=N>.
  • SubfieldPatternQuery negation flag (#63, #73): SubfieldPatternQuery("020", "a", r"^978-", negate=True) pushes pattern inversion into Rust so non-matching fields never cross the FFI boundary. Thanks to @acdha@code4lib.social for suggesting this.
  • SubfieldValueQuery negation flag (#64, #74): SubfieldValueQuery("650", "a", "History", negate=True) pushes value inversion into Rust. Works with both exact and partial matching.
  • Comprehensive pymarc API compatibility (#71, #72): Full drop-in replacement for pymarc. Thanks to @mistersql@mastodon.social for highlighting several of these compatibility gaps. Changes include:
    • Record accessors as @property: All 17 record accessors (title, author, isbn, issn, subjects, location, notes, publisher, uniform_title, sudoc, issn_title, issnl, pubyear, series, physical_description, plus aliases physicaldescription, uniformtitle, addedentries) are now properties, matching pymarc's record.title syntax.
    • Unified ControlField into Field: Field('001', data='12345') creates a control field. ControlField remains as a backward-compatible subclass. Control field content accessed via .data attribute.
    • Record['xxx'] raises KeyError for missing tags (use record.get(tag) for safe access), matching pymarc behavior.
    • Record.as_marc() / as_marc21(): Returns ISO 2709 bytes.
    • Record.as_json() / as_dict(): pymarc-compatible MARC-in-JSON serialization.
    • Field.value(): Space-joined subfield values.
    • Field.format_field(): Human-readable text representation.
    • Field.as_marc() / as_marc21(): Field-level binary serialization.
    • Field.add_subfield(code, value, pos=N): Positional insert support.
    • Field.linkage_occurrence_num(): Extract $6 linkage info.
    • Field.convert_legacy_subfields(): Classmethod for old flat-list format.
    • add_field(*fields): Accepts multiple fields at once.
    • remove_field(*fields): Accepts multiple fields, returns None.
    • remove_fields(*tags): Bulk removal by tag.
    • add_ordered_field(*fields): Tag-sorted insert.
    • add_grouped_field(*fields): Insert after same-tag group.
    • parse_xml_to_array(), parse_json_to_array(), map_records(): Module-level convenience functions.
    • MARC constants: LEADER_LEN, DIRECTORY_ENTRY_LEN, END_OF_FIELD, END_OF_RECORD, SUBFIELD_INDICATOR, MARC_XML_NS, MARC_XML_SCHEMA.
    • Exception hierarchy: MrrcException base class with RecordLengthInvalid, RecordLeaderInvalid, BaseAddressInvalid, BaseAddressNotFound, RecordDirectoryInvalid, EndOfRecordNotFound, FieldNotFound, FatalReaderError, and BadSubfieldCodeWarning.
    • pubyear returns str (not int), matching pymarc.

Breaking Changes

  • Record accessors are now @property: record.title()record.title. Affects all 17 accessors: title, author, isbn, issn, issn_title, issnl, subjects, notes, location, series, sudoc, publisher, pubyear, physical_description, uniform_title, plus new physicaldescription, uniformtitle, addedentries.
  • pubyear returns str: Previously returned Optional[int], now returns Optional[str] to match pymarc.
  • Record['xxx'] raises KeyError for missing tags: Previously returned None. Use record.get(tag) for safe access that returns None.
  • ControlField unified into Field: ControlField is now a subclass of Field. Control field content is in .data (not .value). Field('001', data='12345') is the preferred constructor.
  • remove_field() returns None: Previously returned a list of removed fields.
  • add_field() / remove_field() accept *args: Signature changed from single field to *fields. Existing single-arg calls still work.
  • Default Field indicators changed from '0' to ' ': Matches pymarc. Only affects fields created without explicit indicators.

Changed

  • SubfieldPatternQuery and SubfieldValueQuery marked #[non_exhaustive] (#74): Prevents external struct literal construction, requiring use of constructors. Future field additions will not be semver-breaking.

Dependencies

  • Bump pygments from 2.19.2 to 2.20.0 (#67)
  • Bump ruff from 0.15.4 to 0.15.8
  • Bump actions/deploy-pages from 4 to 5
  • Bump codecov/codecov-action from 5 to 6

Fixed

  • mrrc.__version__ now reports the correct version: Previously hardcoded as "0.1.0" in both mrrc/__init__.py and src-python/src/lib.rs. Now derived from Cargo.toml at compile time via env!("CARGO_PKG_VERSION"), eliminating the need to manually update version strings during releases.

Documentation

  • Migration guide updated: Added record.get() and field.is_control_field() examples to docs/guides/migration-from-pymarc.md, reflecting closer pymarc API parity.
  • All docs and examples updated for pymarc API compatibility: Record accessors now shown as properties (record.title not record.title()), control field access uses .data, missing-field access patterns use record.get() or try/except, and new methods/constants/exceptions are documented throughout the API reference, migration guide, quickstart, tutorials, and runnable examples.