From ee5894eaf00e13c1173f7e21dae82cb96e39d4f4 Mon Sep 17 00:00:00 2001 From: Jen Bradley <55467578+janbridley@users.noreply.github.com> Date: Thu, 16 May 2024 13:24:11 -0400 Subject: [PATCH] Support Python 3.6+ (#13) * Add minimal setup.py * Switch dataclass to namedtuple * Fix typing for python < 3.7 * Split tests into two blocks * Add required workflow steps * pip install -r * Add pytest to reqs * Rename CI features * Properly implement workflow_call procedure * Remove incorrect runs_on key * Rename action * Fix syntax in CI * Set legacy tests to run after modern versions pass * Clean up CI script * Clean up action names * Parameterize runs-on * Add runs-on input to workflow callable * Move python 3.6 tests to ubuntu 20.04 * Test python 3.5 * Remove broken python 3.5 * Update install files * Swapped requirements.txt with requirements.in * Swapped tests requirements.txt with requirements.in * Added legacy test requirements file * Fixed pin and reran pip-compile on python 3.6 * Set default value for requirements-file --- .github/workflows/CI.yaml | 53 +++++++++++--------------- .github/workflows/run_tests.yaml | 37 ++++++++++++++++++ doc/requirements.in | 4 ++ doc/requirements.txt | 64 +++++++++++++++++++++++++++++++- parsnip/parse.py | 11 +++--- parsnip/patterns.py | 6 +-- pyproject.toml | 2 +- setup.py | 4 ++ tests/conftest.py | 10 +---- tests/requirements-legacy.in | 2 + tests/requirements-legacy.txt | 32 ++++++++++++++++ tests/requirements.in | 2 + tests/requirements.txt | 17 ++++++++- 13 files changed, 193 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/run_tests.yaml create mode 100644 doc/requirements.in create mode 100644 setup.py create mode 100644 tests/requirements-legacy.in create mode 100644 tests/requirements-legacy.txt create mode 100644 tests/requirements.in diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 9c5fe9e..3f390ee 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -1,45 +1,36 @@ name: Run Tests -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - on: - # trigger on pull requests pull_request: - - # trigger on all commits to master push: branches: - "main" - "breaking" - - # trigger on request workflow_dispatch: + jobs: - run_tests: - name: Run tests on ubuntu-latest with Python ${{ matrix.python-version }} - runs-on: ubuntu-latest + run-tests-modern-python: + strategy: + fail-fast: true + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + runs-on: ["ubuntu-latest"] + # Pull in the test script from run_tests and distribute python from matrix versions + uses: ./.github/workflows/run_tests.yaml + with: + python-version: ${{ matrix.python-version }} + runs-on: ${{ matrix.runs-on }} + + run-tests-legacy-python: + needs: run-tests-modern-python # Wait until tests pass on python 3.9+ strategy: fail-fast: true matrix: - python-version: ["3.9","3.10","3.11","3.12"] - 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 requirements.txt - python -m pip install tests/requirements.txt - - name: Install package - run: | - python --version - python -c "import numpy; print('numpy', numpy.__version__)" - python -m pip install . -v --progress-bar off - - name: Test with pytest - run: | - python -m pytest -v + python-version: ["3.6", "3.7", "3.8"] + runs-on: ["ubuntu-20.04"] + uses: ./.github/workflows/run_tests.yaml + with: + python-version: ${{ matrix.python-version }} + runs-on: ${{ matrix.runs-on }} + requirements-file: "tests/requirements-legacy.txt" diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..e3e125a --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,37 @@ +name: Run tests + +on: + workflow_call: + inputs: + python-version: + required: true + type: string + runs-on: + required: true + type: string + requirements-file: + default: "tests/requirements.txt" + type: string + +jobs: + run_test: + runs-on: ${{ inputs.runs-on }} + steps: + # Steps common to both groups + - uses: actions/checkout@v4 + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + - name: Install dependencies + run: | + python -m pip install -r requirements.txt + python -m pip install -r ${{ inputs.requirements-file }} + - name: Install package + run: | + python --version + python -m pip install . -v --progress-bar off + python -c "import parsnip; print('parsnip', parsnip.__version__)" + - name: Test with pytest + run: | + python -m pytest -v diff --git a/doc/requirements.in b/doc/requirements.in new file mode 100644 index 0000000..6480824 --- /dev/null +++ b/doc/requirements.in @@ -0,0 +1,4 @@ +autodocsumm==0.2.12 +furo==2024.5.6 +numpy>=1.26.4 +sphinx==7.3.7 diff --git a/doc/requirements.txt b/doc/requirements.txt index 26cd033..3be342f 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,4 +1,66 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --strip-extras requirements.in +# +alabaster==0.7.16 + # via sphinx autodocsumm==0.2.12 + # via -r requirements.in +babel==2.15.0 + # via sphinx +beautifulsoup4==4.12.3 + # via furo +certifi==2024.2.2 + # via requests +charset-normalizer==3.3.2 + # via requests +docutils==0.21.2 + # via sphinx furo==2024.5.6 -numpy>=1.19 + # via -r requirements.in +idna==3.7 + # via requests +imagesize==1.4.1 + # via sphinx +jinja2==3.1.4 + # via sphinx +markupsafe==2.1.5 + # via jinja2 +numpy==1.26.4 + # via -r requirements.in +packaging==24.0 + # via sphinx +pygments==2.18.0 + # via + # furo + # sphinx +requests==2.31.0 + # via sphinx +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.5 + # via beautifulsoup4 sphinx==7.3.7 + # via + # -r requirements.in + # autodocsumm + # furo + # sphinx-basic-ng +sphinx-basic-ng==1.0.0b2 + # via furo +sphinxcontrib-applehelp==1.0.8 + # via sphinx +sphinxcontrib-devhelp==1.0.6 + # via sphinx +sphinxcontrib-htmlhelp==2.0.5 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.7 + # via sphinx +sphinxcontrib-serializinghtml==1.1.10 + # via sphinx +urllib3==2.2.1 + # via requests diff --git a/parsnip/parse.py b/parsnip/parse.py index baca349..8d7712c 100644 --- a/parsnip/parse.py +++ b/parsnip/parse.py @@ -1,5 +1,4 @@ """CIF parsing tools.""" - import warnings import numpy as np @@ -15,9 +14,9 @@ def _remove_comments_from_line(line): def read_table( filename: str, keys: str, - filter_line: tuple[tuple[str, str]] = ((r",\s+", ",")), + filter_line: tuple = ((r",\s+", ",")), keep_original_key_order=False, -) -> np.ndarray[str]: +) -> np.ndarray: r"""Extract data from a CIF file loop_ table. CIF files store tabular data as whitespace-delimited blocks that start with `loop_`. @@ -61,7 +60,7 @@ def read_table( Args: filename (str): The name of the .cif file to be parsed. keys (tuple[str]): The names of the keys to be parsed. - filter_line (tuple[tuple[str]], optional): + filter_line (tuple[tuple[str,str]], optional): A tuple of strings that are compiled to a regex filter and applied to each data line. (Default value: ((r",\s+",",")) ) keep_original_key_order (bool, optional): @@ -146,13 +145,13 @@ def read_table( def read_fractional_positions( filename: str, - filter_line: tuple[tuple[str, str]] = ((r",\s+", ",")), + filter_line: tuple = ((r",\s+", ",")), ): r"""Extract the fractional X,Y,Z coordinates from a CIF file. Args: filename (str): The name of the .cif file to be parsed. - filter_line (tuple[tuple[str]], optional): + filter_line (tuple[tuple[str,str]], optional): A tuple of strings that are compiled to a regex filter and applied to each data line. (Default value: ((r",\s+",",")) ) diff --git a/parsnip/patterns.py b/parsnip/patterns.py index f81923a..430a6ff 100644 --- a/parsnip/patterns.py +++ b/parsnip/patterns.py @@ -8,11 +8,11 @@ _comma_prune_spaces = re.compile(r",\s+") -def compile_pattern_from_strings(filter_patterns: tuple[str]): +def compile_pattern_from_strings(filter_patterns: tuple): """Return a regex pattern that matches any of the characters in the filter. Args: - filter_patterns (list[str]): Description + filter_patterns (tuple[str]): Description Returns: re.Pattern: Pattern matching any of the input characters. @@ -46,7 +46,7 @@ class LineCleaner: what that pattern will be replaced with. """ - def __init__(self, patterns: tuple[tuple[str, str]]): + def __init__(self, patterns: tuple): self.patterns, self.replacements = [], [] # If we only have a single tuple diff --git a/pyproject.toml b/pyproject.toml index 8e6412d..2fb22f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "parsnip" version = "0.0.2" -requires-python = ">=3.9" +requires-python = ">=3.6" description = "Minimal library for parsing CIF/mmCIF files in Python." readme = "README.md" license = { file = "LICENSE" } diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1c7fac8 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +# ruff: noqa: D100 +from setuptools import setup + +setup(name="parsnip") diff --git a/tests/conftest.py b/tests/conftest.py index 5852b37..4589560 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,18 +1,12 @@ -import dataclasses import os +from collections import namedtuple import pytest # ruff: noqa: N816. Allow mixed-case global variables -@dataclasses.dataclass -class CifData: - """Class to hold the filename and stored keys for a CIF file.""" - - filename: str - symop_keys: tuple[str] - atom_site_keys: tuple[str] +CifData = namedtuple("CifData", ["filename", "symop_keys", "atom_site_keys"]) box_keys = ( diff --git a/tests/requirements-legacy.in b/tests/requirements-legacy.in new file mode 100644 index 0000000..c9e5797 --- /dev/null +++ b/tests/requirements-legacy.in @@ -0,0 +1,2 @@ +gemmi==0.6.3 +pytest==7.0.1 diff --git a/tests/requirements-legacy.txt b/tests/requirements-legacy.txt new file mode 100644 index 0000000..1f9c539 --- /dev/null +++ b/tests/requirements-legacy.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with python 3.6 +# To update, run: +# +# pip-compile requirements-legacy.in +# +attrs==22.2.0 + # via pytest +gemmi==0.6.3 + # via -r requirements-legacy.in +importlib-metadata==4.8.3 + # via + # pluggy + # pytest +iniconfig==1.1.1 + # via pytest +packaging==21.3 + # via pytest +pluggy==1.0.0 + # via pytest +py==1.11.0 + # via pytest +pyparsing==3.1.2 + # via packaging +pytest==7.0.1 + # via -r requirements-legacy.in +tomli==1.2.3 + # via pytest +typing-extensions==4.1.1 + # via importlib-metadata +zipp==3.6.0 + # via importlib-metadata diff --git a/tests/requirements.in b/tests/requirements.in new file mode 100644 index 0000000..946cb29 --- /dev/null +++ b/tests/requirements.in @@ -0,0 +1,2 @@ +gemmi==0.6.5 +pytest==8.2.0 diff --git a/tests/requirements.txt b/tests/requirements.txt index b6d2376..ee7a087 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1 +1,16 @@ -gemmi +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --strip-extras requirements.in +# +gemmi==0.6.5 + # via -r requirements.in +iniconfig==2.0.0 + # via pytest +packaging==24.0 + # via pytest +pluggy==1.5.0 + # via pytest +pytest==8.2.0 + # via -r requirements.in