Skip to content

Commit 3d986d4

Browse files
committed
fix onlist auth and build parity
1 parent 9bdbe65 commit 3d986d4

File tree

5 files changed

+149
-14
lines changed

5 files changed

+149
-14
lines changed

seqspec/auth.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import json
22
import os
3+
import tomllib
34
from pathlib import Path
45
from typing import Dict, List, Optional, Tuple
56
from urllib.parse import urlparse
67

7-
import tomllib
8-
98
AUTH_CONFIG_ENV = "SEQSPEC_AUTH_CONFIG"
109

1110

seqspec/seqspec_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
This module provides functionality to validate seqspec files against the specification schema.
44
"""
55

6-
from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
76
import os
7+
from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
88
from os import path
99
from pathlib import Path
1010
from typing import Dict, List, Optional

seqspec/seqspec_onlist.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,22 @@ def run_onlist(parser: ArgumentParser, args: Namespace) -> None:
125125
if args.format:
126126
# Join operation - requires download and output path
127127
save_path = args.output or Path(args.yaml).resolve().parent
128-
result_path = join_onlists_and_save(onlists, args.format, save_path, base_path)
128+
result_path = join_onlists_and_save(
129+
onlists,
130+
args.format,
131+
save_path,
132+
base_path,
133+
auth_profile=args.auth_profile,
134+
)
129135
print(result_path)
130136
elif args.output:
131137
# Download operation - download remote files to output location
132-
result_paths = download_onlists_to_path(onlists, args.output, base_path)
138+
result_paths = download_onlists_to_path(
139+
onlists,
140+
args.output,
141+
base_path,
142+
auth_profile=args.auth_profile,
143+
)
133144
for path_info in result_paths:
134145
print(f"{path_info['url']}")
135146
else:
@@ -210,7 +221,10 @@ def get_onlist_urls(onlists: List[Onlist], base_path: Path) -> List[Dict[str, st
210221

211222

212223
def download_onlists_to_path(
213-
onlists: List[Onlist], output_path: Path, base_path: Path
224+
onlists: List[Onlist],
225+
output_path: Path,
226+
base_path: Path,
227+
auth_profile: str | None = None,
214228
) -> List[Dict[str, str]]:
215229
"""Download remote onlists and return local paths."""
216230
downloaded_paths = []
@@ -222,7 +236,7 @@ def download_onlists_to_path(
222236
downloaded_paths.append({"file_id": onlist.file_id, "url": str(local_path)})
223237
else:
224238
# Remote file - download it
225-
onlist_elements = read_remote_list(onlist, auth_profile=args.auth_profile)
239+
onlist_elements = read_remote_list(onlist, auth_profile=auth_profile)
226240
# Create unique filename for this onlist
227241
filename = f"{onlist.file_id}_{output_path.name}"
228242
download_path = output_path.parent / filename
@@ -235,7 +249,11 @@ def download_onlists_to_path(
235249

236250

237251
def join_onlists_and_save(
238-
onlists: List[Onlist], format_type: str, output_path: Path, base_path: Path
252+
onlists: List[Onlist],
253+
format_type: str,
254+
output_path: Path,
255+
base_path: Path,
256+
auth_profile: str | None = None,
239257
) -> str:
240258
"""Download onlists, join them, and save to output path."""
241259
# Download all onlists first
@@ -244,7 +262,7 @@ def join_onlists_and_save(
244262
if onlist.urltype == "local":
245263
content = read_local_list(onlist, str(base_path))
246264
else:
247-
content = read_remote_list(onlist, auth_profile=args.auth_profile)
265+
content = read_remote_list(onlist, auth_profile=auth_profile)
248266
onlist_contents.append(content)
249267

250268
# Join the onlists

src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,23 @@ use seqspec::utils;
2323

2424
use clap::{Parser, Subcommand};
2525

26+
const BUILD_DEPRECATED_MESSAGE: &str =
27+
"seqspec build is deprecated. Use seqspec init/insert/modify or construct the spec directly.";
28+
2629
#[derive(Parser, Debug)]
2730
#[command(name = "seqspec", version)]
2831
struct Args {
2932
#[command(subcommand)]
3033
subcmd: Commands,
3134
}
3235

36+
#[derive(clap::Args, Debug)]
37+
struct BuildArgs {}
38+
3339
#[derive(Subcommand, Debug)]
3440
enum Commands {
3541
Auth(seqspec_auth::AuthArgs),
42+
Build(BuildArgs),
3643
Version(seqspec_version::VersionArgs),
3744
Format(seqspec_format::FormatArgs),
3845
Find(seqspec_find::FindArgs),
@@ -55,6 +62,7 @@ fn main() {
5562
let args = Args::parse();
5663
match args.subcmd {
5764
Commands::Auth(args) => seqspec_auth::run(&args).unwrap(),
65+
Commands::Build(_) => run_build_deprecated(),
5866
Commands::Version(args) => seqspec_version::run_version(&args),
5967
Commands::Format(args) => seqspec_format::run_format(&args),
6068
Commands::Find(args) => seqspec_find::run_find(&args),
@@ -74,3 +82,27 @@ fn main() {
7482
Commands::Print(args) => seqspec_print::run_print(&args),
7583
}
7684
}
85+
86+
fn run_build_deprecated() {
87+
eprintln!("{}", BUILD_DEPRECATED_MESSAGE);
88+
std::process::exit(1);
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use super::*;
94+
95+
#[test]
96+
fn test_build_subcommand_is_recognized() {
97+
let args = Args::try_parse_from(["seqspec", "build"]).unwrap();
98+
assert!(matches!(args.subcmd, Commands::Build(_)));
99+
}
100+
101+
#[test]
102+
fn test_build_deprecated_message_matches_python_cli() {
103+
assert_eq!(
104+
BUILD_DEPRECATED_MESSAGE,
105+
"seqspec build is deprecated. Use seqspec init/insert/modify or construct the spec directly."
106+
);
107+
}
108+
}

tests/test_seqspec_onlist.py

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import pytest
21
import os
2+
from argparse import ArgumentParser, Namespace
33
from pathlib import Path
44
from unittest.mock import patch
5+
6+
import pytest
7+
58
from seqspec.seqspec_onlist import (
9+
Onlist,
10+
download_onlists_to_path,
611
get_onlists,
712
join_onlist_contents,
13+
join_onlists_and_save,
814
run_onlist,
915
)
10-
from seqspec.utils import load_spec
11-
from argparse import Namespace, ArgumentParser
12-
from seqspec.seqspec_onlist import Onlist
13-
1416

1517

1618
def test_get_onlists_region(dogmaseq_dig_spec):
@@ -19,27 +21,111 @@ def test_get_onlists_region(dogmaseq_dig_spec):
1921
assert len(onlists) == 1
2022
assert onlists[0].file_id == "RNA-737K-arc-v1.txt"
2123

24+
2225
def test_get_onlists_region_type(dogmaseq_dig_spec):
2326
"""Test get_onlists with region-type selector"""
2427
onlists = get_onlists(dogmaseq_dig_spec, "rna", "region-type", "barcode")
2528
assert len(onlists) > 0
2629
for onlist in onlists:
2730
assert onlist is not None
2831

32+
2933
def test_get_onlists_read(dogmaseq_dig_spec):
3034
"""Test get_onlists with read selector"""
3135
onlists = get_onlists(dogmaseq_dig_spec, "rna", "read", "rna_R1")
3236
assert len(onlists) == 1
3337

38+
3439
def test_join_onlist_contents_product():
3540
"""Test joining onlists with product format"""
3641
contents = [["A", "B"], ["1", "2"]]
3742
joined = join_onlist_contents(contents, "product")
3843
assert set(joined) == {"A1", "A2", "B1", "B2"}
3944

45+
4046
def test_join_onlist_contents_multi():
4147
"""Test joining onlists with multi format"""
4248
contents = [["A", "B"], ["1", "2", "3"]]
4349
joined = join_onlist_contents(contents, "multi")
4450
assert joined == ["A 1", "B 2", "- 3"]
4551

52+
53+
def remote_onlist() -> Onlist:
54+
return Onlist(
55+
file_id="remote_list",
56+
filename="remote.txt.gz",
57+
filetype="txt.gz",
58+
filesize=123,
59+
url="https://example.org/remote.txt.gz",
60+
urltype="https",
61+
md5="abc",
62+
)
63+
64+
65+
def test_download_onlists_to_path_threads_auth_profile(tmp_path):
66+
calls = []
67+
68+
def fake_read_remote_list(onlist, base_path="", auth_profile=None):
69+
calls.append(
70+
{
71+
"file_id": onlist.file_id,
72+
"base_path": base_path,
73+
"auth_profile": auth_profile,
74+
}
75+
)
76+
return ["AAA", "CCC"]
77+
78+
output_path = tmp_path / "joined.txt"
79+
with patch("seqspec.seqspec_onlist.read_remote_list", side_effect=fake_read_remote_list):
80+
downloaded = download_onlists_to_path(
81+
[remote_onlist()],
82+
output_path,
83+
tmp_path,
84+
auth_profile="igvf",
85+
)
86+
87+
assert calls == [
88+
{
89+
"file_id": "remote_list",
90+
"base_path": "",
91+
"auth_profile": "igvf",
92+
}
93+
]
94+
assert len(downloaded) == 1
95+
assert downloaded[0]["file_id"] == "remote_list"
96+
assert downloaded[0]["url"].endswith("remote_list_joined.txt")
97+
assert Path(downloaded[0]["url"]).read_text().splitlines() == ["AAA", "CCC"]
98+
99+
100+
def test_join_onlists_and_save_threads_auth_profile(tmp_path):
101+
calls = []
102+
103+
def fake_read_remote_list(onlist, base_path="", auth_profile=None):
104+
calls.append(
105+
{
106+
"file_id": onlist.file_id,
107+
"base_path": base_path,
108+
"auth_profile": auth_profile,
109+
}
110+
)
111+
return ["AAA", "CCC"]
112+
113+
output_path = tmp_path / "product.txt"
114+
with patch("seqspec.seqspec_onlist.read_remote_list", side_effect=fake_read_remote_list):
115+
result_path = join_onlists_and_save(
116+
[remote_onlist()],
117+
"product",
118+
output_path,
119+
tmp_path,
120+
auth_profile="igvf",
121+
)
122+
123+
assert calls == [
124+
{
125+
"file_id": "remote_list",
126+
"base_path": "",
127+
"auth_profile": "igvf",
128+
}
129+
]
130+
assert result_path == str(output_path)
131+
assert output_path.read_text().splitlines() == ["AAA", "CCC"]

0 commit comments

Comments
 (0)