Skip to content

Commit 6b35b30

Browse files
committed
Merge branch 'main' into chore/10-gh-actions
2 parents f2b7672 + a2850a1 commit 6b35b30

10 files changed

Lines changed: 221 additions & 101 deletions

File tree

CHANGELOG.rst

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
Change & Version Information
22
============================
33

4-
The following is a summary of changes and improvements to
5-
:mod:`neuxml`. New features in each version should be listed, with
6-
any necessary information about installation or upgrade notes.
7-
8-
0.1.0
4+
1.0.0
95
-----
106

11-
* Fork `eulxml <https://github.com/emory-libraries/eulxml>`_
12-
package from version `1.1.3` with the new name `neuxml`
7+
Initial release of `neuxml.` This is a hard fork of `eulxml <https://github.com/emory-libraries/eulxml>`_
8+
version 1.1.3, which drops Django forms integration and support for older versions of Python.
9+
10+
* Rename `eulxml` to `neuxml` throughout
1311
* Remove `forms` submodule and drop Django requirements
14-
* Add GitHub workflow for pypi publication
12+
* Add GitHub Actions workflow for PyPI publication
1513
* Update for Python 3.12 compatibility
14+
* Store a default XML catalog and schemas in codebase and released package
15+
* Migrate build environment to Hatchling with `pypackage.toml`
16+
* Migrate test environment from Nose to Pytest
17+
18+
For a record of the pre-existing functionality, refer to the `eulxml changelog <https://github.com/emory-libraries/eulxml/blob/master/CHANGELOG.rst>`_.

DEVNOTES.rst

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -59,40 +59,3 @@ in that module, and store the schemas and catalog file in the subdirectory
5959

6060
To specify other remote schema URLs and catalog locations, use the provided
6161
keyword arguments ``xsd_schemas``, ``xmlcatalog_dir``, and ``xmlcatalog_file``.
62-
63-
Migration from ``eulxml``
64-
-------------------------
65-
66-
A convenience script has been included under ``scripts/`` called
67-
``migrate_eulxml.py`` to migrate your project from ``eulxml`` to ``neuxml``,
68-
which will replace any usage of the package name in all ``.py`` files in the
69-
passed directory and subdirectories. After upgrading to Python 3.12+ and
70-
updating the package in your project's dependencies, you can run the script::
71-
72-
python scripts/migrate_eulxml.py /path/to/your/project
73-
74-
In addition to renaming the package from ``eulxml`` to ``neuxml``, the usage
75-
of indirect imports from ``neuxml.xmlmap`` has been removed, and definitions
76-
must be imported directly from its submodules instead. The migration script
77-
will also attempt to make these changes automatically.
78-
79-
For example, imports have changed from this style:
80-
81-
.. code-block:: python
82-
83-
from neuxml import xmlmap
84-
85-
xmlmap.XmlObject
86-
xmlmap.Field
87-
88-
to this style:
89-
90-
.. code-block:: python
91-
92-
from neuxml.xmlmap import core, fields
93-
94-
core.XmlObject
95-
fields.Field
96-
97-
Class imports are also acceptable (e.g. ``from neuxml.xmlmap.core import
98-
XmlObject``).
File renamed without changes.

MIGRATION.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
Migration from ``eulxml``
2+
-------------------------
3+
4+
A convenience script called ``migrate-eulxml`` has been included in order
5+
to migrate your project from ``eulxml`` to ``neuxml``, which will replace
6+
any usage of the package name in all ``.py`` files in the passed directory
7+
and subdirectories. After upgrading to Python 3.12+ and installing the
8+
updated package, you can run the script::
9+
10+
migrate-eulxml /path/to/your/project
11+
12+
Or you can run the script directly without installing the package::
13+
14+
python neuxml/utils/migrate_eulxml.py /path/to/your/project
15+
16+
In addition to renaming the package from ``eulxml`` to ``neuxml``, the usage
17+
of indirect imports from ``neuxml.xmlmap`` has been removed, and definitions
18+
must be imported directly from its submodules instead. The migration script
19+
will also attempt to make these changes automatically, but may not work in
20+
all cases, so be sure to check your imports manually as well.
21+
22+
For example, imports have changed from this style:
23+
24+
.. code-block:: python
25+
26+
from neuxml import xmlmap
27+
28+
xmlmap.XmlObject
29+
xmlmap.Field
30+
31+
to this style:
32+
33+
.. code-block:: python
34+
35+
from neuxml.xmlmap import core, fields
36+
37+
core.XmlObject
38+
fields.Field
39+
40+
Class and function imports are also acceptable (e.g. ``from neuxml.xmlmap.core
41+
import XmlObject``).
42+
43+
----
44+
45+
If you would like to automatically replace all instances of ``eulxml`` with
46+
``neuxml`` but otherwise complete the import migration manually, you may use
47+
the following one-line shell script.
48+
49+
On MacOS:
50+
51+
.. code-block:: shell
52+
53+
find . -name '*.py' -print0 | xargs -0 sed -i '' -e 's/eulxml/neuxml/g'
54+
55+
Or on other Unix-based operating systems:
56+
57+
.. code-block:: shell
58+
59+
find . -name '*.py' -print0 | xargs -0 sed -i 's/eulxml/neuxml/g'

README.rst

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,71 @@
22
neuxml
33
======
44

5-
**package**
6-
.. image:: https://img.shields.io/pypi/v/neuxml.svg
7-
:target: https://pypi.python.org/pypi/neuxml
8-
:alt: PyPI
5+
.. image:: https://img.shields.io/pypi/v/neuxml.svg
6+
:target: https://pypi.python.org/pypi/neuxml
7+
:alt: PyPI
98

10-
.. image:: https://img.shields.io/github/license/Princeton-CDH/neuxml.svg
11-
:alt: License
9+
.. image:: https://img.shields.io/github/license/Princeton-CDH/neuxml.svg
10+
:alt: License
1211

13-
.. image:: https://img.shields.io/pypi/dm/neuxml.svg
14-
:alt: PyPI downloads
12+
.. image:: https://img.shields.io/pypi/pyversions/neuxml
13+
:alt: PyPI - Python Version
1514

16-
neuxml is a `Python <http://www.python.org/>`_ module that provides
17-
utilities and classes for interacting with XML that allow the
18-
definition of re-usable XML objects that can be accessed, updated and
19-
created as standard Python types.
15+
.. image:: https://img.shields.io/pypi/dm/neuxml.svg
16+
:alt: PyPI downloads
2017

21-
**neuxml.xpath** provides functions and classes for parsing XPath
22-
expressions using `PLY <http://www.dabeaz.com/ply/>`_.
18+
`neuxml` is a Python library that provides utilities and classes for
19+
object-oriented access to XML. `neuxml` makes it possible to define reusable
20+
python classes to access, update, and create XML content as standard Python types.
2321

24-
**neuxml.xmlmap** makes it easier to map XML to Python objects in a
25-
nicer, more pythonic and object-oriented way than typical DOM access
26-
usually provides. XML can be read, modified, and even created from
27-
scratch (in cases where the configured XPath is simple enough to
28-
determine the nodes that should be constructed).
22+
**neuxml.xmlmap** makes it possible to map XML content to Python objects in a
23+
pythonic and object-oriented way, which is easier to use than typical DOM access.
24+
With the `neuxml.xmlmap.core.XmlObject` class, XML can be read, modified, and even
25+
created from scratch in some cases, as long as the configured XPath can
26+
be used to construct new nodes.
2927

30-
Dependencies
31-
============
32-
33-
**neuxml** depends on `PLY <http://www.dabeaz.com/ply/>`_ and `lxml
34-
<http://lxml.de/>`_.
28+
Object-oriented access depends on **neuxml.xpath**, which provides functions and
29+
classes for parsing XPath expressions using `PLY <http://www.dabeaz.com/ply/>`_.
3530

31+
Installation
32+
============
3633

37-
Contact Information
38-
===================
34+
We recommend using pip to install the officially released versions from PyPI:
3935

40-
**eulxml** was created by the `Center for Digital Humanities at Princeton <https://cdh.princeton.edu/>`_.
36+
```console
37+
pip install neuxml
38+
```
4139

42-
cdhdevteam@princeton.edu
40+
It is also possible to install directly from GitHub. Use a branch or tag name,
41+
e.g. `@develop` or `@1.0` to install a specific tagged version or branch.
4342

43+
```console
44+
pip install git+https://github.com/Princeton-CDH/neuxml.git@develop#egg=neuxml
45+
```
4446

4547
License
4648
=======
49+
4750
**neuxml** is distributed under the Apache 2.0 License.
4851

4952

5053
Development History
5154
===================
5255

53-
This codebase was forked from a package called **eulxml**, originally developed
54-
by Emory University Libraries. To see and interact with the full development
55-
history of **eulxml**, see `eulxml <https://github.com/emory-libraries/eulxml>`_.
56+
`neuxml` is a hard fork of `eulxml <https://github.com/emory-libraries/eulxml>`_,
57+
which was originally developed by Emory University Libraries from 2011-2016.
58+
`neuxml` has been updated for compatibility with current versions of Python
59+
and drops the support for Django form integration. The full development history
60+
for the `eulxml` package is available at the original repository: https://github.com/emory-libraries/eulxml
61+
62+
63+
Technical documentation
64+
=======================
5665

66+
For instructions on developer setup, unit testing, XML catalog file management,
67+
and migrating from `eulxml`, refer to ``DEVNOTES.rst``.
5768

58-
Developer instructions
59-
======================
69+
Migration from ``eulxml``
70+
=========================
6071

61-
For development instructions and notes, see ``DEVNOTES.rst``.
72+
If migrating from a previous ``eulxml`` installation, see ``MIGRATION.rst``.

neuxml/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from importlib import resources
2020
from contextlib import ExitStack
2121

22-
__version__ = "0.1.0"
22+
__version__ = "1.0.0"
2323

2424
# Paths for XML catalog file & directory
2525

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,31 +81,34 @@ def fix_xmlmap(code):
8181
return code
8282

8383

84-
"""Handle command line args"""
85-
86-
parser = argparse.ArgumentParser()
87-
parser.add_argument(
88-
"project_path",
89-
type=pathlib.Path,
90-
help="The path to the root of a project using eulxml",
91-
)
92-
93-
args = parser.parse_args()
94-
95-
9684
def is_valid(path):
9785
"""consider hidden files/folders and venv site-packages invalid for migration"""
9886
return not any(
9987
(part for part in path.parts if part.startswith(".") or "site-packages" in part)
10088
)
10189

10290

103-
# migrate every valid python file in project path and subdirectories
104-
for filepath in args.project_path.glob("**/*.py"):
105-
if is_valid(filepath):
106-
try:
107-
code = filepath.read_text(encoding="utf-8")
108-
migrated_code = fix_xmlmap(re.sub("eulxml", "neuxml", code))
109-
filepath.write_text(migrated_code, encoding="utf-8")
110-
except Exception as e:
111-
print(f"Failed to process {filepath}: {e}")
91+
def main(arg_list=None):
92+
"""Handle command line arguments and run migration functions"""
93+
parser = argparse.ArgumentParser()
94+
parser.add_argument(
95+
"project_path",
96+
type=pathlib.Path,
97+
help="The path to the root of a project using eulxml",
98+
)
99+
100+
args = parser.parse_args(arg_list)
101+
102+
# migrate every valid python file in project path and subdirectories
103+
for filepath in args.project_path.glob("**/*.py"):
104+
if is_valid(filepath):
105+
try:
106+
code = filepath.read_text(encoding="utf-8")
107+
migrated_code = fix_xmlmap(re.sub("eulxml", "neuxml", code))
108+
filepath.write_text(migrated_code, encoding="utf-8")
109+
except Exception as e:
110+
print(f"Failed to process {filepath}: {e}")
111+
112+
113+
if __name__ == "__main__":
114+
main()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ dev = [
3636
"pre-commit",
3737
]
3838

39+
[project.scripts]
40+
migrate-eulxml = "neuxml.utils.migrate_eulxml:main"
41+
3942
[project.urls]
4043
Repository = "https://github.com/Princeton-CDH/neuxml"
4144
Changelog = "https://github.com/Princeton-CDH/neuxml/blob/main/CHANGELOG.rst"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from eulxml import xmlmap
2+
from eulxml.xmlmap import teimap
3+
from eulxml.xmlmap import (
4+
IntegerField, NodeField, NodeListField,
5+
SimpleBooleanField, StringField, XmlObject,
6+
load_xmlobject_from_string, mods
7+
)
8+
9+
class TeiElement(teimap.Tei):
10+
folio_start = xmlmap.StringField("@from")
11+
folio_end = xmlmap.StringField("@to")
12+
13+
class CustomObject(xmlmap.XmlObject):
14+
xml = xmlmap.load_xmlobject_from_file("file")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from io import StringIO
2+
import pathlib
3+
import re
4+
import sys
5+
from unittest.mock import patch
6+
7+
from neuxml.utils.migrate_eulxml import is_valid, main
8+
9+
10+
class TestMigrateEulxml:
11+
def test_is_valid(self):
12+
# hidden files/folders, site-packages should not be valid
13+
bad_path = pathlib.Path("/project/.git/example.py")
14+
assert not is_valid(bad_path)
15+
bad_path = pathlib.Path(
16+
"/project/venv/lib/python3.12/site-packages/pytest/__main__.py"
17+
)
18+
assert not is_valid(bad_path)
19+
good_path = pathlib.Path("/project/example.py")
20+
assert is_valid(good_path)
21+
22+
def test_main(self):
23+
"""integration tests for the migration script, which
24+
should hit every case and thus every helper function"""
25+
mock_args = ["/fake/path"]
26+
mock_file = "test/test_utils/fixtures/old_import.txt"
27+
with patch.object(pathlib.Path, "glob") as mock_glob:
28+
mock_glob.return_value = [pathlib.Path(mock_file)]
29+
with patch.object(pathlib.Path, "write_text") as mock_write_text:
30+
main(mock_args)
31+
output_text = mock_write_text.call_args[0][0]
32+
33+
# should replace all eulxml with neuxml
34+
assert "eulxml" not in output_text
35+
assert "neuxml" in output_text
36+
37+
# should replace `import xmlmap` with submodule imports
38+
assert "import xmlmap" not in output_text
39+
assert "from neuxml.xmlmap import core, fields" in output_text
40+
41+
# should categorize imports correctly: core, fields, module
42+
assert re.search(
43+
r"from neuxml\.xmlmap\.core import.*XmlObject", output_text
44+
)
45+
assert re.search(
46+
r"from neuxml\.xmlmap\.fields import.*NodeField", output_text
47+
)
48+
assert "from neuxml.xmlmap import mods" in output_text
49+
50+
# should replace inline references appropriately
51+
assert "xmlmap.StringField" not in output_text
52+
assert "fields.StringField" in output_text
53+
assert "xmlmap.XmlObject" not in output_text
54+
assert "core.XmlObject" in output_text
55+
assert "xmlmap.load_" not in output_text
56+
assert "core.load_" in output_text
57+
58+
# should print exceptions with file path
59+
mock_write_text.side_effect = Exception("msg")
60+
with patch.object(sys, "stdout", new_callable=StringIO) as mock_stdout:
61+
main(mock_args)
62+
assert (
63+
f"Failed to process {mock_file}: msg" in mock_stdout.getvalue()
64+
)

0 commit comments

Comments
 (0)