From 2f4821328fb2262b2bd87d05f286ffc8b6b7afeb Mon Sep 17 00:00:00 2001 From: Ed Summers Date: Thu, 5 Jun 2025 12:19:21 -0400 Subject: [PATCH] Use pyproject and remove setup.py This commit includes changes to remove the use of setup.py and setuptools in favor of using the existing pyrproject.toml and hatch. A source layout was chosen, which required several changes such as: - pushing bagit.py into src/bagit/ - pushing locale into src/bagit/locale The gettext related code in setup.py was relocated to utils/locales.py It's possible the way that the gettext functionality is setup needs to adjustment, as I'm new to using it. Also we now rely on running tests with uv and pytest, which happens in the test github action. We could chose to update the PyPI publishing step to use uv build/publish too? --- .github/workflows/test.yml | 13 ++-- .gitignore | 2 + MANIFEST.in | 7 --- Makefile | 8 +-- README.rst | 10 +-- pyproject.toml | 39 +++++++++--- setup.py | 61 ------------------- bagit.py => src/bagit/__init__.py | 7 +-- {locale => src/bagit/locale}/bagit-python.pot | 20 ++++-- {locale => src/bagit/locale}/bagit.pot | 0 .../locale}/en/LC_MESSAGES/bagit-python.po | 20 ++++-- test.py | 2 +- bench.py => utils/bench.py | 0 utils/locales.py | 25 ++++++++ 14 files changed, 106 insertions(+), 108 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 setup.py rename bagit.py => src/bagit/__init__.py (99%) rename {locale => src/bagit/locale}/bagit-python.pot (96%) rename {locale => src/bagit/locale}/bagit.pot (100%) rename {locale => src/bagit/locale}/en/LC_MESSAGES/bagit-python.po (96%) rename bench.py => utils/bench.py (100%) create mode 100755 utils/locales.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b37ba86..03b1d44 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,17 +20,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - pip install coverage - pip install --editable . - - name: Run test - run: python -m unittest discover + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Run tests + run: uv run pytest diff --git a/.gitignore b/.gitignore index 1f4b930..333e320 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist MANIFEST bagit.egg-info .idea +uv.lock +*.log diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index db7a199..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -prune test-data -exclude .* -exclude Dockerfile -exclude MANIFEST.in -exclude test.py -exclude bench.py -recursive-include locale *.po *.mo diff --git a/Makefile b/Makefile index 9907ac7..17b0319 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,13 @@ COMPILED_MESSAGES=$(patsubst %.po,%.mo, $(wildcard locale/*/LC_MESSAGES/bagit-py all: messages compile clean: - rm -f locale/*/LC_MESSAGES/*.mo + rm -f src/bagit/locale/*/LC_MESSAGES/*.mo messages: - xgettext --language=python -d bagit-python --no-location -o locale/bagit-python.pot bagit.py + xgettext --language=python -d bagit-python --no-location -o src/bagit/locale/bagit-python.pot src/bagit/__init__.py # Until http://savannah.gnu.org/bugs/?20923 is fixed: - sed -i '' -e 's/CHARSET/UTF-8/g' locale/bagit-python.pot - msgmerge --no-fuzzy-matching --lang=en --output-file=locale/en/LC_MESSAGES/bagit-python.po locale/en/LC_MESSAGES/bagit-python.po locale/bagit-python.pot + sed -i '' -e 's/CHARSET/UTF-8/g' src/bagit/locale/bagit-python.pot + msgmerge --no-fuzzy-matching --lang=en --output-file=src/bagit/locale/en/LC_MESSAGES/bagit-python.po src/bagit/locale/en/LC_MESSAGES/bagit-python.po src/bagit/locale/bagit-python.pot %.mo: %.po msgfmt -o $@ $< diff --git a/README.rst b/README.rst index d7804e4..f4fa486 100644 --- a/README.rst +++ b/README.rst @@ -219,23 +219,23 @@ Contributing to bagit-python development % git clone git://github.com/LibraryOfCongress/bagit-python.git % cd bagit-python # MAKE CHANGES - % python test.py + % uv run pytest Running the tests ~~~~~~~~~~~~~~~~~ -You can quickly run the tests using the built-in unittest framework: +You can quickly run the tests using `uv `_. :: - python -m unittest discover + uv run pytest If you have Docker installed, you can run the tests under Linux inside a container: :: - % docker build -t bagit:latest . && docker run -it bagit:latest + docker build -t bagit:latest . && docker run -it bagit:latest Benchmarks ---------- @@ -246,7 +246,7 @@ bench utility: :: - % ./bench.py + ./utils/bench.py License ------- diff --git a/pyproject.toml b/pyproject.toml index 7cd2d39..937984a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools>=64", "setuptools-scm>=8"] -build-backend = "setuptools.build_meta" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" [project] name = "bagit" @@ -8,7 +8,8 @@ dynamic = ["version"] description = "Create and validate BagIt packages" readme = {file = "README.rst", content-type = "text/x-rst"} authors = [ - { name = "Ed Summers", email = "ehs@pobox.com" }, + { name = "Chris Adams", email = "chris@improbable.org" }, + { name = "Ed Summers", email = "ehs@pobox.com" } ] classifiers = [ "Intended Audience :: Developers", @@ -18,6 +19,10 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Filesystems", ] +requires-python = ">= 3.9" + +[project.scripts] +"bagit" = "bagit:main" [project.urls] Homepage = "https://libraryofcongress.github.io/bagit-python/" @@ -27,14 +32,34 @@ Homepage = "https://libraryofcongress.github.io/bagit-python/" [tool.ruff] target-version = "py38" - -[tool.setuptools_scm] - [tool.isort] line_length = 110 default_section = "THIRDPARTY" known_first_party = "bagit" +[tool.pytest.ini_options] +testpaths = ["test.py"] +addopts = "-v" + [tool.coverage.run] branch = true -include = "bagit.py" +include = "src" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.targets.sdist] +packages = ["src/bagit"] +include = [ + "src/bagit/*.py", + "src/bagit/locale/*.po", + "src/bagit/locale/*.mo", +] + + +[dependency-groups] +dev = [ + "coverage>=7.8.2", + "pytest>=8.4.0", + "ruff>=0.11.12", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index 15c91b4..0000000 --- a/setup.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -from __future__ import absolute_import, print_function - -import glob -import os -import subprocess -import sys - -from setuptools import setup - -description = "Create and validate BagIt packages" - - -def get_message_catalogs(): - message_catalogs = [] - - for po_file in glob.glob("locale/*/LC_MESSAGES/bagit-python.po"): - mo_file = po_file.replace(".po", ".mo") - - if not os.path.exists(mo_file) or os.path.getmtime(mo_file) < os.path.getmtime( - po_file - ): - try: - subprocess.check_call(["msgfmt", "-o", mo_file, po_file]) - except (OSError, subprocess.CalledProcessError) as exc: - print( - "Translation catalog %s could not be compiled (is gettext installed?) " - " — translations will not be available for this language: %s" - % (po_file, exc), - file=sys.stderr, - ) - continue - - message_catalogs.append((os.path.dirname(mo_file), (mo_file,))) - - return message_catalogs - - -setup( - name="bagit", - use_scm_version=True, - url="https://libraryofcongress.github.io/bagit-python/", - author="Ed Summers", - author_email="ehs@pobox.com", - py_modules=["bagit"], - scripts=["bagit.py"], - data_files=get_message_catalogs(), - description=description, - platforms=["POSIX"], - setup_requires=["setuptools_scm"], - classifiers=[ - "License :: Public Domain", - "Intended Audience :: Developers", - "Topic :: Communications :: File Sharing", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: System :: Filesystems", - "Programming Language :: Python :: 3", - ], -) diff --git a/bagit.py b/src/bagit/__init__.py similarity index 99% rename from bagit.py rename to src/bagit/__init__.py index c9c1737..ce24059 100755 --- a/bagit.py +++ b/src/bagit/__init__.py @@ -240,7 +240,7 @@ def make_bag( ) LOGGER.info(_("Creating bagit.txt")) - txt = """BagIt-Version: 0.97\nTag-File-Character-Encoding: UTF-8\n""" + txt = """BagIt-Version: 1.0\nTag-File-Character-Encoding: UTF-8\n""" with open_text_file("bagit.txt", "w") as bagit_file: bagit_file.write(txt) @@ -1479,10 +1479,7 @@ def _make_parser(): checksum_args = parser.add_argument_group( _("Checksum Algorithms"), - _( - "Select the manifest algorithms to be used when creating bags" - " (default=%s)" - ) + _("Select the manifest algorithms to be used when creating bags (default=%s)") % ", ".join(DEFAULT_CHECKSUMS), ) diff --git a/locale/bagit-python.pot b/src/bagit/locale/bagit-python.pot similarity index 96% rename from locale/bagit-python.pot rename to src/bagit/locale/bagit-python.pot index 3543c31..85bf167 100644 --- a/locale/bagit-python.pot +++ b/src/bagit/locale/bagit-python.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-26 10:28-0400\n" +"POT-Creation-Date: 2025-06-05 12:14-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -226,20 +226,23 @@ msgid "" "%(found_byte_count)d bytes" msgstr "" -msgid "Bag validation failed" +msgid "Bag is incomplete" msgstr "" #, python-format msgid "Unable to calculate file hashes for %s" msgstr "" +msgid "Bag validation failed" +msgstr "" + msgid "bagit.txt must not contain a byte-order mark" msgstr "" #, python-format msgid "" -"%(path)s %(algorithm)s validation failed: expected=\"%(expected)s\" found=" -"\"%(found)s\"" +"%(path)s %(algorithm)s validation failed: expected=\"%(expected)s\" " +"found=\"%(found)s\"" msgstr "" #, python-format @@ -345,16 +348,23 @@ msgstr "" msgid "bagit-python version %s" msgstr "" -msgid "The number of processes must be 0 or greater" +msgid "The number of processes must be greater than 0" msgstr "" msgid "--fast is only allowed as an option for --validate!" msgstr "" +msgid "--completeness-only is only allowed as an option for --validate!" +msgstr "" + #, python-format msgid "%s valid according to Payload-Oxum" msgstr "" +#, python-format +msgid "%s is complete and valid according to Payload-Oxum" +msgstr "" + #, python-format msgid "%s is valid" msgstr "" diff --git a/locale/bagit.pot b/src/bagit/locale/bagit.pot similarity index 100% rename from locale/bagit.pot rename to src/bagit/locale/bagit.pot diff --git a/locale/en/LC_MESSAGES/bagit-python.po b/src/bagit/locale/en/LC_MESSAGES/bagit-python.po similarity index 96% rename from locale/en/LC_MESSAGES/bagit-python.po rename to src/bagit/locale/en/LC_MESSAGES/bagit-python.po index ddf0f3f..8767e56 100644 --- a/locale/en/LC_MESSAGES/bagit-python.po +++ b/src/bagit/locale/en/LC_MESSAGES/bagit-python.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-26 10:28-0400\n" +"POT-Creation-Date: 2025-06-05 12:14-0400\n" "PO-Revision-Date: 2017-04-27 15:02-0400\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -233,20 +233,23 @@ msgid "" "%(found_byte_count)d bytes" msgstr "" -msgid "Bag validation failed" +msgid "Bag is incomplete" msgstr "" #, python-format msgid "Unable to calculate file hashes for %s" msgstr "Unable to calculate file hashes for %s" +msgid "Bag validation failed" +msgstr "" + msgid "bagit.txt must not contain a byte-order mark" msgstr "" #, python-format msgid "" -"%(path)s %(algorithm)s validation failed: expected=\"%(expected)s\" found=" -"\"%(found)s\"" +"%(path)s %(algorithm)s validation failed: expected=\"%(expected)s\" " +"found=\"%(found)s\"" msgstr "" #, python-format @@ -352,16 +355,23 @@ msgstr "" msgid "bagit-python version %s" msgstr "" -msgid "The number of processes must be 0 or greater" +msgid "The number of processes must be greater than 0" msgstr "" msgid "--fast is only allowed as an option for --validate!" msgstr "" +msgid "--completeness-only is only allowed as an option for --validate!" +msgstr "" + #, python-format msgid "%s valid according to Payload-Oxum" msgstr "%s valid according to Payload-Oxum" +#, python-format +msgid "%s is complete and valid according to Payload-Oxum" +msgstr "" + #, python-format msgid "%s is valid" msgstr "%s is valid" diff --git a/test.py b/test.py index 735bd73..c18784d 100644 --- a/test.py +++ b/test.py @@ -523,7 +523,7 @@ def test_make_bag(self): tagmanifest_txt = slurp_text_file( j(self.tmpdir, "tagmanifest-md5.txt") ).splitlines() - self.assertIn("9e5ad981e0d29adc278f6a294b8c2aca bagit.txt", tagmanifest_txt) + self.assertIn("eaa2c609ff6371712f623f5531945b44 bagit.txt", tagmanifest_txt) self.assertIn( "a0ce6631a2a6d1a88e6d38453ccc72a5 manifest-md5.txt", tagmanifest_txt ) diff --git a/bench.py b/utils/bench.py similarity index 100% rename from bench.py rename to utils/bench.py diff --git a/utils/locales.py b/utils/locales.py new file mode 100755 index 0000000..aaed193 --- /dev/null +++ b/utils/locales.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import sys +import subprocess + +from pathlib import Path + +src_dir = Path(__file__).parent.parent / "src" + +for po_file in src_dir.glob("bagit/locale/*/LC_MESSAGES/bagit-python.po"): + mo_file = po_file.with_suffix(".mo") + + if not mo_file.is_file() or mo_file.stat().st_mtime < po_file.stat().st_mtime: + try: + print(f"compiling {po_file} to {mo_file}") + subprocess.check_call(["msgfmt", "-o", mo_file, po_file]) + except (OSError, subprocess.CalledProcessError) as exc: + print( + "Translation catalog %s could not be compiled (is gettext installed?) " + " — translations will not be available for this language: %s" + % (po_file, exc), + file=sys.stderr, + ) + continue