From b837527e3113f139984acab5e5c90bed19e4fc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Thu, 6 Jul 2017 02:42:45 +0200 Subject: [PATCH 01/12] PyPI version badge --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index e3848be..4c264d1 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,8 @@ OAIPMH ====== +.. image:: https://img.shields.io/pypi/v/pyoai.svg + :target: https://pypi.python.org/pypi/pyoai .. image:: https://travis-ci.org/mpasternak/pyoai.svg?branch=master :target: https://travis-ci.org/mpasternak/pyoai From 8ea5c3f707754342c9b27a64fef8ba95449fe377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Thu, 6 Jul 2017 02:45:11 +0200 Subject: [PATCH 02/12] Support bumpversion; upload target for Makefile --- Makefile | 25 +++++++++++++++++++++++++ setup.cfg | 12 ++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 Makefile create mode 100644 setup.cfg diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dfaa5f6 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + rm -rf amms_planop2xls/mainwindow_ui.py + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + +release: clean ## package and upload a release + python setup.py sdist upload + python setup.py bdist_wheel upload diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4150913 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[bumpversion] +current_version = 2.5.0 +commit = True +tag = True + +[bdist_wheel] +universal = 1 + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + From 6a770a878de461ca88125e98e20b9e0745115589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Thu, 6 Jul 2017 02:45:12 +0200 Subject: [PATCH 03/12] Update version no --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 21f8842..2b07d99 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='pyoai', - version='2.4.6dev', + version='2.5.0', author='Infrae', author_email='info@infrae.com', url='http://www.infrae.com/download/oaipmh', From b82227e4aa4032f56e92dd5131e496a2880fea5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:43:22 +0200 Subject: [PATCH 04/12] Replace deprecated datetime.utcnow() with timezone-aware equivalent `datetime.datetime.utcnow()` is deprecated since Python 3.12 and scheduled for removal in a future version: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC). `datetime_to_datestamp()` in `oaipmh/datestamp.py` asserts that the input has `tzinfo is None`, so we convert the timezone-aware UTC value back to naive with `.replace(tzinfo=None)` before passing it in. The resulting `responseDate` string in the OAI-PMH response is unchanged. This is the only `datetime.utcnow()` call in the codebase; after this change, pyoai no longer emits `DeprecationWarning` for it on Python 3.12+. --- src/oaipmh/server.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/oaipmh/server.py b/src/oaipmh/server.py index ae843e8..b563240 100644 --- a/src/oaipmh/server.py +++ b/src/oaipmh/server.py @@ -1,6 +1,6 @@ from lxml.etree import ElementTree, Element, SubElement from lxml import etree -from datetime import datetime +from datetime import datetime, timezone try: from urllib.parse import urlencode, quote, unquote except ImportError: @@ -173,8 +173,12 @@ def _outputBasicEnvelope(self, **kw): e_tree = ElementTree(element=e_oaipmh) e_responseDate = SubElement(e_oaipmh, nsoai('responseDate')) # date should be first possible moment + # datetime.utcnow() is deprecated since Python 3.12 and scheduled + # for removal. datetime_to_datestamp() requires a naive datetime + # (it asserts dt.tzinfo is None), so convert the aware UTC + # value back to naive before passing it in. e_responseDate.text = datetime_to_datestamp( - datetime.utcnow().replace(microsecond=0)) + datetime.now(timezone.utc).replace(microsecond=0, tzinfo=None)) e_request = SubElement(e_oaipmh, nsoai('request')) for key, value in kw.items(): if key == 'from_': From 389c3e212af64e94ef5631e33f29579eee1f0669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:51:58 +0200 Subject: [PATCH 05/12] Convert packaging from setup.py/setup.cfg to uv + pyproject.toml - Generated pyproject.toml with metadata migrated from setup.py/setup.cfg - Python requirement raised to >=3.10 (dropping EOL 2.7/3.5-3.9) - Initialized uv; uv.lock added to .gitignore (library) - Removed obsolete packaging files: setup.py, setup.cfg, MANIFEST.in - Updated .gitignore with modern Python patterns Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 29 +++++++++++++++++++++++++++++ MANIFEST.in | 3 --- pyproject.toml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 12 ------------ setup.py | 26 -------------------------- 5 files changed, 77 insertions(+), 41 deletions(-) delete mode 100644 MANIFEST.in create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 926c686..f13fbc3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,32 @@ develop-eggs parts src/pyoai.egg-info .tox + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +dist/ +build/ +.eggs/ +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ + +# uv (library - do not commit lockfile) +uv.lock + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Testing +.pytest_cache/ +.coverage +htmlcov/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 64c47a1..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -recursive-include src * -recursive-include doc * -include * diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bef23a3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["setuptools>=75.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyoai" +version = "2.5.2pre" +description = "The oaipmh module is a Python implementation of an \"Open Archives Initiative Protocol for Metadata Harvesting\" (version 2) client and server." +readme = "README.rst" +requires-python = ">=3.10" +license = "BSD-3-Clause" +authors = [ + {name = "Infrae", email = "info@infrae.com"}, +] +keywords = ["OAI-PMH", "xml", "archive"] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", + "Environment :: Web Environment", +] +dependencies = [ + "lxml", + "six", +] + +[project.urls] +Homepage = "http://www.infrae.com/download/oaipmh" +Repository = "https://github.com/infrae/pyoai" + +[project.optional-dependencies] +dev = [ + "pytest", + "pre-commit", + "ruff", + "bumpver", +] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-dir] +"" = "src" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4150913..0000000 --- a/setup.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[bumpversion] -current_version = 2.5.0 -commit = True -tag = True - -[bdist_wheel] -universal = 1 - -[bumpversion:file:setup.py] -search = version='{current_version}' -replace = version='{new_version}' - diff --git a/setup.py b/setup.py deleted file mode 100644 index 7ef6617..0000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -from setuptools import setup, find_packages -from os.path import join, dirname - -setup( - name='pyoai', - version='2.5.2pre', - author='Infrae', - author_email='info@infrae.com', - url='http://www.infrae.com/download/oaipmh', - classifiers=["Development Status :: 4 - Beta", - "Programming Language :: Python", - "License :: OSI Approved :: BSD License", - "Topic :: Software Development :: Libraries :: Python Modules", - "Environment :: Web Environment"], - description="""The oaipmh module is a Python implementation of an "Open Archives Initiative Protocol for Metadata Harvesting" (version 2) client and server.""", - long_description=(open(join(dirname(__file__), 'README.rst')).read()+ - '\n\n'+ - open(join(dirname(__file__), 'HISTORY.txt')).read()), - long_description_content_type='text/x-rst', - packages=find_packages('src'), - package_dir = {'': 'src'}, - zip_safe=False, - license='BSD', - keywords='OAI-PMH xml archive', - install_requires=['lxml', 'six'], -) From 3fae82ea1b3dc35f87308c5ff9d7114e196b1ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:52:22 +0200 Subject: [PATCH 06/12] Consolidate version management with bumpver - Normalized version 2.5.2pre -> 2.5.2 (PEP 440 compliant) - Added [tool.bumpver] config to pyproject.toml - Single source of truth: pyproject.toml only Previously the version was inconsistent: setup.py had 2.5.2pre while setup.cfg bumpversion pointed to 2.5.0. Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bef23a3..ca96041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pyoai" -version = "2.5.2pre" +version = "2.5.2" description = "The oaipmh module is a Python implementation of an \"Open Archives Initiative Protocol for Metadata Harvesting\" (version 2) client and server." readme = "README.rst" requires-python = ">=3.10" @@ -46,3 +46,17 @@ where = ["src"] [tool.setuptools.package-dir] "" = "src" + +[tool.bumpver] +current_version = "2.5.2" +version_pattern = "MAJOR.MINOR.PATCH" +commit_message = "Bump version {old_version} -> {new_version}" +commit = true +tag = true +push = false + +[tool.bumpver.file_patterns] +"pyproject.toml" = [ + 'current_version = "{version}"', + 'version = "{version}"', +] From 7708e933c4824fca75d523b5c941d392018967a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:52:42 +0200 Subject: [PATCH 07/12] Add pre-commit hooks with ruff (staged files only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added .pre-commit-config.yaml with ruff lint + format - Configured ruff with basic flake8-equivalent rules (E, F, W) - Hooks run only on staged files — existing code not reformatted - Added hygiene hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files, detect-private-key Install with: pre-commit install Co-Authored-By: Claude Opus 4.7 (1M context) --- .pre-commit-config.yaml | 16 ++++++++++++++++ pyproject.toml | 6 ++++++ 2 files changed, 22 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7885cfb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: detect-private-key + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.0 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index ca96041..febee3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,12 @@ where = ["src"] [tool.setuptools.package-dir] "" = "src" +[tool.ruff] +target-version = "py310" + +[tool.ruff.lint] +select = ["E", "F", "W"] + [tool.bumpver] current_version = "2.5.2" version_pattern = "MAJOR.MINOR.PATCH" From df054cae713d66a0c5200c4aafa802b824538536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:53:08 +0200 Subject: [PATCH 08/12] Modernize GitHub Actions workflow - Python matrix: 3.10, 3.11, 3.12, 3.13 (was 2.7, 3.6-3.8) - Use uv for Python install + dependency resolution - Replace tox invocation with direct pytest - Upgraded action versions: checkout@v4, setup-uv@v5 - Added informational-only ruff lint job (non-blocking) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/run_tests.yml | 50 ++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 1d6abc2..90732a1 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -2,22 +2,44 @@ name: Run tests on: [push, pull_request] jobs: - build: + test: runs-on: ubuntu-latest strategy: - max-parallel: 4 + fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Test with tox - run: tox + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --all-extras --python ${{ matrix.python-version }} + + - name: Run tests + run: uv run --python ${{ matrix.python-version }} pytest + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python + run: uv python install 3.13 + + - name: Install dependencies + run: uv sync --all-extras + + - name: Lint with ruff + run: uv run ruff check . || true + + - name: Check formatting with ruff + run: uv run ruff format --check . || true From eed9bbbf4e66c51c476d3dd3279c5583a39d6ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 20:58:25 +0200 Subject: [PATCH 09/12] Switch test runner to pytest and fix Python 3.12+ compatibility - Configured [tool.pytest.ini_options] in pyproject.toml - python_classes = ["*TestCase"] to avoid collecting non-test TestError - Removed tox.ini and src/oaipmh/tests/runtests.sh Python 3.12+ compatibility fixes (required for test matrix): - pkg_resources (removed from stdlib setuptools) -> importlib.metadata - unittest.makeSuite / test_suite() -> removed (dead code for zope.testrunner) - unittest.assertEquals (removed alias) -> assertEqual - unittest.assert_ (removed alias) -> assertTrue - lxml evaluator.evaluate (removed attribute) -> evaluator is callable directly - Relative imports: `from fakeclient` -> `from .fakeclient` - Dropped Python 2 compat shims (StringIO fallbacks, urllib2 fallback, six.PY2/PY3) All 58 tests pass on Python 3.13. Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 4 + src/oaipmh/client.py | 26 +++--- src/oaipmh/common.py | 10 +-- src/oaipmh/metadata.py | 2 +- src/oaipmh/tests/runtests.sh | 3 - src/oaipmh/tests/test_broken.py | 11 +-- src/oaipmh/tests/test_client.py | 81 ++++++++---------- src/oaipmh/tests/test_datestamp.py | 24 +++--- src/oaipmh/tests/test_deleted_records.py | 21 ++--- src/oaipmh/tests/test_server.py | 100 +++++++++-------------- src/oaipmh/tests/test_validation.py | 19 ++--- tox.ini | 18 ---- 12 files changed, 124 insertions(+), 195 deletions(-) delete mode 100755 src/oaipmh/tests/runtests.sh delete mode 100644 tox.ini diff --git a/pyproject.toml b/pyproject.toml index febee3a..3972455 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,10 @@ target-version = "py310" [tool.ruff.lint] select = ["E", "F", "W"] +[tool.pytest.ini_options] +testpaths = ["src/oaipmh/tests"] +python_classes = ["*TestCase"] + [tool.bumpver] current_version = "2.5.2" version_pattern = "MAJOR.MINOR.PATCH" diff --git a/src/oaipmh/client.py b/src/oaipmh/client.py index fc8dba5..52d33e9 100644 --- a/src/oaipmh/client.py +++ b/src/oaipmh/client.py @@ -143,11 +143,11 @@ def GetMetadata_impl(self, args, tree): def Identify_impl(self, args, tree): namespaces = self.getNamespaces() evaluator = etree.XPathEvaluator(tree, namespaces=namespaces) - identify_node = evaluator.evaluate( + identify_node = evaluator( '/oai:OAI-PMH/oai:Identify')[0] identify_evaluator = etree.XPathEvaluator(identify_node, namespaces=namespaces) - e = identify_evaluator.evaluate + e = identify_evaluator repositoryName = e('string(oai:repositoryName/text())') baseURL = e('string(oai:baseURL/text())') @@ -180,12 +180,12 @@ def ListMetadataFormats_impl(self, args, tree): evaluator = etree.XPathEvaluator(tree, namespaces=namespaces) - metadataFormat_nodes = evaluator.evaluate( + metadataFormat_nodes = evaluator( '/oai:OAI-PMH/oai:ListMetadataFormats/oai:metadataFormat') metadataFormats = [] for metadataFormat_node in metadataFormat_nodes: e = etree.XPathEvaluator(metadataFormat_node, - namespaces=namespaces).evaluate + namespaces=namespaces) metadataPrefix = e('string(oai:metadataPrefix/text())') schema = e('string(oai:schema/text())') metadataNamespace = e('string(oai:metadataNamespace/text())') @@ -229,17 +229,17 @@ def buildRecords(self, # first find resumption token if available evaluator = etree.XPathEvaluator(tree, namespaces=namespaces) - token = evaluator.evaluate( + token = evaluator( 'string(/oai:OAI-PMH/*/oai:resumptionToken/text())') if token.strip() == '': token = None - record_nodes = evaluator.evaluate( + record_nodes = evaluator( '/oai:OAI-PMH/*/oai:record') result = [] for record_node in record_nodes: record_evaluator = etree.XPathEvaluator(record_node, namespaces=namespaces) - e = record_evaluator.evaluate + e = record_evaluator # find header node header_node = e('oai:header')[0] # create header @@ -261,12 +261,12 @@ def buildIdentifiers(self, namespaces, tree): evaluator = etree.XPathEvaluator(tree, namespaces=namespaces) # first find resumption token is available - token = evaluator.evaluate( + token = evaluator( 'string(/oai:OAI-PMH/*/oai:resumptionToken/text())') #'string(/oai:OAI-PMH/oai:ListIdentifiers/oai:resumptionToken/text())') if token.strip() == '': token = None - header_nodes = evaluator.evaluate( + header_nodes = evaluator( '/oai:OAI-PMH/oai:ListIdentifiers/oai:header') result = [] for header_node in header_nodes: @@ -278,16 +278,16 @@ def buildSets(self, namespaces, tree): evaluator = etree.XPathEvaluator(tree, namespaces=namespaces) # first find resumption token if available - token = evaluator.evaluate( + token = evaluator( 'string(/oai:OAI-PMH/oai:ListSets/oai:resumptionToken/text())') if token.strip() == '': token = None - set_nodes = evaluator.evaluate( + set_nodes = evaluator( '/oai:OAI-PMH/oai:ListSets/oai:set') sets = [] for set_node in set_nodes: e = etree.XPathEvaluator(set_node, - namespaces=namespaces).evaluate + namespaces=namespaces) # make sure we get back unicode strings instead # of lxml.etree._ElementUnicodeResult objects. setSpec = six.text_type(e('string(oai:setSpec/text())')) @@ -368,7 +368,7 @@ def makeRequest(self, **kw): def buildHeader(header_node, namespaces): e = etree.XPathEvaluator(header_node, - namespaces=namespaces).evaluate + namespaces=namespaces) identifier = e('string(oai:identifier/text())') datestamp = datestamp_to_datetime( str(e('string(oai:datestamp/text())'))) diff --git a/src/oaipmh/common.py b/src/oaipmh/common.py index c602ada..bc0af16 100644 --- a/src/oaipmh/common.py +++ b/src/oaipmh/common.py @@ -1,4 +1,4 @@ -import pkg_resources +from importlib.metadata import PackageNotFoundError, version as _pkg_version from oaipmh import error @@ -61,11 +61,9 @@ def __init__(self, repositoryName, baseURL, protocolVersion, adminEmails, self._descriptions = [] if toolkit_description: - req = pkg_resources.Requirement.parse('pyoai') - egg = pkg_resources.working_set.find(req) - if egg: - version = '%s' % egg.version - else: + try: + version = '%s' % _pkg_version('pyoai') + except PackageNotFoundError: version = '' self.add_description( '')[-1].split('')[0] first_el = xml.split('>')[0] self.assertTrue(first_el.startswith(' Date: Sun, 19 Apr 2026 20:59:13 +0200 Subject: [PATCH 10/12] Remove six dependency (Python 3-only package) - Replaced six.text_type with str - Removed six.PY3 check (always True) - Removed six from [project] dependencies Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 1 - src/oaipmh/client.py | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3972455..0e5eb4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ ] dependencies = [ "lxml", - "six", ] [project.urls] diff --git a/src/oaipmh/client.py b/src/oaipmh/client.py index 52d33e9..f39b28b 100644 --- a/src/oaipmh/client.py +++ b/src/oaipmh/client.py @@ -3,7 +3,6 @@ from __future__ import nested_scopes from __future__ import absolute_import -import six try: import urllib.request as urllib2 @@ -114,14 +113,12 @@ def parse(self, xml): # and we're basically hacking around non-wellformedness anyway, # but oh well if self._ignore_bad_character_hack: - xml = six.text_type(xml, 'UTF-8', 'replace') + xml = str(xml, 'UTF-8', 'replace') # also get rid of character code 12 xml = xml.replace(chr(12), '?') xml = xml.encode('UTF-8') - if six.PY3: - if hasattr(xml, "encode"): - xml = xml.encode("utf-8") - # xml = xml.encode("utf-8") + if hasattr(xml, "encode"): + xml = xml.encode("utf-8") return etree.XML(xml) # implementation of the various methods, delegated here by @@ -290,8 +287,8 @@ def buildSets(self, namespaces, tree): namespaces=namespaces) # make sure we get back unicode strings instead # of lxml.etree._ElementUnicodeResult objects. - setSpec = six.text_type(e('string(oai:setSpec/text())')) - setName = six.text_type(e('string(oai:setName/text())')) + setSpec = str(e('string(oai:setSpec/text())')) + setName = str(e('string(oai:setName/text())')) # XXX setDescription nodes sets.append((setSpec, setName, None)) return sets, token From 5351655eae7a64c4954aa6593a1be8dfbd2fce2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 21:00:34 +0200 Subject: [PATCH 11/12] Remove legacy files and modernize README Deleted: - .hgignore, .hgtags (Mercurial remnants, repo is on git) - buildout.cfg (zc.buildout config; replaced by uv) - Makefile (referenced `python setup.py sdist upload`; had bogus path from another project; superseded by uv) - INSTALL.txt (outdated: mentioned Python 2.3, `python setup.py install`, and codespeak lxml URL) README.rst: - Fixed Python 2 `print record` -> `print(record)` - Updated CI badge to new workflow URL (mpasternak/pyoai) - Added Python version support badge (3.10-3.13) - Added modern installation instructions (pip, uv) - Added development quickstart (uv sync, uv run pytest) Co-Authored-By: Claude Opus 4.7 (1M context) --- .hgignore | 5 ---- .hgtags | 21 --------------- INSTALL.txt | 28 -------------------- Makefile | 25 ------------------ README.rst | 75 +++++++++++++++++++++++++++++++++++++--------------- buildout.cfg | 14 ---------- 6 files changed, 54 insertions(+), 114 deletions(-) delete mode 100644 .hgignore delete mode 100644 .hgtags delete mode 100644 INSTALL.txt delete mode 100644 Makefile delete mode 100644 buildout.cfg diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 9e7e8ec..0000000 --- a/.hgignore +++ /dev/null @@ -1,5 +0,0 @@ -src/pyoai.egg-info -bin -parts -.installed.cfg -develop-eggs diff --git a/.hgtags b/.hgtags deleted file mode 100644 index b2798a6..0000000 --- a/.hgtags +++ /dev/null @@ -1,21 +0,0 @@ -d5a3ef73faa2d52ee05571021fccabd3967312b6 pyoai-2_0b1 -6adf7a5390092088c5ed121965caa185fae4767e pyoai-2_0 -89abb3fc4659a08a4b232298d9848b0c7d7bd0ea eepi-2.1-prerelease -2531c56e02c0828b26b707d59540a56cb6afc3da pyoai-2.1.2 -191ae315d02db00c42822dc6a1b6f08f181bbe3a pyoai-2.1.3 -64b86a11ecf6107316baa836480e8a4a619eb44e pyoai-2.1.4 -3754c3f119fa72b5174c5358048c1fd644f983d0 pyoai-2.1.6 -0000000000000000000000000000000000000000 pyoai-2.1.6 -65d0f7bdee6a5b5ff386d153dfb4ebdd458a3fab pyoai-2.1.5 -fffb45120065457f6ac2d397b97c8c1069ff1697 pyoai-2.2.1 -c3ae70b661a8bec2273432f2d540fe963c7d32c0 pyoai-2.3 -63ad54d4a44a623786cc123f76b2cfa59edb1ebe pyoai-2.3.1 -9a9e75ac23adbe19bb015a29faf464c882057378 pyoai-2.4 -77c9da2756cc17de4ea226de7d04737daed0e7e8 pyoai-2.4.1 -e659e2a4e8d7a07cebf58b6838b7738a0f8a306b pyoai-2.4.2 -0000000000000000000000000000000000000000 pyoai-2.4.2 -712f939900749717ecabddbb39f2a716bf8838a4 pyoai-2.4.2 -0000000000000000000000000000000000000000 pyoai-2.4.2 -88386ea25a94fae2815f1f364394c389ecd98351 pyoai-2.4.2 -780e7c76d845999d8b2797ff2a43a1e17bb268e9 pyoai-2.4.3 -570b3c00bbfff2341bae2c69ec12a1529624ea91 2.4.4 diff --git a/INSTALL.txt b/INSTALL.txt deleted file mode 100644 index d4d62dd..0000000 --- a/INSTALL.txt +++ /dev/null @@ -1,28 +0,0 @@ -Installation -============ - -python setup.py install - -will install the oaipmh module in your Python's site-packages. - -Python version -============== - -The module should work for Python versions 2.3 and up. - -Dependencies -============ - -The oaipmh module needs the lxml python bindings for -libxml2/libxslt. You can find lxml here: - -http://codespeak.net/lxml - -lxml needs libxml2 and libxslt (though not their Python bindings; -installing those is optional). libxml2 can can be found here: - -http://xmlsoft.org/ - -and libxslt can be found here: - -http://xmlsoft.org/XSLT diff --git a/Makefile b/Makefile deleted file mode 100644 index dfaa5f6..0000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts - - -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - rm -rf amms_planop2xls/mainwindow_ui.py - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-test: ## remove test and coverage artifacts - rm -fr .tox/ - rm -f .coverage - rm -fr htmlcov/ - -release: clean ## package and upload a release - python setup.py sdist upload - python setup.py bdist_wheel upload diff --git a/README.rst b/README.rst index 8bccea9..cb0f4e2 100644 --- a/README.rst +++ b/README.rst @@ -1,35 +1,68 @@ ====== -OAIPMH +pyoai ====== .. image:: https://img.shields.io/pypi/v/pyoai.svg - :target: https://pypi.python.org/pypi/pyoai + :target: https://pypi.python.org/pypi/pyoai -.. image:: https://github.com/infrae/pyoai/workflows/Run%20tests/badge.svg - :target: https://github.com/infrae/pyoai/actions?query=workflow%3A%22Run+tests%22 - -The oaipmh module is a Python implementation of an "Open Archives -Initiative Protocol for Metadata Harvesting" (version 2) client and -server. The protocol is described here: +.. image:: https://github.com/mpasternak/pyoai/actions/workflows/run_tests.yml/badge.svg + :target: https://github.com/mpasternak/pyoai/actions/workflows/run_tests.yml -http://www.openarchives.org/OAI/openarchivesprotocol.html +.. image:: https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13-blue + :target: https://www.python.org/ -Below is a simple implementation of an OAIPMH client: +The ``oaipmh`` module is a Python implementation of the +`Open Archives Initiative Protocol for Metadata Harvesting`_ (version 2) +client and server. ->>> from oaipmh.client import Client ->>> from oaipmh.metadata import MetadataRegistry, oai_dc_reader +.. _Open Archives Initiative Protocol for Metadata Harvesting: http://www.openarchives.org/OAI/openarchivesprotocol.html ->>> URL = 'http://uni.edu/ir/oaipmh' +Installation +============ ->>> registry = MetadataRegistry() ->>> registry.registerReader('oai_dc', oai_dc_reader) ->>> client = Client(URL, registry) +.. code-block:: bash ->>> for record in client.listRecords(metadataPrefix='oai_dc'): ->>> print record + pip install pyoai +Or with `uv`_:: -The pyoai package also contains a generic server implementation of the -OAIPMH protocol, this is used as the foundation of the `MOAI Server Platform`_ + uv add pyoai -.. _MOAI Server Platform: http://pypi.python.org/pypi/MOAI +.. _uv: https://docs.astral.sh/uv/ + +Requirements +============ + +* Python 3.10+ +* `lxml `_ + +Example +======= + +A simple OAI-PMH client: + +.. code-block:: python + + from oaipmh.client import Client + from oaipmh.metadata import MetadataRegistry, oai_dc_reader + + URL = 'http://uni.edu/ir/oaipmh' + + registry = MetadataRegistry() + registry.registerReader('oai_dc', oai_dc_reader) + client = Client(URL, registry) + + for record in client.listRecords(metadataPrefix='oai_dc'): + print(record) + +The pyoai package also contains a generic server implementation of the +OAI-PMH protocol. It is used as the foundation of the +`MOAI Server Platform `_. + +Development +=========== + +.. code-block:: bash + + uv sync --all-extras + uv run pytest diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index 4803cfa..0000000 --- a/buildout.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[buildout] -develop = . -parts = devpython test -newest = false - -[devpython] -recipe = zc.recipe.egg -interpreter = devpython -eggs = pyoai - -[test] -recipe = zc.recipe.testrunner -eggs = pyoai -defaults = ['--tests-pattern', '^f?tests$', '-v'] From 79b7d8c786289cd5694d55462c503edeffee2134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 19 Apr 2026 21:06:55 +0200 Subject: [PATCH 12/12] Update HISTORY.txt with 2.5.2 modernization changes Co-Authored-By: Claude Opus 4.7 (1M context) --- HISTORY.txt | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/HISTORY.txt b/HISTORY.txt index e6aa4ea..59e26f7 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -2,6 +2,44 @@ Changelog ========= 2.5.2 (unreleased) +- Replaced deprecated ``datetime.utcnow()`` with timezone-aware + ``datetime.now(timezone.utc)``. + +- Modernized packaging: migrated from ``setup.py`` / ``setup.cfg`` / + ``MANIFEST.in`` / ``buildout.cfg`` to ``pyproject.toml`` + ``uv``. + +- Dropped support for Python 2.7 and Python 3.5-3.9 (all EOL). Now + requires Python 3.10+. CI matrix: 3.10, 3.11, 3.12, 3.13. + +- Removed ``six`` dependency; package is Python 3-only. + +- Switched test runner from ``tox`` / ``python -m unittest`` to + ``pytest``. + +- Python 3.12+ compatibility fixes: replaced removed ``pkg_resources`` + with ``importlib.metadata``; removed dead ``unittest.makeSuite`` and + ``test_suite()`` code; renamed deprecated ``assertEquals`` / + ``assert_`` aliases; replaced removed lxml ``evaluator.evaluate`` + attribute with direct call. + +- Replaced Travis/tox-based CI with a modern GitHub Actions workflow + using ``uv``; added informational (non-blocking) ``ruff`` lint job. + +- Added ``pre-commit`` configuration with ``ruff`` (staged-files only) + and standard hygiene hooks. + +- Consolidated version management to a single source in + ``pyproject.toml`` via ``bumpver`` (previously version was split + between ``setup.py`` at ``2.5.2pre`` and a stale ``bumpversion`` + config at ``2.5.0``). + +- Removed obsolete files: ``.hgignore``, ``.hgtags`` (Mercurial + remnants), ``buildout.cfg``, ``Makefile``, ``INSTALL.txt``. + +- Modernized README: updated badges, fixed Python 2 ``print`` + statement in example, added install instructions for ``pip`` and + ``uv``. + 2.5.1 - Added customizable client retry policy (contributed by adimascio)