Skip to content

Commit

Permalink
Add windows and macos platforms to CI (#383)
Browse files Browse the repository at this point in the history
* Add windows and macos to strategy matrix

* Make pypairix dep platform-dependent

* Make pysam dep platform-dependent

* Fix drive letter colon bug on Windows

* test: Skip tabix or pairix loading tests if libs not available

* Do int cast to int64 instead of platform-dependent int

* Force unix-style line endings on tsv outputs

* Clean up temp files manually on Windows

See issues with NamedTemporaryFile on Windows: https://docs.python.org/3.12/library/tempfile.html#tempfile.NamedTemporaryFile

* tests: Skip digest test on windows (irreproducible issue only observed on 32-bit action runner)
  • Loading branch information
nvictus committed Feb 2, 2024
1 parent f309973 commit fab84a4
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 21 deletions.
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,25 @@ jobs:
hatch run lint
Test:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
include:
- os: windows-latest
python-version: "3.11"
- os: macos-latest
python-version: "3.11"

runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: |
- name: Run tests on ${{ matrix.os }}
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
# stop the build if there are Python syntax errors or undefined names
Expand All @@ -44,6 +52,6 @@ jobs:
Release:
# Only run on v* tag events
if: startsWith(github.ref, 'refs/tags')
if: startsWith(github.ref, 'refs/tags/v')
needs: [Lint, Test]
uses: open2c/cooler/.github/workflows/publish.yml@master
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ complete = [
"ipytree>=0.2.2",
"ipywidgets>=8.0.0",
"matplotlib",
"pypairix",
"pypairix; platform_system != 'Windows'",
"psutil",
"pysam",
"pysam; platform_system != 'Windows'",
]
dev = [
"cooler[complete,test]",
Expand Down
4 changes: 2 additions & 2 deletions src/cooler/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ def _loc_slice(df, beg, end):

# Select bin annotations that correspond to the bin1 IDs in the pixels df
if "bin1_id" in columns:
bin1 = pixels["bin1_id"].to_numpy().astype(int, copy=False, casting="safe")
bin1 = pixels["bin1_id"].to_numpy().astype(np.int64, copy=False, casting="safe")
if len(bin1) == 0:
bmin = bmax = 0
elif len(bins) > len(pixels):
Expand All @@ -612,7 +612,7 @@ def _loc_slice(df, beg, end):

# Select bin annotations that correspond to the bin2 IDs in the pixels df
if "bin2_id" in columns:
bin2 = pixels["bin2_id"].to_numpy().astype(int, copy=False, casting="safe")
bin2 = pixels["bin2_id"].to_numpy().astype(np.int64, copy=False, casting="safe")
if len(bin2) == 0:
bmin = bmax = 0
elif len(bins) > len(pixels):
Expand Down
7 changes: 4 additions & 3 deletions src/cooler/cli/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ def parse_field_param(arg, includes_colnum=True, includes_agg=True):


def parse_bins(arg):
# Provided chromsizes and binsize
if ":" in arg:
chromsizes_file, binsize = arg.split(":")
# User provided chromsizes path and binsize
arg_nodrive = op.splitdrive(arg)[1] # Remove drive + colon if on Windows
if ":" in arg_nodrive:
chromsizes_file, binsize = arg.rsplit(":", maxsplit=1)
if not op.exists(chromsizes_file):
raise ValueError(f'File "{chromsizes_file}" not found')
try:
Expand Down
6 changes: 4 additions & 2 deletions src/cooler/cli/digest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def digest(chromsizes, fasta, enzyme, out, header, rel_ids):
f = open(out, "w")

if header:
frags[0:0].to_csv(f, sep="\t", index=False, header=True)
frags.to_csv(f, sep="\t", index=False, header=False)
frags[0:0].to_csv(
f, sep="\t", lineterminator='\n', index=False, header=True
)
frags.to_csv(f, sep="\t", lineterminator='\n', index=False, header=False)
f.flush()
9 changes: 8 additions & 1 deletion src/cooler/cli/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,20 @@ def dump(
if is_first_chunk:
if header:
chunk[0:0].to_csv(
f, sep="\t", index=False, header=True, float_format=float_format
f,
sep="\t",
lineterminator='\n',
index=False,
header=True,
float_format=float_format,
na_rep=na_rep,
)
is_first_chunk = False

chunk.to_csv(
f,
sep="\t",
lineterminator='\n',
index=False,
header=False,
float_format=float_format,
Expand Down
6 changes: 4 additions & 2 deletions src/cooler/cli/makebins.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def makebins(chromsizes, binsize, out, header, rel_ids):
f = open(out, "w")

if header:
bins[0:0].to_csv(f, sep="\t", index=False, header=True)
bins.to_csv(f, sep="\t", index=False, header=False)
bins[0:0].to_csv(
f, sep="\t", lineterminator='\n', index=False, header=True
)
bins.to_csv(f, sep="\t", lineterminator='\n', index=False, header=False)
f.flush()
16 changes: 14 additions & 2 deletions src/cooler/create/_create.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import os.path as op
import posixpath
import tempfile
Expand Down Expand Up @@ -464,6 +465,7 @@ def create(
"""
file_path, group_path = parse_cooler_uri(cool_uri)
file_path = op.realpath(file_path)

if mode is None:
mode = "a" if append else "w"
Expand Down Expand Up @@ -708,11 +710,16 @@ def create_from_unordered(

dtypes = _get_dtypes_arg(dtypes, kwargs)

is_windows = os.name == "nt"
if is_windows and delete_temp:
delete = False
else:
delete = delete_temp
temp_files = []

# Sort pass
tf = tempfile.NamedTemporaryFile(
suffix=".multi.cool", delete=delete_temp, dir=temp_dir
suffix=".multi.cool", delete=delete, dir=temp_dir
)
temp_files.append(tf)
uris = []
Expand All @@ -730,7 +737,7 @@ def create_from_unordered(
edges = np.linspace(0, n, int(np.sqrt(n)), dtype=int)

tf2 = tempfile.NamedTemporaryFile(
suffix=".multi.cool", delete=delete_temp, dir=temp_dir
suffix=".multi.cool", delete=delete, dir=temp_dir
)
temp_files.append(tf2)
uris2 = []
Expand Down Expand Up @@ -763,6 +770,11 @@ def create_from_unordered(
logger.info(f"Merging into {cool_uri}")
create(cool_uri, bins, chunks, columns=columns, dtypes=dtypes, mode=mode, **kwargs)

if is_windows and delete_temp:
for tf in temp_files:
if not tf.closed:
tf.close()
os.remove(tf.name)
del temp_files


Expand Down
4 changes: 2 additions & 2 deletions src/cooler/create/_ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,8 @@ def aggregate(self, chrom): # pragma: no cover
np.searchsorted(start_abspos, abspos2, side="right") - 1
)
else:
rel_bin1 = np.floor(table["cut1"] / binsize).astype(int)
rel_bin2 = np.floor(table["cut2"] / binsize).astype(int)
rel_bin1 = np.floor(table["cut1"] / binsize).astype(np.int64)
rel_bin2 = np.floor(table["cut2"] / binsize).astype(np.int64)
table["bin1_id"] = chrom_binoffset[table["chrom_id1"].values] + rel_bin1
table["bin2_id"] = chrom_binoffset[table["chrom_id2"].values] + rel_bin2

Expand Down
4 changes: 2 additions & 2 deletions src/cooler/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ def digest(fasta_records, enzyme):
raise ValueError(f"Unknown enzyme name: {enzyme}") from e

def _each(chrom):
seq = bioseq.Seq(str(fasta_records[chrom]))
cuts = np.r_[0, np.array(cut_finder(seq)) + 1, len(seq)].astype(int)
seq = bioseq.Seq(str(fasta_records[chrom][:]))
cuts = np.r_[0, np.array(cut_finder(seq)) + 1, len(seq)].astype(np.int64)
n_frags = len(cuts) - 1

frags = pd.DataFrame(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import traceback; traceback.print_tb(result.exception.__traceback__)
"""
import os
import os.path as op

import click
Expand Down Expand Up @@ -76,6 +77,7 @@ def test_makebins():
)


@pytest.mark.skipif(os.name == "nt", reason="Non-reproducible off-by-one bug on Win32 GitHub runner")
def test_digest():
runner = CliRunner()
result = runner.invoke(
Expand Down
5 changes: 5 additions & 0 deletions tests/test_create_ingest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# import filecmp
import importlib.util
import os
import os.path as op
import tempfile
Expand All @@ -15,6 +16,8 @@
from cooler.cli.cload import tabix as cload_tabix
from cooler.cli.load import load

pysam_missing = importlib.util.find_spec("pysam") is None
pairix_missing = importlib.util.find_spec("pairix") is None
_pandas_major_version = int(pd.__version__.split(".")[0])

tmp = tempfile.gettempdir()
Expand Down Expand Up @@ -175,6 +178,7 @@ def _mock_hdf5_pairs():
should_work_with_int32_cols(chromsizes, bintable, mock_pairs)


@pytest.mark.skipif(pysam_missing, reason="pysam not installed")
@pytest.mark.filterwarnings("ignore")
@pytest.mark.parametrize(
"bins_path,pairs_path,ref_path,nproc",
Expand Down Expand Up @@ -214,6 +218,7 @@ def test_cload_tabix(bins_path, pairs_path, ref_path, nproc):
pass


@pytest.mark.skipif(pairix_missing, reason="pairix not installed")
@pytest.mark.parametrize(
"bins_path,pairs_path,ref_path,nproc",
[
Expand Down

0 comments on commit fab84a4

Please sign in to comment.