eadpy 0.1.4__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eadpy-0.1.4 → eadpy-0.2.0}/.gitignore +2 -2
- eadpy-0.2.0/CHANGELOG.md +79 -0
- eadpy-0.2.0/PKG-INFO +357 -0
- eadpy-0.1.4/PKG-INFO → eadpy-0.2.0/README.md +80 -19
- eadpy-0.2.0/pyproject.toml +63 -0
- eadpy-0.2.0/src/eadpy/__init__.py +97 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/src/eadpy/cli.py +25 -7
- {eadpy-0.1.4 → eadpy-0.2.0}/src/eadpy/ead.py +140 -406
- eadpy-0.2.0/src/eadpy/parsers.py +1386 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/tests/sample.xml +2 -2
- eadpy-0.2.0/tests/sample_ead3.xml +278 -0
- eadpy-0.2.0/tests/sample_ead4.xml +304 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/tests/test_cli.py +70 -1
- eadpy-0.2.0/tests/test_completeness.py +307 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/tests/test_ead.py +79 -1
- eadpy-0.2.0/tests/test_ead3.py +269 -0
- eadpy-0.2.0/tests/test_ead4.py +217 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/tests/test_ead_counts.py +10 -4
- {eadpy-0.1.4 → eadpy-0.2.0}/tests/test_parsing.py +196 -13
- eadpy-0.1.4/.python-version +0 -1
- eadpy-0.1.4/README.md +0 -259
- eadpy-0.1.4/pyproject.toml +0 -26
- eadpy-0.1.4/src/eadpy/__init__.py +0 -77
- eadpy-0.1.4/uv.lock +0 -846
- {eadpy-0.1.4 → eadpy-0.2.0}/LICENSE +0 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/pytest.ini +0 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/src/eadpy/exceptions.py +0 -0
- {eadpy-0.1.4 → eadpy-0.2.0}/src/eadpy/py.typed +0 -0
eadpy-0.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
While the version is below 1.0.0, breaking changes may appear in minor releases.
|
|
8
|
+
|
|
9
|
+
## 0.2.0 - Unreleased
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
- Content marked `audience="internal"` is now **excluded** from parsed output.
|
|
14
|
+
ArchivesSpace exports unpublished records this way when "include unpublished"
|
|
15
|
+
is selected, and repositories use it for internal-use `<dao>` links and
|
|
16
|
+
processing notes, so this content previously appeared in JSON chunks and CSV
|
|
17
|
+
rows. Pass `include_internal=True` to any constructor, or `--include-internal`
|
|
18
|
+
on the command line, to restore the previous behavior. `<eadheader>` and
|
|
19
|
+
`<control>` are exempt from the filter: marking the finding aid's own header
|
|
20
|
+
`audience="internal"` is a common convention, and discarding it would take the
|
|
21
|
+
record identifier with it.
|
|
22
|
+
- An `<ead>` root with an unrecognized namespace now raises `EadParseError`
|
|
23
|
+
instead of being parsed as EAD 2002. `EadParseError` subclasses `ValueError`,
|
|
24
|
+
so existing `except ValueError` handlers still catch it.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- EAD3 (1.1.x) support, detected from the `http://ead3.archivists.org/schema/`
|
|
29
|
+
namespace. EAD3-specific constructs are folded into the existing output shape,
|
|
30
|
+
so downstream consumers need no changes: `<unitdatestructured>` and
|
|
31
|
+
`<physdescstructured>` are read alongside their legacy equivalents
|
|
32
|
+
(ArchivesSpace exports emit both), `<part>` children of name and subject
|
|
33
|
+
elements are joined, `<daoset>` groups are flattened into the digital objects
|
|
34
|
+
list, and `<didnote>` is reported under the same key as EAD 2002 `<note>`.
|
|
35
|
+
- Experimental EAD 4.0 support for the draft
|
|
36
|
+
`https://standards.openpreservation.org/ead/v4` schema. The schema is
|
|
37
|
+
unreleased and element mappings may change. Two known limitations: EAD 4.0 has
|
|
38
|
+
no repository element, so `repository` is always `None`, and it has no `<dao>`
|
|
39
|
+
equivalent, so `<reference href="...">` links in a component's own description
|
|
40
|
+
are reported as its digital objects.
|
|
41
|
+
- `EAD.ead_version` property reporting the detected version as `"2002"`,
|
|
42
|
+
`"ead3"`, or `"ead4"`. Documents with no namespace fall back to structural
|
|
43
|
+
detection from the root's child elements.
|
|
44
|
+
- `include_internal` (keyword-only) on `from_path`, `from_string`, `from_bytes`
|
|
45
|
+
and `from_file`, and `--include-internal` on both CLI subcommands.
|
|
46
|
+
- A `DeprecationWarning` when the ignored `encoding` parameter is passed to
|
|
47
|
+
`from_string`. It was previously accepted and silently discarded, which also
|
|
48
|
+
meant `from_string(xml, True)` — a caller meaning `include_internal` — did
|
|
49
|
+
nothing at all.
|
|
50
|
+
- Package metadata: license expression, classifiers, and project URLs.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- Whitespace inside element-only content is now collapsed. Finding aids that
|
|
55
|
+
hard-wrap prose leaked raw newlines and tabs into `repository`,
|
|
56
|
+
`extent`/`physdesc`, and note blocks such as `<bioghist>` and
|
|
57
|
+
`<scopecontent>`, which in turn produced multi-line CSV cells.
|
|
58
|
+
- Source distributions no longer include local tooling configuration.
|
|
59
|
+
|
|
60
|
+
## 0.1.4
|
|
61
|
+
|
|
62
|
+
- Dependency updates and minor bug fixes.
|
|
63
|
+
|
|
64
|
+
## 0.1.3
|
|
65
|
+
|
|
66
|
+
- Refactored `EAD` class methods into the public `eadpy` package interface.
|
|
67
|
+
- Added `from_path`, `from_string`, `from_bytes` and `from_file` constructors.
|
|
68
|
+
|
|
69
|
+
## 0.1.2
|
|
70
|
+
|
|
71
|
+
- Added batch directory processing to the command-line interface.
|
|
72
|
+
|
|
73
|
+
## 0.1.1
|
|
74
|
+
|
|
75
|
+
- Added CSV export.
|
|
76
|
+
|
|
77
|
+
## 0.1.0
|
|
78
|
+
|
|
79
|
+
- Initial release: EAD 2002 parsing and JSON chunk export.
|
eadpy-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eadpy
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A Python library for working with Encoded Archival Description (EAD) XML files
|
|
5
|
+
Project-URL: Homepage, https://github.com/nulib-labs/eadpy
|
|
6
|
+
Project-URL: Repository, https://github.com/nulib-labs/eadpy
|
|
7
|
+
Project-URL: Issues, https://github.com/nulib-labs/eadpy/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/nulib-labs/eadpy/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Brendan Quinn <brendan-quinn@northwestern.edu>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: archives,archivesspace,ead,ead3,finding-aid,xml
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Text Processing :: Markup :: XML
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: lxml>=6.1.1
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# EADPy
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
|
|
33
|
+
A Python library for working with Encoded Archival Description (EAD) XML documents.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Parse and manipulate EAD 2002 and EAD3 XML documents — the version is auto-detected
|
|
38
|
+
- Experimental support for the draft EAD 4.0 schema (unreleased; mappings may change)
|
|
39
|
+
- Excludes staff-only content marked `audience="internal"` by default
|
|
40
|
+
- Convert EAD to various formats (JSON, CSV)
|
|
41
|
+
- Tools for batch processing of EAD files
|
|
42
|
+
|
|
43
|
+
## EAD Version Support
|
|
44
|
+
|
|
45
|
+
EADPy detects the EAD version from the document's schema namespace (with a
|
|
46
|
+
structural fallback for documents without a namespace) and exposes it as
|
|
47
|
+
`ead.ead_version`:
|
|
48
|
+
|
|
49
|
+
| Version | Namespace | `ead_version` |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| EAD 2002 | `urn:isbn:1-931666-22-9` | `"2002"` |
|
|
52
|
+
| EAD3 (1.1.x) | `http://ead3.archivists.org/schema/` | `"ead3"` |
|
|
53
|
+
| EAD 4.0 (draft) | `https://standards.openpreservation.org/ead/v4` | `"ead4"` |
|
|
54
|
+
|
|
55
|
+
All versions produce the same normalized data structure, JSON chunks, and CSV
|
|
56
|
+
columns, so downstream consumers don't need to care which version they were
|
|
57
|
+
given. Version-specific constructs are folded into the common shape: EAD3
|
|
58
|
+
`<unitdatestructured>` and `<physdescstructured>` are read alongside their
|
|
59
|
+
legacy equivalents (ArchivesSpace exports emit both), `<part>` children of
|
|
60
|
+
name and subject elements are joined, and `<daoset>` groups are flattened
|
|
61
|
+
into the digital objects list.
|
|
62
|
+
|
|
63
|
+
EAD 4.0 support is **experimental** — the schema has not been released and
|
|
64
|
+
element mappings may change. Two limitations worth knowing: EAD 4.0 has no
|
|
65
|
+
repository element, so `repository` is always `None`, and it has no `<dao>`
|
|
66
|
+
equivalent, so `<reference href="...">` links in a component's own
|
|
67
|
+
description are reported as its digital objects.
|
|
68
|
+
|
|
69
|
+
A `<ead>` root with an unrecognized namespace raises `EadParseError`
|
|
70
|
+
(before v0.2.0 it was parsed as EAD 2002).
|
|
71
|
+
|
|
72
|
+
## Internal (Unpublished) Content
|
|
73
|
+
|
|
74
|
+
Description marked `audience="internal"` is staff-only: ArchivesSpace exports
|
|
75
|
+
unpublished records this way when "include unpublished" is selected, and
|
|
76
|
+
repositories use it for internal-use `<dao>` links and processing notes.
|
|
77
|
+
EADPy **excludes** that content by default, so JSON chunks and CSV rows are
|
|
78
|
+
safe to index or display:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
ead = eadpy.from_path("finding_aid.xml") # internal content skipped
|
|
82
|
+
ead = eadpy.from_path("finding_aid.xml", include_internal=True) # full archivist view
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
eadpy file finding_aid.xml -o output.json --include-internal
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`include_internal` is a keyword-only argument on all four constructors. The
|
|
90
|
+
filter applies to
|
|
91
|
+
any element carrying the attribute — components, notes, digital objects,
|
|
92
|
+
access terms and inline markup — with one exception: `<eadheader>` and
|
|
93
|
+
`<control>` are never dropped, since marking the finding aid's own header
|
|
94
|
+
`audience="internal"` is a common convention and discarding it would take the
|
|
95
|
+
record identifier with it. If a document's entire `<archdesc>` is marked
|
|
96
|
+
internal, parsing emits a `UserWarning` rather than silently returning an
|
|
97
|
+
empty record.
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
Install EADPy using `pip`:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install eadpy
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Install using `uv`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uv tool install eadpy
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
EADPy requires Python 3.11 or higher.
|
|
114
|
+
|
|
115
|
+
## Command-line Usage
|
|
116
|
+
|
|
117
|
+
The following command will process an EAD XML file and export it to JSON format:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
eadpy file path/to/finding_aid.xml -o output.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
To export to CSV format instead:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
eadpy file path/to/finding_aid.xml -o output.csv -f csv
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For batch processing of multiple EAD XML files in a directory:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
eadpy dir path/to/ead_directory -o path/to/output_directory
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
To process subdirectories recursively:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
eadpy dir path/to/ead_directory -r -o path/to/output_directory
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use the verbose flag for detailed information during processing:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
eadpy file path/to/finding_aid.xml -v
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Run the following to view all available options:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
eadpy --help
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Python Usage
|
|
154
|
+
|
|
155
|
+
EADPy provides multiple ways to create an `EAD` instance depending on your source data:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import eadpy
|
|
159
|
+
|
|
160
|
+
# Load an EAD file from a file path
|
|
161
|
+
ead = eadpy.from_path("path/to/finding_aid.xml")
|
|
162
|
+
|
|
163
|
+
# Create an EAD instance from an XML string
|
|
164
|
+
xml_string = """<?xml version="1.0" encoding="UTF-8"?>
|
|
165
|
+
<ead xmlns="urn:isbn:1-931666-22-9">
|
|
166
|
+
<!-- EAD content here -->
|
|
167
|
+
</ead>"""
|
|
168
|
+
ead = eadpy.from_string(xml_string)
|
|
169
|
+
|
|
170
|
+
# Create an EAD instance from bytes
|
|
171
|
+
from eadpy import from_bytes
|
|
172
|
+
with open("path/to/finding_aid.xml", "rb") as f:
|
|
173
|
+
xml_bytes = f.read()
|
|
174
|
+
ead = from_bytes(xml_bytes)
|
|
175
|
+
|
|
176
|
+
# Create an EAD instance from a file-like object
|
|
177
|
+
from eadpy import from_file
|
|
178
|
+
with open("path/to/finding_aid.xml", "r") as f:
|
|
179
|
+
ead = from_file(f)
|
|
180
|
+
|
|
181
|
+
# Also works with StringIO or BytesIO objects
|
|
182
|
+
from io import StringIO, BytesIO
|
|
183
|
+
from eadpy import from_file, from_string, from_bytes
|
|
184
|
+
|
|
185
|
+
string_io = StringIO(xml_string)
|
|
186
|
+
ead = from_file(string_io)
|
|
187
|
+
|
|
188
|
+
bytes_io = BytesIO(xml_bytes)
|
|
189
|
+
ead = from_file(bytes_io)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Class-Based API Style
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from eadpy import EAD
|
|
196
|
+
|
|
197
|
+
# Load an EAD file from a file path
|
|
198
|
+
ead = EAD.from_path("path/to/finding_aid.xml")
|
|
199
|
+
|
|
200
|
+
# Create an EAD instance from an XML string
|
|
201
|
+
ead = EAD.from_string(xml_string)
|
|
202
|
+
|
|
203
|
+
# Create an EAD instance from bytes
|
|
204
|
+
with open("path/to/finding_aid.xml", "rb") as f:
|
|
205
|
+
xml_bytes = f.read()
|
|
206
|
+
ead = EAD.from_bytes(xml_bytes)
|
|
207
|
+
|
|
208
|
+
# Create an EAD instance from a file-like object
|
|
209
|
+
with open("path/to/finding_aid.xml", "r") as f:
|
|
210
|
+
ead = EAD.from_file(f)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Export to JSON chunks
|
|
214
|
+
|
|
215
|
+
JSON chunks are useful for embedding or display in applications:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Create chunks and save them to a file
|
|
219
|
+
chunks = ead.create_and_save_chunks("output.json")
|
|
220
|
+
|
|
221
|
+
# Or create chunks without saving
|
|
222
|
+
chunks = ead.create_item_chunks()
|
|
223
|
+
|
|
224
|
+
# Then save them separately if needed
|
|
225
|
+
ead.save_chunks_to_json(chunks, "output.json")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Export to CSV
|
|
229
|
+
|
|
230
|
+
CSV export is useful for tabular analysis:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
# Create CSV data and save it to a file
|
|
234
|
+
csv_data = ead.create_and_save_csv("output.csv")
|
|
235
|
+
|
|
236
|
+
# Or create CSV data without saving
|
|
237
|
+
csv_data = ead.create_csv_data()
|
|
238
|
+
|
|
239
|
+
# Then save it separately if needed
|
|
240
|
+
ead.save_csv_data(csv_data, "output.csv")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## API Reference
|
|
244
|
+
|
|
245
|
+
### Package Level Functions
|
|
246
|
+
|
|
247
|
+
- **`from_path(file_path: str, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable. Set `include_internal=True` to keep `audience="internal"` content.
|
|
248
|
+
|
|
249
|
+
- **`from_string(xml_string: str, encoding: str = None, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from an XML string. Any XML encoding declaration is ignored (the string is already decoded); the `encoding` parameter is deprecated and raises a `DeprecationWarning` if passed.
|
|
250
|
+
|
|
251
|
+
- **`from_bytes(xml_bytes: bytes, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from XML bytes. Useful when working with binary data from HTTP responses or other sources.
|
|
252
|
+
|
|
253
|
+
- **`from_file(file_like_object, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file-like object with a `read()` method. Works with both text-based (StringIO) and binary (BytesIO) file objects.
|
|
254
|
+
|
|
255
|
+
### Class Methods (Object Creation)
|
|
256
|
+
|
|
257
|
+
- **`EAD.from_path(file_path: str, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable. Set `include_internal=True` to keep `audience="internal"` content.
|
|
258
|
+
|
|
259
|
+
- **`EAD.from_string(xml_string: str, encoding: str = None, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from an XML string. Any XML encoding declaration is ignored (the string is already decoded); the `encoding` parameter is deprecated and raises a `DeprecationWarning` if passed.
|
|
260
|
+
|
|
261
|
+
- **`EAD.from_bytes(xml_bytes: bytes, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from XML bytes. Useful when working with binary data from HTTP responses or other sources.
|
|
262
|
+
|
|
263
|
+
- **`EAD.from_file(file_like_object, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file-like object with a `read()` method. Works with both text-based (StringIO) and binary (BytesIO) file objects.
|
|
264
|
+
|
|
265
|
+
### Instance Properties
|
|
266
|
+
|
|
267
|
+
- **`ead_version -> str`**: The detected EAD version of the parsed document: `"2002"`, `"ead3"`, or `"ead4"`.
|
|
268
|
+
|
|
269
|
+
### Instance Methods (Data Export)
|
|
270
|
+
|
|
271
|
+
- **`create_item_chunks() -> list`**: Creates item-focused chunks that include relevant information from their parent hierarchy. Returns a list of dictionaries, each containing a text representation and metadata for each item.
|
|
272
|
+
|
|
273
|
+
- **`save_chunks_to_json(chunks: list, output_file: str) -> None`**: Saves chunks to a JSON file. Takes a list of chunks and an output file path.
|
|
274
|
+
|
|
275
|
+
- **`create_and_save_chunks(output_file: str) -> list`**: Creates item-focused chunks and saves them to a JSON file. Returns the chunks that were created and saved.
|
|
276
|
+
|
|
277
|
+
- **`create_csv_data() -> list`**: Creates a flattened hierarchy representation suitable for CSV export. Returns a list of dictionaries, each representing a row in the CSV.
|
|
278
|
+
|
|
279
|
+
- **`save_csv_data(csv_data: list, output_file: str) -> None`**: Saves CSV data to a file. Takes a list of dictionaries and an output file path.
|
|
280
|
+
|
|
281
|
+
- **`create_and_save_csv(output_file: str) -> list`**: Creates flattened CSV data and saves it to a file. Returns the CSV data that was created and saved.
|
|
282
|
+
|
|
283
|
+
## Command-line Reference
|
|
284
|
+
|
|
285
|
+
### Global options
|
|
286
|
+
|
|
287
|
+
- `--version`: Show the version number and exit
|
|
288
|
+
- `--help`: Show help message and exit
|
|
289
|
+
|
|
290
|
+
### File command options
|
|
291
|
+
|
|
292
|
+
- `input`: Path to the EAD XML file (required)
|
|
293
|
+
- `-o, --output`: Path to the output file
|
|
294
|
+
- `-f, --format`: Output format ('json' or 'csv')
|
|
295
|
+
- `-v, --verbose`: Print detailed information
|
|
296
|
+
- `--include-internal`: Keep content marked `audience="internal"` (excluded by default)
|
|
297
|
+
|
|
298
|
+
### Directory command options
|
|
299
|
+
|
|
300
|
+
- `input_dir`: Path to the directory containing EAD XML files (required)
|
|
301
|
+
- `-o, --output-dir`: Directory for output files
|
|
302
|
+
- `-f, --format`: Output format ('json' or 'csv', default: 'json')
|
|
303
|
+
- `-r, --recursive`: Process subdirectories recursively
|
|
304
|
+
- `-v, --verbose`: Print detailed information
|
|
305
|
+
- `--include-internal`: Keep content marked `audience="internal"` (excluded by default)
|
|
306
|
+
|
|
307
|
+
## Development
|
|
308
|
+
|
|
309
|
+
### Setting up the development environment
|
|
310
|
+
|
|
311
|
+
EADPy uses [uv](https://github.com/astral-sh/uv) for dependency management and virtual environment setup.
|
|
312
|
+
|
|
313
|
+
1. Clone the repository:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
git clone https://github.com/nulib-labs/eadpy
|
|
317
|
+
cd eadpy
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
2. Create a virtual environment and install the project with its dev dependencies:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
uv sync
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
3. Activate the virtual environment (optional — `uv run` works without it):
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
source .venv/bin/activate # On Unix/macOS
|
|
330
|
+
# or
|
|
331
|
+
.venv\Scripts\activate # On Windows
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Running tests
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
uv run pytest
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Changelog
|
|
341
|
+
|
|
342
|
+
See [CHANGELOG.md](CHANGELOG.md). Note that 0.2.0 changes two default
|
|
343
|
+
behaviors: `audience="internal"` content is now excluded, and an `<ead>` root
|
|
344
|
+
with an unrecognized namespace raises `EadParseError` instead of being parsed
|
|
345
|
+
as EAD 2002.
|
|
346
|
+
|
|
347
|
+
## Contributing
|
|
348
|
+
|
|
349
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
350
|
+
|
|
351
|
+
## Acknowledgements
|
|
352
|
+
|
|
353
|
+
Special thanks to the [ArcLight](https://github.com/projectblacklight/arclight) project, which inspired the EAD processing approach taken here. Thank you to the developers and contributors of ArcLight for their work in the archival community!
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: eadpy
|
|
3
|
-
Version: 0.1.4
|
|
4
|
-
Summary: A Python library for working with Encoded Archival Description (EAD) XML files
|
|
5
|
-
Author-email: Brendan Quinn <brendan-quinn@northwestern.edu>
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.11
|
|
8
|
-
Requires-Dist: lxml>=6.1.1
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
|
|
11
1
|
# EADPy
|
|
12
2
|
|
|
13
3
|

|
|
@@ -17,10 +7,68 @@ A Python library for working with Encoded Archival Description (EAD) XML documen
|
|
|
17
7
|
|
|
18
8
|
## Features
|
|
19
9
|
|
|
20
|
-
- Parse and manipulate EAD 2002 XML documents
|
|
10
|
+
- Parse and manipulate EAD 2002 and EAD3 XML documents — the version is auto-detected
|
|
11
|
+
- Experimental support for the draft EAD 4.0 schema (unreleased; mappings may change)
|
|
12
|
+
- Excludes staff-only content marked `audience="internal"` by default
|
|
21
13
|
- Convert EAD to various formats (JSON, CSV)
|
|
22
14
|
- Tools for batch processing of EAD files
|
|
23
15
|
|
|
16
|
+
## EAD Version Support
|
|
17
|
+
|
|
18
|
+
EADPy detects the EAD version from the document's schema namespace (with a
|
|
19
|
+
structural fallback for documents without a namespace) and exposes it as
|
|
20
|
+
`ead.ead_version`:
|
|
21
|
+
|
|
22
|
+
| Version | Namespace | `ead_version` |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| EAD 2002 | `urn:isbn:1-931666-22-9` | `"2002"` |
|
|
25
|
+
| EAD3 (1.1.x) | `http://ead3.archivists.org/schema/` | `"ead3"` |
|
|
26
|
+
| EAD 4.0 (draft) | `https://standards.openpreservation.org/ead/v4` | `"ead4"` |
|
|
27
|
+
|
|
28
|
+
All versions produce the same normalized data structure, JSON chunks, and CSV
|
|
29
|
+
columns, so downstream consumers don't need to care which version they were
|
|
30
|
+
given. Version-specific constructs are folded into the common shape: EAD3
|
|
31
|
+
`<unitdatestructured>` and `<physdescstructured>` are read alongside their
|
|
32
|
+
legacy equivalents (ArchivesSpace exports emit both), `<part>` children of
|
|
33
|
+
name and subject elements are joined, and `<daoset>` groups are flattened
|
|
34
|
+
into the digital objects list.
|
|
35
|
+
|
|
36
|
+
EAD 4.0 support is **experimental** — the schema has not been released and
|
|
37
|
+
element mappings may change. Two limitations worth knowing: EAD 4.0 has no
|
|
38
|
+
repository element, so `repository` is always `None`, and it has no `<dao>`
|
|
39
|
+
equivalent, so `<reference href="...">` links in a component's own
|
|
40
|
+
description are reported as its digital objects.
|
|
41
|
+
|
|
42
|
+
A `<ead>` root with an unrecognized namespace raises `EadParseError`
|
|
43
|
+
(before v0.2.0 it was parsed as EAD 2002).
|
|
44
|
+
|
|
45
|
+
## Internal (Unpublished) Content
|
|
46
|
+
|
|
47
|
+
Description marked `audience="internal"` is staff-only: ArchivesSpace exports
|
|
48
|
+
unpublished records this way when "include unpublished" is selected, and
|
|
49
|
+
repositories use it for internal-use `<dao>` links and processing notes.
|
|
50
|
+
EADPy **excludes** that content by default, so JSON chunks and CSV rows are
|
|
51
|
+
safe to index or display:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
ead = eadpy.from_path("finding_aid.xml") # internal content skipped
|
|
55
|
+
ead = eadpy.from_path("finding_aid.xml", include_internal=True) # full archivist view
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
eadpy file finding_aid.xml -o output.json --include-internal
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`include_internal` is a keyword-only argument on all four constructors. The
|
|
63
|
+
filter applies to
|
|
64
|
+
any element carrying the attribute — components, notes, digital objects,
|
|
65
|
+
access terms and inline markup — with one exception: `<eadheader>` and
|
|
66
|
+
`<control>` are never dropped, since marking the finding aid's own header
|
|
67
|
+
`audience="internal"` is a common convention and discarding it would take the
|
|
68
|
+
record identifier with it. If a document's entire `<archdesc>` is marked
|
|
69
|
+
internal, parsing emits a `UserWarning` rather than silently returning an
|
|
70
|
+
empty record.
|
|
71
|
+
|
|
24
72
|
## Installation
|
|
25
73
|
|
|
26
74
|
Install EADPy using `pip`:
|
|
@@ -169,23 +217,27 @@ ead.save_csv_data(csv_data, "output.csv")
|
|
|
169
217
|
|
|
170
218
|
### Package Level Functions
|
|
171
219
|
|
|
172
|
-
- **`from_path(file_path: str) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable.
|
|
220
|
+
- **`from_path(file_path: str, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable. Set `include_internal=True` to keep `audience="internal"` content.
|
|
173
221
|
|
|
174
|
-
- **`from_string(xml_string: str, encoding: str =
|
|
222
|
+
- **`from_string(xml_string: str, encoding: str = None, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from an XML string. Any XML encoding declaration is ignored (the string is already decoded); the `encoding` parameter is deprecated and raises a `DeprecationWarning` if passed.
|
|
175
223
|
|
|
176
|
-
- **`from_bytes(xml_bytes: bytes) -> EAD`**: Creates an EAD instance from XML bytes. Useful when working with binary data from HTTP responses or other sources.
|
|
224
|
+
- **`from_bytes(xml_bytes: bytes, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from XML bytes. Useful when working with binary data from HTTP responses or other sources.
|
|
177
225
|
|
|
178
|
-
- **`from_file(file_like_object) -> EAD`**: Creates an EAD instance from a file-like object with a `read()` method. Works with both text-based (StringIO) and binary (BytesIO) file objects.
|
|
226
|
+
- **`from_file(file_like_object, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file-like object with a `read()` method. Works with both text-based (StringIO) and binary (BytesIO) file objects.
|
|
179
227
|
|
|
180
228
|
### Class Methods (Object Creation)
|
|
181
229
|
|
|
182
|
-
- **`EAD.from_path(file_path: str) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable.
|
|
230
|
+
- **`EAD.from_path(file_path: str, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file path. Validates that the file exists, is not a directory, and is readable. Set `include_internal=True` to keep `audience="internal"` content.
|
|
231
|
+
|
|
232
|
+
- **`EAD.from_string(xml_string: str, encoding: str = None, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from an XML string. Any XML encoding declaration is ignored (the string is already decoded); the `encoding` parameter is deprecated and raises a `DeprecationWarning` if passed.
|
|
233
|
+
|
|
234
|
+
- **`EAD.from_bytes(xml_bytes: bytes, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from XML bytes. Useful when working with binary data from HTTP responses or other sources.
|
|
183
235
|
|
|
184
|
-
- **`EAD.
|
|
236
|
+
- **`EAD.from_file(file_like_object, *, include_internal: bool = False) -> EAD`**: Creates an EAD instance from a file-like object with a `read()` method. Works with both text-based (StringIO) and binary (BytesIO) file objects.
|
|
185
237
|
|
|
186
|
-
|
|
238
|
+
### Instance Properties
|
|
187
239
|
|
|
188
|
-
- **`
|
|
240
|
+
- **`ead_version -> str`**: The detected EAD version of the parsed document: `"2002"`, `"ead3"`, or `"ead4"`.
|
|
189
241
|
|
|
190
242
|
### Instance Methods (Data Export)
|
|
191
243
|
|
|
@@ -214,6 +266,7 @@ ead.save_csv_data(csv_data, "output.csv")
|
|
|
214
266
|
- `-o, --output`: Path to the output file
|
|
215
267
|
- `-f, --format`: Output format ('json' or 'csv')
|
|
216
268
|
- `-v, --verbose`: Print detailed information
|
|
269
|
+
- `--include-internal`: Keep content marked `audience="internal"` (excluded by default)
|
|
217
270
|
|
|
218
271
|
### Directory command options
|
|
219
272
|
|
|
@@ -222,6 +275,7 @@ ead.save_csv_data(csv_data, "output.csv")
|
|
|
222
275
|
- `-f, --format`: Output format ('json' or 'csv', default: 'json')
|
|
223
276
|
- `-r, --recursive`: Process subdirectories recursively
|
|
224
277
|
- `-v, --verbose`: Print detailed information
|
|
278
|
+
- `--include-internal`: Keep content marked `audience="internal"` (excluded by default)
|
|
225
279
|
|
|
226
280
|
## Development
|
|
227
281
|
|
|
@@ -256,6 +310,13 @@ source .venv/bin/activate # On Unix/macOS
|
|
|
256
310
|
uv run pytest
|
|
257
311
|
```
|
|
258
312
|
|
|
313
|
+
## Changelog
|
|
314
|
+
|
|
315
|
+
See [CHANGELOG.md](CHANGELOG.md). Note that 0.2.0 changes two default
|
|
316
|
+
behaviors: `audience="internal"` content is now excluded, and an `<ead>` root
|
|
317
|
+
with an unrecognized namespace raises `EadParseError` instead of being parsed
|
|
318
|
+
as EAD 2002.
|
|
319
|
+
|
|
259
320
|
## Contributing
|
|
260
321
|
|
|
261
322
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "eadpy"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A Python library for working with Encoded Archival Description (EAD) XML files"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Brendan Quinn", email = "brendan-quinn@northwestern.edu" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
keywords = ["ead", "archives", "finding-aid", "xml", "archivesspace", "ead3"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Intended Audience :: Education",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Topic :: Text Processing :: Markup :: XML",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.11"
|
|
26
|
+
dependencies = [
|
|
27
|
+
"lxml>=6.1.1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/nulib-labs/eadpy"
|
|
32
|
+
Repository = "https://github.com/nulib-labs/eadpy"
|
|
33
|
+
Issues = "https://github.com/nulib-labs/eadpy/issues"
|
|
34
|
+
Changelog = "https://github.com/nulib-labs/eadpy/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=9.1.1",
|
|
43
|
+
"pytest-cov>=7.1.0",
|
|
44
|
+
"twine>=6.2.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
eadpy = "eadpy.cli:main"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
# Everything needed to build and test from source, and nothing else: the
|
|
52
|
+
# working tree also holds local tooling config and a scratch corpus of real
|
|
53
|
+
# finding aids that have no place in a published artifact.
|
|
54
|
+
include = [
|
|
55
|
+
"src", "tests", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml",
|
|
56
|
+
"pytest.ini",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.coverage.run]
|
|
60
|
+
source = ["eadpy"]
|
|
61
|
+
|
|
62
|
+
[tool.coverage.report]
|
|
63
|
+
exclude_also = ["if __name__ == .__main__.:"]
|