Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 71 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ jobs:
- os: windows-latest
python-version: '3.14'

env:
JOB_ID: ${{ strategy.job-index }}
NUM_JOBS: ${{ strategy.job-total }}

steps:
- uses: actions/checkout@v7
with:
Expand All @@ -88,19 +84,6 @@ jobs:
run: |
mkdir C:\a
xcopy D:\a C:\a /s /e
- name: Determine if downloads are enabled for this job
# for testing, limit downloads from the resource servers to only the selected job for
# PRs and the main branch; note that the main branch is tested weekly via `cron`,
# so this ensures all Python versions will be periodically integration tested with the
# resource servers
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }}
shell: bash
run: |
SELECTED_JOB=$((10#$(date +%V) % $NUM_JOBS))
if [[ $SELECTED_JOB == $JOB_ID ]]; then
# set environment variable to download resources for selected job
echo "DOWNLOADS_ENABLED=true" >> $GITHUB_ENV
fi
- name: Install dependencies
run: |
pip install pytest-cov sybil
Expand Down Expand Up @@ -144,3 +127,74 @@ jobs:
- name: Test with pytest
run: |
pytest --cov=snps tests README.md

# Everyday CI runs fully offline against fixture-backed resources. The live-integration
# job below is the only one that contacts the real resource servers (Zenodo, S3,
# Ensembl, NCBI). It runs weekly and on changes to `main`.
#
# To exercise every OS x Python combination against the live servers (and the full
# offline suite) over time without running them all every week, this job picks one
# combination per ISO week; over a full rotation all combinations are covered.
live-matrix:
needs: [test]
if: ${{ github.repository == 'apriha/snps' && (github.event_name == 'schedule' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.base_ref == 'main')) }}
runs-on: ubuntu-latest
outputs:
os: ${{ steps.pick.outputs.os }}
python-version: ${{ steps.pick.outputs.python-version }}
steps:
- name: Pick this week's OS / Python combination
id: pick
shell: bash
run: |
oses=(ubuntu-latest macos-latest windows-latest)
pys=(3.9 3.10 3.11 3.12 3.13 3.14)
n=$(( ${#oses[@]} * ${#pys[@]} ))
idx=$(( 10#$(date -u +%V) % n ))
os="${oses[$(( idx / ${#pys[@]} ))]}"
py="${pys[$(( idx % ${#pys[@]} ))]}"
echo "Selected for this week: $os / Python $py"
echo "os=$os" >> "$GITHUB_OUTPUT"
echo "python-version=$py" >> "$GITHUB_OUTPUT"

# A single-entry matrix from the selector so the chosen combination shows in the
# job name (e.g. "live-integration (ubuntu-latest, 3.14)").
live-integration:
needs: [live-matrix]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ["${{ needs.live-matrix.outputs.os }}"]
python-version: ["${{ needs.live-matrix.outputs.python-version }}"]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Ensure Python and source code are on same drive (Windows)
if: ${{ runner.os == 'Windows' }}
shell: cmd
run: |
mkdir C:\a
xcopy D:\a C:\a /s /e
- name: Install dependencies
run: |
pip install pytest-cov sybil
pip install .
- name: Test with pytest against the resource servers (Ubuntu & macOS)
if: ${{ runner.os != 'Windows' }}
env:
DOWNLOADS_ENABLED: "true"
run: |
pytest --cov=snps tests README.md
- name: Test with pytest against the resource servers (Windows)
if: ${{ runner.os == 'Windows' }}
working-directory: C:\a\snps\snps
env:
DOWNLOADS_ENABLED: "true"
run: |
pytest --cov=snps tests README.md
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ tests/resources/*
!tests/resources/gsa_chrpos_map.txt
!tests/resources/gsa_rsid_map.txt
!tests/resources/dbsnp_151_37_reverse.txt
!tests/resources/chip_clusters.tsv
!tests/resources/low_quality_snps.tsv
tests/input/23andme.txt.zip
tests/input/discrepant_snps[12].csv
tests/input/ftdna.csv.gz
Expand Down
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,10 @@ genotype files from the following DNA testing sources:

Additionally, `snps` can read a variety of "generic" CSV and TSV files.

## Dependencies
## Requirements

`snps` requires [Python](https://www.python.org) 3.9+ and the following Python
packages:

- [numpy](http://www.numpy.org)
- [pandas](http://pandas.pydata.org)
- [atomicwrites](https://github.com/untitaker/python-atomicwrites)
`snps` requires [Python](https://www.python.org) 3.9+; its dependencies are specified in
`pyproject.toml` and installed automatically by `pip`.

## Installation

Expand Down Expand Up @@ -116,7 +112,7 @@ Load a raw data file exported from a DNA testing source (e.g.,

```python
>>> from snps import SNPs
>>> s = SNPs("resources/sample1.23andme.txt.gz")
>>> s = SNPs(paths[0])
```

`snps` automatically detects the source format and [normalizes](https://snps.readthedocs.io/en/stable/snps.html#snps.snps.SNPs.snps) the data:
Expand Down Expand Up @@ -147,7 +143,7 @@ The SNPs are available as a `pandas.DataFrame`:
Combine SNPs from multiple files (e.g., combine data from different testing companies):

```python
>>> results = s.merge([SNPs("resources/sample2.ftdna.csv.gz")])
>>> results = s.merge([SNPs(paths[1])])
>>> s.count
1006949
```
Expand Down Expand Up @@ -190,6 +186,9 @@ assembly. This ensures the REF alleles in the VCF are accurate:

All output files are saved to the [output directory](https://snps.readthedocs.io/en/stable/output_files.html).

Downloaded resources are cached automatically; set the `SNPS_DATA_DIR` environment
variable to control where they are stored.

### Generate Synthetic Data

Generate synthetic genotype data for testing, examples, or demonstrations:
Expand Down
28 changes: 22 additions & 6 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
"""
Pytest configuration for testing code examples in README.md using Sybil.
"""Pytest configuration: offline resources by default + README doctests via Sybil.

This conftest.py enables Sybil to parse and test Python code blocks in the
README.md file as part of the pytest test suite. The PythonCodeBlockParser
evaluates fenced Python code blocks (```python), while SkipParser allows
selective skipping of examples using Markdown comments when needed.
An autouse fixture injects a fixture-backed resource provider so the entire suite
(including the README examples) runs with zero network and zero mocks. The Sybil
configuration enables testing Python code blocks in README.md as part of pytest:
``PythonCodeBlockParser`` evaluates fenced ``python`` blocks, while ``SkipParser``
allows selectively skipping examples with Markdown comments.
"""

import pytest
from sybil import Sybil
from sybil.parsers.markdown import PythonCodeBlockParser, SkipParser

from snps.resources import set_default_provider
from tests.support import FakeResources


@pytest.fixture(autouse=True)
def _offline_resources():
"""Use a fixture-backed resource provider for every test (no network, no mocks)."""
set_default_provider(FakeResources())
try:
yield
finally:
set_default_provider(None)


pytest_collect_file = Sybil(
parsers=[
PythonCodeBlockParser(),
SkipParser(),
],
patterns=["README.md"],
fixtures=["_offline_resources"],
).pytest()
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ classifiers = [
"Topic :: Scientific/Engineering :: Information Analysis",
"Topic :: Utilities",
]
dependencies = ["numpy", "pandas", "atomicwrites"]
dependencies = [
"numpy",
"pandas",
"atomicwrites",
"pooch",
]

[project.optional-dependencies]
ezancestry = ["ezancestry"]
Expand Down
Loading
Loading