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.
@@ -12,5 +12,5 @@ wheels/
12
12
  .vscode/
13
13
  *.DS_Store
14
14
 
15
- .coverage
16
- htmlcov/
15
+ .coverage*
16
+ htmlcov/
@@ -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
+ ![PyPI - Version](https://img.shields.io/pypi/v/eadpy)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
  ![PyPI - Version](https://img.shields.io/pypi/v/eadpy)
@@ -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 (EAD3 is not yet supported)
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 = 'utf-8') -> 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 unused.
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.from_string(xml_string: str, encoding: str = 'utf-8') -> 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 unused.
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
- - **`EAD.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.
238
+ ### Instance Properties
187
239
 
188
- - **`EAD.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.
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__.:"]