diff --git a/.github/actions/wait-for-pypi-version/action.yaml b/.github/actions/wait-for-pypi-version/action.yaml new file mode 100644 index 0000000..8a0b5d4 --- /dev/null +++ b/.github/actions/wait-for-pypi-version/action.yaml @@ -0,0 +1,93 @@ +name: 'Wait for PyPI version' +description: 'Wait for a specific package version to become available on PyPI or TestPyPI' +inputs: + repository: + description: 'PyPI repository type: "pypi" or "testpypi"' + required: true + package: + description: 'Package name' + required: true + version: + description: 'Package version to wait for' + required: true + max_attempts: + description: 'Maximum number of retry attempts' + required: false + default: '30' + wait_seconds: + description: 'Seconds to wait between attempts' + required: false + default: '10' + +runs: + using: composite + steps: + - name: Install requests + shell: bash + run: | + python -m pip install --upgrade pip + pip install requests + + - name: Wait for version to be available + shell: python + env: + REPOSITORY: ${{ inputs.repository }} + PACKAGE: ${{ inputs.package }} + VERSION: ${{ inputs.version }} + MAX_ATTEMPTS: ${{ inputs.max_attempts }} + WAIT_SECONDS: ${{ inputs.wait_seconds }} + run: | + import os + import sys + import time + + import requests + + repository = os.environ["REPOSITORY"].strip().lower() + package = os.environ["PACKAGE"] + version = os.environ["VERSION"] + max_attempts = int(os.environ.get("MAX_ATTEMPTS", "30")) + wait_seconds = int(os.environ.get("WAIT_SECONDS", "10")) + + if repository == "testpypi": + api_url = f"https://test.pypi.org/pypi/{package}/json" + repo_name = "TestPyPI" + elif repository == "pypi": + api_url = f"https://pypi.org/pypi/{package}/json" + repo_name = "PyPI" + else: + print( + f"ERROR: repository must be 'pypi' or 'testpypi', got {repository!r}", + file=sys.stderr, + ) + sys.exit(1) + + for attempt in range(max_attempts): + try: + r = requests.get(api_url, timeout=10) + r.raise_for_status() + data = r.json() + versions = data.get("releases", {}) + keys = list(versions.keys()) + print("Available versions:", keys[-10:]) # Show last 10 versions + if version in versions: + print(f"✓ Version {version} is available on {repo_name}") + print(f"Version {version} is now available on {repo_name}") + sys.exit(0) + print(f"✗ Version {version} is NOT available on {repo_name}") + except Exception as e: + print(f"Error checking version: {e}") + + current = attempt + 1 + print( + f"Attempt {current}/{max_attempts}: Version {version} not yet available " + f"on {repo_name}, waiting {wait_seconds} seconds..." + ) + time.sleep(wait_seconds) + + print( + f"ERROR: Version {version} did not become available on {repo_name} " + f"after {max_attempts} attempts", + file=sys.stderr, + ) + sys.exit(1) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml deleted file mode 100644 index 543a2cb..0000000 --- a/.github/workflows/deploy.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build and upload to PyPi - -on: - push: - tags: - - "*" - release: - types: - - published - -jobs: - testpypi_push: - environment: - name: deploy - url: https://test.pypi.org/p/MDAnalysisData - permissions: - id-token: write - if: | - github.repository == 'MDAnalysis/MDAnalysisData' && - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) - name: Build, upload and test pure Python wheels to TestPypi - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: testpypi_deploy - uses: MDAnalysis/pypi-deployment@main - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - with: - test_submission: true - package_name: MDAnalysisData - module_name: 'MDAnalysisData' - test_deps: 'pytest pytest-mock' - - pypi_push: - environment: - name: deploy - url: https://pypi.org/p/MDAnalysisData - permissions: - id-token: write - if: | - github.repository == 'MDAnalysis/MDAnalysisData' && - (github.event_name == 'release' && github.event.action == 'published') - name: Build, upload and test pure Python wheels to PyPi - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: pypi_deploy - uses: MDAnalysis/pypi-deployment@main - if: github.event_name == 'release' && github.event.action == 'published' - with: - package_name: MDAnalysisData - module_name: 'MDAnalysisData' - test_deps: 'pytest pytest-mock' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..bf15def --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,229 @@ +name: PyPi Package Deployment + +on: + push: + tags: + - "*" + release: + types: + - published + +concurrency: + group: "${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }}" + cancel-in-progress: false + +defaults: + run: + shell: bash -l {0} + +jobs: + build: + name: Build package + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract-version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build package (binary wheel and source distribution package) + run: python -m build + + - name: Check package + run: twine check dist/* + + - name: Extract package version + id: extract-version + run: | + WHEEL_FILE=$(ls dist/*.whl) + VERSION=$(basename "$WHEEL_FILE" | sed -n 's/mdanalysisdata-\([^-]*\)-.*/\1/p') + if [ -z "$VERSION" ]; then + python -m pip install --upgrade pip + pip install "$WHEEL_FILE" --quiet + VERSION=$(python -c "import MDAnalysisData; print(MDAnalysisData.__version__)") + pip uninstall -y MDAnalysisData --quiet + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Extracted version: $VERSION" + + - name: Upload dist files + uses: actions/upload-artifact@v7 + with: + name: dist-files + path: dist/ + retention-days: 1 + + test-pytest: + name: Run tests + runs-on: ubuntu-latest + needs: build + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Download dist files + uses: actions/download-artifact@v8 + with: + name: dist-files + path: dist/ + + - name: Install package with test dependencies and tests + run: | + python -m pip install --upgrade pip + WHEEL_FILE=$(ls dist/*.whl) + pip install "${WHEEL_FILE}[test]" + + - name: Test import + run: | + python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully')" + + - name: Run tests (without data file downloads) + run: | + pytest --verbose -m "not online" --pyargs MDAnalysisData + + deploy-testpypi: + name: Deploy to TestPyPI + runs-on: ubuntu-latest + needs: [build, test-pytest] + if: | + github.repository == 'MDAnalysis/MDAnalysisData' && + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) + environment: + name: testpypi + url: https://test.pypi.org/p/MDAnalysisData + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download dist files + uses: actions/download-artifact@v8 + with: + name: dist-files + path: dist/ + + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@v1.13.0 + with: + repository-url: https://test.pypi.org/legacy/ + verbose: true # useful debugging info (eg wrong classifiers) + skip-existing: true # allows repeated testing with same tag + + deploy-pypi: + name: Deploy to PyPI + runs-on: ubuntu-latest + needs: [build, test-pytest] + if: | + github.repository == 'MDAnalysis/MDAnalysisData' && + (github.event_name == 'release' && github.event.action == 'published') + environment: + name: pypi + url: https://pypi.org/p/MDAnalysisData + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download dist files + uses: actions/download-artifact@v8 + with: + name: dist-files + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1.13.0 + + test-deployed-testpypi: + name: Test deployed package (TestPyPI) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + needs: [build, deploy-testpypi] + if: | + github.repository == 'MDAnalysis/MDAnalysisData' && + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Checkout repository for actions + uses: actions/checkout@v6 + with: + path: source # put code under different path and do not cd to avoid interference with pytest invocation (missing authors.py) + + - name: Wait for version to be available on TestPyPI + uses: ./source/.github/actions/wait-for-pypi-version + with: + repository: testpypi + package: MDAnalysisData + version: ${{ needs.build.outputs.version }} + + - name: Install from TestPyPI + run: | + python -m pip install --upgrade pip + pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "MDAnalysisData[test]==${{ needs.build.outputs.version }}" + + - name: Test import + run: | + python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully from TestPyPi')" + + - name: Run tests + run: | + pytest --verbose -m "not online" --pyargs MDAnalysisData + + test-deployed-pypi: + name: Test deployed package (PyPI) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + needs: [build, deploy-pypi] + if: | + github.repository == 'MDAnalysis/MDAnalysisData' && + (github.event_name == 'release' && github.event.action == 'published') + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Checkout repository for actions + uses: actions/checkout@v6 + with: + path: source # put code under different path and do not cd to avoid interference with pytest invocation (missing authors.py) + + - name: Wait for version to be available on PyPI + uses: ./source/.github/actions/wait-for-pypi-version + with: + repository: pypi + package: MDAnalysisData + version: ${{ needs.build.outputs.version }} + + - name: Install from PyPI + run: | + python -m pip install --upgrade pip + pip install "MDAnalysisData[test]==${{ needs.build.outputs.version }}" + + - name: Test import + run: | + python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully from PyPi')" + + - name: Run tests + run: | + pytest --verbose -m "not online" --pyargs MDAnalysisData diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3c85b00..0659471 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: - python-version: 3.9 + python-version: 3.14 - name: Display Python version run: python -c "import sys; print(sys.version)" @@ -44,7 +44,7 @@ jobs: make html - name: deploy docs - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/_build/html diff --git a/.github/workflows/gh-ci.yml b/.github/workflows/gh-ci.yml index 501c22b..e5b1ee0 100644 --- a/.github/workflows/gh-ci.yml +++ b/.github/workflows/gh-ci.yml @@ -24,24 +24,24 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.11", "3.12", "3.13", "3.14"] include: - os: macOS-latest - python-version: "3.9" + python-version: "3.11" - os: macOS-latest - python-version: "3.12" + python-version: "3.14" - os: windows-latest - python-version: "3.9" + python-version: "3.11" - os: windows-latest - python-version: "3.12" + python-version: "3.14" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -55,7 +55,7 @@ jobs: - name: Install package dependencies run: | - python -m pip install six setuptools tqdm + python -m pip install setuptools tqdm - name: Install package run: | @@ -67,7 +67,7 @@ jobs: - name: Codecov if: github.event_name != 'schedule' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v6 with: name: codecov-${{ matrix.os }}-py${{ matrix.python-version }} token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3c11c..bae6bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -## Changes +### Changes +- Update package to only support NEP29 range (Python 3.11 - 3.14 as of writing) - Update packaging to be PEP518 compliant. +### Fixes +- removed deprecated pkg_resources use (#75) + ## [0.9.0] - 2023-10-30 ### Changes diff --git a/MANIFEST.in b/MANIFEST.in index db4e2ce..f3cac6b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,3 @@ -include README.md AUTHORS LICENSE CHANGELOG.md +include README.md CHANGELOG.md +graft MDAnalysisData +global-exclude __pycache__ *.pyc *~ diff --git a/MDAnalysisData/descr/__init__.py b/MDAnalysisData/descr/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/MDAnalysisData/descr/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/MDAnalysisData/tests/conftest.py b/MDAnalysisData/tests/conftest.py new file mode 100644 index 0000000..8cb55bc --- /dev/null +++ b/MDAnalysisData/tests/conftest.py @@ -0,0 +1,5 @@ +# see also pyproject.toml pytest.markers +def pytest_configure(config): + config.addinivalue_line( + "markers", "online: will fetch remote files (deselect with with '-m \"not online\"')", + ) diff --git a/pyproject.toml b/pyproject.toml index 85b2d8f..fad179a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "setuptools >= 40.9.0", + "setuptools >= 77.0.0", "versioningit", ] build-backend = "setuptools.build_meta" @@ -8,7 +8,8 @@ build-backend = "setuptools.build_meta" [project] name = "MDAnalysisData" description = "MDAnalysis example data" -license = {file = "LICENSE" } +license = "BSD-3-Clause" +license-files = ["LICENSE", "AUTHORS"] authors = [ {name = "Oliver Beckstein", email = "orbeckst@gmail.com"}, ] @@ -19,23 +20,22 @@ classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows ", + "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Bio-Informatics", "Topic :: Scientific/Engineering :: Chemistry", "Topic :: Software Development :: Libraries :: Python Modules", ] readme = {file = "README.md", content-type = "text/markdown"} -requires-python = ">=3.9" +requires-python = ">=3.11" dependencies = [ "tqdm", ] @@ -59,17 +59,14 @@ documentation = "https://www.mdanalysis.org/MDAnalysisData" [tool.setuptools] zip-safe = true -include-package-data = true [tool.setuptools.packages.find] -namespaces = false -include=["MDAnalysisData"] +namespaces = true +where = ["."] exclude=["docs"] [tool.setuptools.package-data] -MDAnalysisData = [ - "descr/*.rst", -] +"MDAnalysisData.descr" = ["*.rst"] [tool.versioningit] default-version = "1+unknown" diff --git a/setup.py b/setup.py index a21e25d..9433195 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ """ Barebones setup.py for dynamic authors list generation. """ -import codecs import os import warnings @@ -33,7 +32,7 @@ def dynamic_author_list(): # MDAnalysisData. authors = [] - with codecs.open(abspath('AUTHORS'), encoding='utf-8') as infile: + with open(abspath('AUTHORS'), encoding='utf-8') as infile: # An author is a bullet point under the title "Chronological list of # authors". We first want move the cursor down to the title of # interest. @@ -64,7 +63,7 @@ def dynamic_author_list(): # Write the authors.py file. out_path = abspath('MDAnalysisData/authors.py') - with codecs.open(out_path, 'w', encoding='utf-8') as outfile: + with open(out_path, 'w', encoding='utf-8') as outfile: # Write the header header = '''\ #-*- coding:utf-8 -*-