diff --git a/.github/workflows/python-somacore.yaml b/.github/workflows/python-somacore.yaml index 5d6f433e..163c36b0 100644 --- a/.github/workflows/python-somacore.yaml +++ b/.github/workflows/python-somacore.yaml @@ -21,17 +21,12 @@ jobs: cache: pip cache-dependency-path: python-spec/requirements-py${{ env.PYTHON_VERSION }}-lint.txt - uses: psf/black@stable - with: - src: "./python-spec" - uses: isort/isort-action@v1 - with: - sort-paths: "./python-spec" - name: Install typing packages run: | pip install -r python-spec/requirements-py${{ env.PYTHON_VERSION }}-lint.txt - name: Run mypy - working-directory: ./python-spec - run: mypy ./src + run: mypy ./python-spec/src run-tests: runs-on: ubuntu-latest @@ -41,6 +36,10 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 + with: + # setuptools-scm needs a deep clone so it can look through history + # to find a relevant tag. + fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -50,7 +49,7 @@ jobs: run: | pip install --upgrade pip wheel pytest pytest-cov setuptools pip install -r python-spec/requirements-py${{ matrix.python-version }}.txt - pip install ./python-spec + pip install . - name: Run tests working-directory: ./python-spec run: | @@ -75,10 +74,9 @@ jobs: - name: Set up environment run: | pip install --upgrade build pip wheel setuptools setuptools-scm - ./python-spec/write-version-file - python -m build python-spec + python -m build . - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - packages_dir: python-spec/dist + packages_dir: dist diff --git a/.gitignore b/.gitignore index 2f2cbf1e..a3e4fda2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ temp/ tmp/ +**/__pycache__ +**/*.egg-info +build/ +dist/ +python-spec/src/somacore/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a51ea10f..96b38d56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,15 +8,11 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - - # Because the built-in pre-commit hooks don't natively support projects - # not at the root of their directory, we have to roll our own hooks. - - repo: local + - repo: https://github.com/psf/black + rev: '22.12.0' hooks: - - id: format-check - name: Format check - language: python - files: ^python-spec/ - types_or: [python, pyi] - entry: ./.pre-commit/format-check.sh - additional_dependencies: [black, isort] + - id: black + - repo: https://github.com/PyCQA/isort + rev: '5.11.4' + hooks: + - id: isort diff --git a/.pre-commit/format-check.sh b/.pre-commit/format-check.sh deleted file mode 100755 index 080e36b5..00000000 --- a/.pre-commit/format-check.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e - -cd python-spec -black . -isort . diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..e447782e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +# Python package manifest. We have to do this manually because otherwise +# setuptools-scm will try to include literally everything in the repo +# and we only want to include the Python-specific stuff in our build. + +global-exclude * +include pyproject.toml +include MANIFEST.in +recursive-include python-spec/README.md +recursive-include python-spec/src *.py py.typed diff --git a/python-spec/pyproject.toml b/pyproject.toml similarity index 59% rename from python-spec/pyproject.toml rename to pyproject.toml index aa7d9c35..213c9056 100644 --- a/python-spec/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,15 @@ +# Build and release configuration for the Python version of core SOMA. +# This lives at the root directory to save us lots of headaches with versioning. + [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools", "setuptools-scm", "wheel"] build-backend = "setuptools.build_meta" [project] name = "somacore" description = "Python-language API specification and base utilities for implementation of the SOMA system." dynamic = ["version"] -readme = "./README.md" +readme = "./python-spec/README.md" dependencies = [ "anndata", "attrs>=22.1", @@ -22,14 +25,20 @@ classifiers = ["License :: OSI Approved :: MIT License"] dev = ["black", "isort", "setuptools-scm"] [tool.setuptools] -packages.find.where = ["src"] +packages.find.where = ["python-spec/src"] package-data.somacore = ["py.typed"] -dynamic.version.attr = "somacore._version.version" + +[tool.setuptools_scm] +write_to = "python-spec/src/somacore/_version.py" +# Keep Python executable package versioning separate from the spec and R impl +# by requiring `python-` at the start of the tag (e.g. `python-v1.2.3`). +tag_regex = '^python-(?P[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$' [tool.isort] profile = "black" line_length = 88 force_single_line = true +known_first_party = ["somacore"] single_line_exclusions = ["typing", "typing_extensions"] [[tool.mypy.overrides]] diff --git a/python-spec/.gitignore b/python-spec/.gitignore deleted file mode 100644 index e259e06a..00000000 --- a/python-spec/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -**/__pycache__ -**/*.egg-info -build -dist -src/somacore/_generated_version.py diff --git a/python-spec/setup.py b/python-spec/setup.py deleted file mode 100644 index d7b9fee1..00000000 --- a/python-spec/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Degenerate setup.py so you can ``pip install -e .`` if you want.""" -import setuptools - -setuptools.setup() diff --git a/python-spec/setuptools-scm.toml b/python-spec/setuptools-scm.toml deleted file mode 100644 index eac9a17f..00000000 --- a/python-spec/setuptools-scm.toml +++ /dev/null @@ -1,9 +0,0 @@ -# setuptools-scm specific data from a pyproject.toml file. -# Used by `write-version-file`. - -[tool.setuptools_scm] -root = ".." -write_to = "python-spec/src/somacore/_generated_version.py" -# Keep Python executable package versioning separate from the spec and R impl -# by requiring `python-` at the start of the tag (e.g. `python-v1.2.3`). -tag_regex = '^python-(?P[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$' diff --git a/python-spec/src/somacore/__init__.py b/python-spec/src/somacore/__init__.py index a8980d08..6565c3d4 100644 --- a/python-spec/src/somacore/__init__.py +++ b/python-spec/src/somacore/__init__.py @@ -4,15 +4,21 @@ unified namespace. """ -from somacore import _version from somacore import base from somacore import data from somacore import ephemeral from somacore import options from somacore.query import axis -__version__ = _version.version -__version_tuple__ = _version.version_tuple +try: + # This trips up mypy since it's a generated file: + from somacore import _version # type: ignore[attr-defined] + + __version__ = _version.version + __version_tuple__ = _version.version_tuple +except ImportError: + __version__ = "0.0.0.dev+invalid" + __version_tuple__ = (0, 0, 0, "dev", "invalid") SOMAObject = base.SOMAObject Collection = base.Collection diff --git a/python-spec/src/somacore/_version.py b/python-spec/src/somacore/_version.py deleted file mode 100644 index 1f0c9570..00000000 --- a/python-spec/src/somacore/_version.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Internal version information to indirect `_generated_version.py`. - -setuptools imports this module to find the version for the package at *build -time*. We use this indirect module so that if the user has not run -``write-version-file`` (e.g. they just did a fresh checkout), ``pip install`` -will still work. If we used ``setuptools.dynamic.version.attr`` and pointed it -at `somacore._generated_version` directly, installing from a fresh checkout -would fail. -""" - -import pathlib -from typing import Any, Dict, Tuple, Union - - -def _read_version() -> Tuple[str, Tuple[Union[str, int], ...]]: - version_contents: Dict[str, Any] = dict( - version="0.0.0.dev+local-checkout", - version_tuple=(0, 0, 0, "dev", "local-checkout"), - ) - - # We do this the hard way since neither - # from somacore import _generated_version - # nor - # from . import _generated_version - # will work in the build environment. - gen_file = pathlib.Path(__file__).parent / "_generated_version.py" - try: - exec(gen_file.read_text(), version_contents) - except Exception: - # The generated file does not exist or is not valid. - # Ignore it. - pass - return version_contents["version"], version_contents["version_tuple"] - - -version, version_tuple = _read_version() diff --git a/python-spec/update-requirements-txt b/python-spec/update-requirements-txt index e2764d8d..00f10ae9 100755 --- a/python-spec/update-requirements-txt +++ b/python-spec/update-requirements-txt @@ -14,12 +14,12 @@ LINTVER=3.7 for PYVER in 3.7 3.8 3.9 3.10; do CONDIR="$TEMPDIR/py-$PYVER" conda create -y -p "$CONDIR" "python=$PYVER" - conda run -p "$CONDIR" pip install . + conda run -p "$CONDIR" pip install .. conda run -p "$CONDIR" pip freeze --exclude somacore >"requirements-py$PYVER.txt" done CONDIR="$TEMPDIR/py-$LINTVER-lint" conda create -y -p "$CONDIR" "python=$LINTVER" -conda run -p "$CONDIR" pip install . mypy +conda run -p "$CONDIR" pip install .. mypy conda run -p "$CONDIR" mypy --install-types --non-interactive ./src conda run -p "$CONDIR" pip freeze --exclude somacore >"requirements-py$LINTVER-lint.txt" diff --git a/python-spec/write-version-file b/python-spec/write-version-file deleted file mode 100755 index ea71d46d..00000000 --- a/python-spec/write-version-file +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# Updates the `_version.py` file. Run this *before* building a package. -# Make sure that you have `setuptools-scm` installed in your environment. -# -# When running `pip install`, pip gets the package directory and copies it out -# to a temporary folder before executing anything related to setup (i.e., only -# the contents of `python-spec/...`). This means that the `.git` folder, which -# is in the parent directory, is absent, and thus setuptools-scm is not able to -# detect the version at build time. -# -# To work around this, we just run this *before* doing any package related work -# when running a build. - -set -ex -cd "$(dirname "$0")" -python -m setuptools_scm --config setuptools-scm.toml