haplohub-cli 2.5.0__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/PKG-INFO +2 -2
  2. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/_version.py +3 -3
  3. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/algorithm/result/__init__.py +17 -3
  4. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/cohort/__init__.py +1 -1
  5. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/cohort/member/__init__.py +20 -9
  6. haplohub_cli-2.6.0/haplohub_cli/commands/cohort/sample.py +84 -0
  7. haplohub_cli-2.6.0/haplohub_cli/commands/cohort/tests/test_delete.py +88 -0
  8. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/file.py +18 -3
  9. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/metadata/__init__.py +1 -1
  10. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/api/client.py +5 -0
  11. haplohub_cli-2.6.0/haplohub_cli/core/deletion.py +28 -0
  12. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/generic.py +9 -3
  13. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/member.py +4 -4
  14. haplohub_cli-2.6.0/haplohub_cli/types/__init__.py +0 -0
  15. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/PKG-INFO +2 -2
  16. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/SOURCES.txt +3 -0
  17. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/requires.txt +1 -1
  18. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/scm_file_list.json +50 -47
  19. haplohub_cli-2.6.0/haplohub_cli.egg-info/scm_version.json +8 -0
  20. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/pyproject.toml +1 -1
  21. haplohub_cli-2.5.0/haplohub_cli/commands/cohort/sample.py +0 -51
  22. haplohub_cli-2.5.0/haplohub_cli.egg-info/scm_version.json +0 -8
  23. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/.github/dependabot.yml +0 -0
  24. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/.github/workflows/on_pr.yml +0 -0
  25. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/.github/workflows/on_push_main.yml +0 -0
  26. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/.gitignore +0 -0
  27. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/.pre-commit-config.yaml +0 -0
  28. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/README.md +0 -0
  29. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/__init__.py +0 -0
  30. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/__init__.py +0 -0
  31. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/auth.py +0 -0
  32. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/auth_web_server.py +0 -0
  33. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/oauth_client.py +0 -0
  34. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/tests/__init__.py +0 -0
  35. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/tests/test_auth_web_server.py +0 -0
  36. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/tests/test_token_storage.py +0 -0
  37. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/auth/token_storage.py +0 -0
  38. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/cli.py +0 -0
  39. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/__init__.py +0 -0
  40. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/algorithm/__init__.py +0 -0
  41. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/cohort/biomarker.py +0 -0
  42. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/cohort/member/report.py +0 -0
  43. {haplohub_cli-2.5.0/haplohub_cli/config → haplohub_cli-2.6.0/haplohub_cli/commands/cohort/tests}/__init__.py +0 -0
  44. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/config.py +0 -0
  45. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/login.py +0 -0
  46. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/variant.py +0 -0
  47. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/commands/version.py +0 -0
  48. {haplohub_cli-2.5.0/haplohub_cli/core/api → haplohub_cli-2.6.0/haplohub_cli/config}/__init__.py +0 -0
  49. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/config/config.py +0 -0
  50. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/config/config_manager.py +0 -0
  51. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/__init__.py +0 -0
  52. {haplohub_cli-2.5.0/haplohub_cli/core/tests → haplohub_cli-2.6.0/haplohub_cli/core/api}/__init__.py +0 -0
  53. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/checksum.py +0 -0
  54. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/network.py +0 -0
  55. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/slug.py +0 -0
  56. {haplohub_cli-2.5.0/haplohub_cli/types → haplohub_cli-2.6.0/haplohub_cli/core/tests}/__init__.py +0 -0
  57. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/tests/test_checksum.py +0 -0
  58. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/tests/test_network.py +0 -0
  59. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/tests/test_slug.py +0 -0
  60. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/types.py +0 -0
  61. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/core/upload.py +0 -0
  62. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/__init__.py +0 -0
  63. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/algorithm.py +0 -0
  64. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/algorithm_result.py +0 -0
  65. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/biomarker.py +0 -0
  66. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/cohort.py +0 -0
  67. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/config.py +0 -0
  68. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/decorators.py +0 -0
  69. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/file.py +0 -0
  70. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/formatter_registry.py +0 -0
  71. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/metadata.py +0 -0
  72. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/sample.py +0 -0
  73. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/utils.py +0 -0
  74. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/formatters/variant.py +0 -0
  75. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/settings.py +0 -0
  76. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli/types/variant_range.py +0 -0
  77. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/dependency_links.txt +0 -0
  78. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/entry_points.txt +0 -0
  79. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/haplohub_cli.egg-info/top_level.txt +0 -0
  80. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/setup.cfg +0 -0
  81. {haplohub_cli-2.5.0 → haplohub_cli-2.6.0}/uv.lock +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haplohub-cli
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: HaploHub Command Line Interface
5
5
  Author-email: Mike Polcari <mike@haplotype-labs.com>, Ilya Khrustalev <ilya@haplotype-labs.com>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: click>=8.1.8
9
- Requires-Dist: haplohub<3,>=2.2.0
9
+ Requires-Dist: haplohub<3,>=2.16.0
10
10
  Requires-Dist: pendulum>=3.0.0
11
11
  Requires-Dist: requests>=2.32.0
12
12
  Requires-Dist: rich>=13.9.4
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.5.0'
22
- __version_tuple__ = version_tuple = (2, 5, 0)
21
+ __version__ = version = '2.6.0'
22
+ __version_tuple__ = version_tuple = (2, 6, 0)
23
23
 
24
- __commit_id__ = commit_id = 'gcd8a204d2'
24
+ __commit_id__ = commit_id = 'gaebb47593'
@@ -17,10 +17,24 @@ def result():
17
17
 
18
18
 
19
19
  @result.command()
20
- def list(algorithm_version_id: Optional[str] = None, cohort_id: Optional[str] = None):
20
+ @click.option("--algorithm", "-a", type=str, required=False, help="Filter by algorithm name (e.g. pgsc_calc)")
21
+ @click.option("--version", "-v", type=str, required=False, help="Filter by algorithm version")
22
+ @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=False, help="Filter by cohort")
23
+ @click.option("--member", "-m", type=types.HAPLOHUB_ID, required=False, help="Filter by member")
24
+ @click.option("--sample", "-s", type=types.HAPLOHUB_ID, required=False, help="Filter by sample")
25
+ def list(
26
+ algorithm: Optional[str] = None,
27
+ version: Optional[str] = None,
28
+ cohort: Optional[str] = None,
29
+ member: Optional[str] = None,
30
+ sample: Optional[str] = None,
31
+ ):
21
32
  return client.algorithm_result.list_algorithm_results(
22
- algorithm_version_id=algorithm_version_id,
23
- cohort_id=cohort_id,
33
+ algorithm=algorithm,
34
+ version=version,
35
+ cohort_id=cohort,
36
+ member_id=member,
37
+ sample_id=sample,
24
38
  )
25
39
 
26
40
 
@@ -36,7 +36,7 @@ def delete(id):
36
36
 
37
37
  @cohort.command()
38
38
  def accession():
39
- return client.metadata.list_accession()
39
+ return client.accession.list_accession()
40
40
 
41
41
 
42
42
  cohort.add_command(biomarker.biomarker)
@@ -1,8 +1,9 @@
1
1
  import click
2
- from haplohub import CreateMemberRequest, UpdateMemberRequest
2
+ from haplohub import CreateMemberRequest, DeleteMode, UpdateMemberRequest
3
3
 
4
4
  from haplohub_cli.core import types
5
5
  from haplohub_cli.core.api.client import client
6
+ from haplohub_cli.core.deletion import DELETE_MODE_CHOICES, DELETE_MODE_HELP, confirm_hard_delete
6
7
 
7
8
  from . import report
8
9
 
@@ -23,14 +24,14 @@ def list(cohort: str):
23
24
 
24
25
  @member.command()
25
26
  @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
26
- @click.option("--patient-id", "-p", type=str, required=False)
27
+ @click.option("--external-id", "-x", type=str, required=False)
27
28
  @click.option("--first-name", "-fn", type=str, required=False)
28
29
  @click.option("--last-name", "-ln", type=str, required=False)
29
30
  @click.option("--gender", "-g", type=str, required=False)
30
31
  @click.option("--birth-date", "-bd", type=str, required=False)
31
- def create(cohort, patient_id=None, first_name=None, last_name=None, gender=None, birth_date=None):
32
+ def create(cohort, external_id=None, first_name=None, last_name=None, gender=None, birth_date=None):
32
33
  request = CreateMemberRequest(
33
- patient_id=patient_id,
34
+ external_id=external_id,
34
35
  first_name=first_name,
35
36
  last_name=last_name,
36
37
  gender=gender,
@@ -42,21 +43,31 @@ def create(cohort, patient_id=None, first_name=None, last_name=None, gender=None
42
43
  @member.command()
43
44
  @click.argument("id")
44
45
  @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
45
- def delete(id, cohort):
46
- return client.member.delete_member(cohort, id)
46
+ @click.option(
47
+ "--mode",
48
+ "-m",
49
+ type=click.Choice(DELETE_MODE_CHOICES),
50
+ default=DeleteMode.SOFT.value,
51
+ show_default=True,
52
+ help=DELETE_MODE_HELP,
53
+ )
54
+ @click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt for --mode hard.")
55
+ def delete(id, cohort, mode, yes):
56
+ confirm_hard_delete(mode, "member", id, yes)
57
+ return client.member.delete_member(cohort, id, mode=mode)
47
58
 
48
59
 
49
60
  @member.command()
50
61
  @click.argument("id")
51
62
  @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
52
- @click.option("--patient-id", "-p", type=str, required=False)
63
+ @click.option("--external-id", "-x", type=str, required=False)
53
64
  @click.option("--first-name", "-fn", type=str, required=False)
54
65
  @click.option("--last-name", "-ln", type=str, required=False)
55
66
  @click.option("--gender", "-g", type=str, required=False)
56
67
  @click.option("--birth-date", "-bd", type=str, required=False)
57
- def update(id, cohort, patient_id=None, first_name=None, last_name=None, gender=None, birth_date=None):
68
+ def update(id, cohort, external_id=None, first_name=None, last_name=None, gender=None, birth_date=None):
58
69
  request = UpdateMemberRequest(
59
- patient_id=patient_id,
70
+ external_id=external_id,
60
71
  first_name=first_name,
61
72
  last_name=last_name,
62
73
  gender=gender,
@@ -0,0 +1,84 @@
1
+ import click
2
+ from haplohub import CreateSampleRequest, DeleteMode, UpdateSampleRequest
3
+
4
+ from haplohub_cli.core import types
5
+ from haplohub_cli.core.api.client import client
6
+ from haplohub_cli.core.deletion import DELETE_MODE_CHOICES, DELETE_MODE_HELP, confirm_hard_delete
7
+
8
+
9
+ @click.group()
10
+ def sample():
11
+ """
12
+ Manage samples
13
+ """
14
+ pass
15
+
16
+
17
+ @sample.command()
18
+ @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
19
+ def list(cohort: str):
20
+ return client.sample.list_samples(cohort)
21
+
22
+
23
+ @sample.command()
24
+ @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
25
+ @click.option(
26
+ "--external-id",
27
+ "-x",
28
+ type=click.STRING,
29
+ required=False,
30
+ help="Sample identity, uppercase alphanumeric starting with a letter. Generated when omitted.",
31
+ )
32
+ @click.option("--member", "-m", type=types.HAPLOHUB_ID, required=False, help="Member to attach the sample to.")
33
+ @click.option(
34
+ "--member-external-id",
35
+ "-xm",
36
+ type=click.STRING,
37
+ required=False,
38
+ help="Member to attach the sample to, by external id. The member is created when it does not exist.",
39
+ )
40
+ def create(cohort, external_id=None, member=None, member_external_id=None):
41
+ request = CreateSampleRequest(
42
+ external_id=external_id,
43
+ member_id=member,
44
+ member_external_id=member_external_id,
45
+ )
46
+ return client.sample.create_sample(cohort, request)
47
+
48
+
49
+ @sample.command()
50
+ @click.argument("id")
51
+ @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
52
+ @click.option(
53
+ "--mode",
54
+ "-m",
55
+ type=click.Choice(DELETE_MODE_CHOICES),
56
+ default=DeleteMode.SOFT.value,
57
+ show_default=True,
58
+ help=DELETE_MODE_HELP,
59
+ )
60
+ @click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt for --mode hard.")
61
+ def delete(id, cohort, mode, yes):
62
+ confirm_hard_delete(mode, "sample", id, yes)
63
+ return client.sample.delete_sample(cohort, id, mode=mode)
64
+
65
+
66
+ @sample.command()
67
+ @click.argument("id")
68
+ @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
69
+ @click.option("--member", "-m", type=types.HAPLOHUB_ID, required=False, help="Member to attach the sample to.")
70
+ @click.option(
71
+ "--member-external-id",
72
+ "-xm",
73
+ type=click.STRING,
74
+ required=False,
75
+ help="Member to attach the sample to, by external id.",
76
+ )
77
+ def update(id, cohort, member=None, member_external_id=None):
78
+ # external_id is the sample's immutable identity, embedded in the BCFs; only the
79
+ # member association can be changed.
80
+ request = UpdateSampleRequest(
81
+ member_id=member,
82
+ member_external_id=member_external_id,
83
+ )
84
+ return client.sample.update_sample(cohort, id, request)
@@ -0,0 +1,88 @@
1
+ from unittest import TestCase
2
+ from unittest.mock import patch
3
+
4
+ from click.testing import CliRunner
5
+
6
+ from haplohub_cli.commands.cohort.member import member
7
+ from haplohub_cli.commands.cohort.sample import sample
8
+
9
+ COHORT = "coh-abc123"
10
+ MEMBER = "mbr-abc123"
11
+ SAMPLE = "smp-abc123"
12
+
13
+
14
+ class DeleteMemberTestCase(TestCase):
15
+ def setUp(self):
16
+ self.runner = CliRunner()
17
+
18
+ def run_delete(self, *args, input=None):
19
+ with patch("haplohub_cli.commands.cohort.member.client") as client:
20
+ result = self.runner.invoke(member, ["delete", MEMBER, "-c", COHORT, *args], input=input)
21
+ return result, client.member.delete_member
22
+
23
+ def test_defaults_to_soft(self):
24
+ result, delete_member = self.run_delete()
25
+
26
+ self.assertEqual(result.exit_code, 0, result.output)
27
+ delete_member.assert_called_once_with(COHORT, MEMBER, mode="soft")
28
+
29
+ def test_cascade_needs_no_confirmation(self):
30
+ result, delete_member = self.run_delete("--mode", "cascade")
31
+
32
+ self.assertEqual(result.exit_code, 0, result.output)
33
+ delete_member.assert_called_once_with(COHORT, MEMBER, mode="cascade")
34
+
35
+ def test_hard_asks_for_confirmation(self):
36
+ result, delete_member = self.run_delete("--mode", "hard", input="y\n")
37
+
38
+ self.assertEqual(result.exit_code, 0, result.output)
39
+ self.assertIn("cannot be undone", result.output)
40
+ delete_member.assert_called_once_with(COHORT, MEMBER, mode="hard")
41
+
42
+ def test_hard_aborts_when_declined(self):
43
+ result, delete_member = self.run_delete("--mode", "hard", input="n\n")
44
+
45
+ self.assertEqual(result.exit_code, 1)
46
+ delete_member.assert_not_called()
47
+
48
+ def test_hard_skips_confirmation_with_yes(self):
49
+ result, delete_member = self.run_delete("--mode", "hard", "--yes")
50
+
51
+ self.assertEqual(result.exit_code, 0, result.output)
52
+ self.assertNotIn("cannot be undone", result.output)
53
+ delete_member.assert_called_once_with(COHORT, MEMBER, mode="hard")
54
+
55
+ def test_rejects_an_unknown_mode(self):
56
+ result, delete_member = self.run_delete("--mode", "nuke")
57
+
58
+ self.assertEqual(result.exit_code, 2)
59
+ delete_member.assert_not_called()
60
+
61
+
62
+ class DeleteSampleTestCase(TestCase):
63
+ def setUp(self):
64
+ self.runner = CliRunner()
65
+
66
+ def run_delete(self, *args, input=None):
67
+ with patch("haplohub_cli.commands.cohort.sample.client") as client:
68
+ result = self.runner.invoke(sample, ["delete", SAMPLE, "-c", COHORT, *args], input=input)
69
+ return result, client.sample.delete_sample
70
+
71
+ def test_defaults_to_soft(self):
72
+ result, delete_sample = self.run_delete()
73
+
74
+ self.assertEqual(result.exit_code, 0, result.output)
75
+ delete_sample.assert_called_once_with(COHORT, SAMPLE, mode="soft")
76
+
77
+ def test_hard_asks_for_confirmation(self):
78
+ result, delete_sample = self.run_delete("--mode", "hard", input="y\n")
79
+
80
+ self.assertEqual(result.exit_code, 0, result.output)
81
+ self.assertIn("cannot be undone", result.output)
82
+ delete_sample.assert_called_once_with(COHORT, SAMPLE, mode="hard")
83
+
84
+ def test_hard_aborts_when_declined(self):
85
+ result, delete_sample = self.run_delete("--mode", "hard", input="n\n")
86
+
87
+ self.assertEqual(result.exit_code, 1)
88
+ delete_sample.assert_not_called()
@@ -63,6 +63,21 @@ def download(cohort: str, file_id: str = None, file_path: str = None, output: st
63
63
  click.echo(f"File saved to {output}")
64
64
 
65
65
 
66
+ def _resolve_file_id(cohort: str, file_path: str) -> str:
67
+ """Look a file up by its location.
68
+
69
+ The API deletes by id only - unlike downloads, which have a by-path endpoint - so
70
+ the path is resolved here to keep --file-path working on both commands.
71
+ """
72
+ listing = client.file.list_files(cohort, recursive=True)
73
+ wanted = file_path.lstrip("/")
74
+ for entry in listing.result.files:
75
+ if entry.location == wanted:
76
+ return entry.id
77
+
78
+ raise click.ClickException(f"No file at '{file_path}' in cohort {cohort}.")
79
+
80
+
66
81
  @file.command()
67
82
  @click.option("--cohort", "-c", type=click.STRING, required=True)
68
83
  @click.option("--file-id", "-i", type=click.STRING, required=False)
@@ -74,10 +89,10 @@ def delete(cohort: str, file_id: str = None, file_path: str = None):
74
89
  if not file_id and not file_path:
75
90
  raise click.UsageError("Must specify either --file-id or --file-path")
76
91
 
77
- if file_id:
78
- return client.file.delete_file(cohort, file_id)
92
+ if not file_id:
93
+ file_id = _resolve_file_id(cohort, file_path)
79
94
 
80
- return client.file.delete_file_by_path(cohort, file_path)
95
+ return client.file.delete_file(cohort, file_id)
81
96
 
82
97
 
83
98
  @file.command()
@@ -13,4 +13,4 @@ def metadata():
13
13
 
14
14
  @metadata.command()
15
15
  def accession():
16
- return client.metadata.list_accession()
16
+ return client.accession.list_accession()
@@ -2,6 +2,7 @@ from functools import cached_property
2
2
 
3
3
  import click
4
4
  from haplohub import (
5
+ AccessionApi,
5
6
  AlgorithmApi,
6
7
  AlgorithmResultApi,
7
8
  ApiClient,
@@ -83,5 +84,9 @@ class Client:
83
84
  def algorithm_result(self):
84
85
  return AlgorithmResultApi(self.client)
85
86
 
87
+ @cached_property
88
+ def accession(self):
89
+ return AccessionApi(self.client)
90
+
86
91
 
87
92
  client = Client()
@@ -0,0 +1,28 @@
1
+ import click
2
+ from haplohub import DeleteMode
3
+
4
+ # The generated client drops `mode` straight into the query string, so the commands pass
5
+ # the plain value rather than the enum member.
6
+ DELETE_MODE_CHOICES = [mode.value for mode in DeleteMode]
7
+
8
+ DELETE_MODE_HELP = (
9
+ "How far the delete reaches. "
10
+ "soft: the record only. "
11
+ "cascade: the record plus everything derived from it, files included; recoverable. "
12
+ "hard: irreversible, erases the records permanently and removes every version of their files."
13
+ )
14
+
15
+
16
+ def confirm_hard_delete(mode: str, entity: str, identifier: str, assume_yes: bool) -> None:
17
+ """Guard `--mode hard`, which cannot be undone.
18
+
19
+ Softer modes leave the rows recoverable and the blobs restorable, so they go through
20
+ without a prompt.
21
+ """
22
+ if mode != DeleteMode.HARD.value or assume_yes:
23
+ return
24
+
25
+ click.confirm(
26
+ f"Permanently erase {entity} {identifier} and every version of its files? This cannot be undone.",
27
+ abort=True,
28
+ )
@@ -1,6 +1,7 @@
1
1
  from haplohub import (
2
2
  ErrorResponse,
3
- ResultResponse,
3
+ GenericResponse,
4
+ SuccessResponse,
4
5
  )
5
6
  from rich.text import Text
6
7
 
@@ -12,8 +13,13 @@ def format_error_response(data: ErrorResponse):
12
13
  return Text(f"Error [{data.error.code}]: {data.error.message}", style="red")
13
14
 
14
15
 
15
- @register(ResultResponse)
16
- def format_result_response(data: ResultResponse):
16
+ # The bare ResultResponse this used to render is gone from the SDK: the API returns
17
+ # generic ResultResponse[T] wrappers, which generate as concrete per-payload classes.
18
+ # SuccessResponse is what the operations carrying no payload of their own return,
19
+ # deletes among them.
20
+ @register(SuccessResponse)
21
+ @register(GenericResponse)
22
+ def format_success_response(data):
17
23
  if data.status == "success":
18
24
  return Text("Result: Operation successful", style="green")
19
25
  else:
@@ -13,7 +13,7 @@ from haplohub_cli.formatters.decorators import register
13
13
  def format_member(data: MemberSchema):
14
14
  table = Table(title="Member", caption=f"Id: {utils.format_id(data.id)}")
15
15
  table.add_column("Id")
16
- table.add_column("Patient ID")
16
+ table.add_column("External ID")
17
17
  table.add_column("First Name")
18
18
  table.add_column("Last Name")
19
19
  table.add_column("Gender")
@@ -21,7 +21,7 @@ def format_member(data: MemberSchema):
21
21
  table.add_column("Created")
22
22
  table.add_row(
23
23
  utils.format_id(data.id),
24
- data.patient_id,
24
+ data.external_id,
25
25
  data.first_name,
26
26
  data.last_name,
27
27
  data.gender,
@@ -35,7 +35,7 @@ def format_member(data: MemberSchema):
35
35
  def format_members(data: PaginatedResponseMemberSchema):
36
36
  table = Table(title="Members", caption=f"Total: {data.total_count}")
37
37
  table.add_column("Id")
38
- table.add_column("Patient ID")
38
+ table.add_column("External ID")
39
39
  table.add_column("First Name")
40
40
  table.add_column("Last Name")
41
41
  table.add_column("Gender")
@@ -45,7 +45,7 @@ def format_members(data: PaginatedResponseMemberSchema):
45
45
  for item in data.items:
46
46
  table.add_row(
47
47
  utils.format_id(item.id),
48
- item.patient_id,
48
+ item.external_id,
49
49
  item.first_name,
50
50
  item.last_name,
51
51
  item.gender,
File without changes
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haplohub-cli
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: HaploHub Command Line Interface
5
5
  Author-email: Mike Polcari <mike@haplotype-labs.com>, Ilya Khrustalev <ilya@haplotype-labs.com>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: click>=8.1.8
9
- Requires-Dist: haplohub<3,>=2.2.0
9
+ Requires-Dist: haplohub<3,>=2.16.0
10
10
  Requires-Dist: pendulum>=3.0.0
11
11
  Requires-Dist: requests>=2.32.0
12
12
  Requires-Dist: rich>=13.9.4
@@ -39,12 +39,15 @@ haplohub_cli/commands/cohort/biomarker.py
39
39
  haplohub_cli/commands/cohort/sample.py
40
40
  haplohub_cli/commands/cohort/member/__init__.py
41
41
  haplohub_cli/commands/cohort/member/report.py
42
+ haplohub_cli/commands/cohort/tests/__init__.py
43
+ haplohub_cli/commands/cohort/tests/test_delete.py
42
44
  haplohub_cli/commands/metadata/__init__.py
43
45
  haplohub_cli/config/__init__.py
44
46
  haplohub_cli/config/config.py
45
47
  haplohub_cli/config/config_manager.py
46
48
  haplohub_cli/core/__init__.py
47
49
  haplohub_cli/core/checksum.py
50
+ haplohub_cli/core/deletion.py
48
51
  haplohub_cli/core/network.py
49
52
  haplohub_cli/core/slug.py
50
53
  haplohub_cli/core/types.py
@@ -1,5 +1,5 @@
1
1
  click>=8.1.8
2
- haplohub<3,>=2.2.0
2
+ haplohub<3,>=2.16.0
3
3
  pendulum>=3.0.0
4
4
  requests>=2.32.0
5
5
  rich>=13.9.4
@@ -1,69 +1,72 @@
1
1
  {
2
2
  "files": [
3
3
  ".pre-commit-config.yaml",
4
+ "pyproject.toml",
4
5
  "README.md",
5
6
  "uv.lock",
6
- "pyproject.toml",
7
7
  ".gitignore",
8
- "haplohub_cli/__init__.py",
8
+ ".github/dependabot.yml",
9
+ ".github/workflows/on_push_main.yml",
10
+ ".github/workflows/on_pr.yml",
9
11
  "haplohub_cli/settings.py",
12
+ "haplohub_cli/__init__.py",
10
13
  "haplohub_cli/cli.py",
14
+ "haplohub_cli/types/variant_range.py",
15
+ "haplohub_cli/types/__init__.py",
16
+ "haplohub_cli/formatters/cohort.py",
17
+ "haplohub_cli/formatters/generic.py",
18
+ "haplohub_cli/formatters/config.py",
19
+ "haplohub_cli/formatters/member.py",
20
+ "haplohub_cli/formatters/algorithm.py",
21
+ "haplohub_cli/formatters/formatter_registry.py",
22
+ "haplohub_cli/formatters/__init__.py",
23
+ "haplohub_cli/formatters/variant.py",
24
+ "haplohub_cli/formatters/algorithm_result.py",
25
+ "haplohub_cli/formatters/biomarker.py",
26
+ "haplohub_cli/formatters/metadata.py",
27
+ "haplohub_cli/formatters/sample.py",
28
+ "haplohub_cli/formatters/decorators.py",
29
+ "haplohub_cli/formatters/file.py",
30
+ "haplohub_cli/formatters/utils.py",
31
+ "haplohub_cli/commands/login.py",
32
+ "haplohub_cli/commands/config.py",
11
33
  "haplohub_cli/commands/__init__.py",
34
+ "haplohub_cli/commands/variant.py",
12
35
  "haplohub_cli/commands/version.py",
13
- "haplohub_cli/commands/config.py",
14
36
  "haplohub_cli/commands/file.py",
15
- "haplohub_cli/commands/variant.py",
16
- "haplohub_cli/commands/login.py",
17
- "haplohub_cli/commands/cohort/biomarker.py",
18
- "haplohub_cli/commands/cohort/__init__.py",
19
- "haplohub_cli/commands/cohort/sample.py",
20
- "haplohub_cli/commands/cohort/member/__init__.py",
21
- "haplohub_cli/commands/cohort/member/report.py",
22
37
  "haplohub_cli/commands/metadata/__init__.py",
23
38
  "haplohub_cli/commands/algorithm/__init__.py",
24
39
  "haplohub_cli/commands/algorithm/result/__init__.py",
25
- "haplohub_cli/core/network.py",
26
- "haplohub_cli/core/__init__.py",
27
- "haplohub_cli/core/checksum.py",
28
- "haplohub_cli/core/slug.py",
29
- "haplohub_cli/core/upload.py",
30
- "haplohub_cli/core/types.py",
31
- "haplohub_cli/core/api/__init__.py",
32
- "haplohub_cli/core/api/client.py",
33
- "haplohub_cli/core/tests/test_checksum.py",
34
- "haplohub_cli/core/tests/__init__.py",
35
- "haplohub_cli/core/tests/test_slug.py",
36
- "haplohub_cli/core/tests/test_network.py",
37
- "haplohub_cli/auth/__init__.py",
40
+ "haplohub_cli/commands/cohort/__init__.py",
41
+ "haplohub_cli/commands/cohort/biomarker.py",
42
+ "haplohub_cli/commands/cohort/sample.py",
43
+ "haplohub_cli/commands/cohort/tests/test_delete.py",
44
+ "haplohub_cli/commands/cohort/tests/__init__.py",
45
+ "haplohub_cli/commands/cohort/member/report.py",
46
+ "haplohub_cli/commands/cohort/member/__init__.py",
47
+ "haplohub_cli/auth/auth_web_server.py",
38
48
  "haplohub_cli/auth/token_storage.py",
39
49
  "haplohub_cli/auth/auth.py",
50
+ "haplohub_cli/auth/__init__.py",
40
51
  "haplohub_cli/auth/oauth_client.py",
41
- "haplohub_cli/auth/auth_web_server.py",
42
- "haplohub_cli/auth/tests/test_token_storage.py",
43
- "haplohub_cli/auth/tests/__init__.py",
44
52
  "haplohub_cli/auth/tests/test_auth_web_server.py",
53
+ "haplohub_cli/auth/tests/__init__.py",
54
+ "haplohub_cli/auth/tests/test_token_storage.py",
55
+ "haplohub_cli/config/config.py",
45
56
  "haplohub_cli/config/__init__.py",
46
57
  "haplohub_cli/config/config_manager.py",
47
- "haplohub_cli/config/config.py",
48
- "haplohub_cli/formatters/biomarker.py",
49
- "haplohub_cli/formatters/__init__.py",
50
- "haplohub_cli/formatters/algorithm.py",
51
- "haplohub_cli/formatters/config.py",
52
- "haplohub_cli/formatters/file.py",
53
- "haplohub_cli/formatters/utils.py",
54
- "haplohub_cli/formatters/member.py",
55
- "haplohub_cli/formatters/cohort.py",
56
- "haplohub_cli/formatters/decorators.py",
57
- "haplohub_cli/formatters/formatter_registry.py",
58
- "haplohub_cli/formatters/metadata.py",
59
- "haplohub_cli/formatters/variant.py",
60
- "haplohub_cli/formatters/algorithm_result.py",
61
- "haplohub_cli/formatters/sample.py",
62
- "haplohub_cli/formatters/generic.py",
63
- "haplohub_cli/types/__init__.py",
64
- "haplohub_cli/types/variant_range.py",
65
- ".github/dependabot.yml",
66
- ".github/workflows/on_pr.yml",
67
- ".github/workflows/on_push_main.yml"
58
+ "haplohub_cli/core/types.py",
59
+ "haplohub_cli/core/network.py",
60
+ "haplohub_cli/core/deletion.py",
61
+ "haplohub_cli/core/checksum.py",
62
+ "haplohub_cli/core/__init__.py",
63
+ "haplohub_cli/core/slug.py",
64
+ "haplohub_cli/core/upload.py",
65
+ "haplohub_cli/core/tests/__init__.py",
66
+ "haplohub_cli/core/tests/test_checksum.py",
67
+ "haplohub_cli/core/tests/test_network.py",
68
+ "haplohub_cli/core/tests/test_slug.py",
69
+ "haplohub_cli/core/api/__init__.py",
70
+ "haplohub_cli/core/api/client.py"
68
71
  ]
69
72
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.6.0",
3
+ "distance": 0,
4
+ "node": "gaebb47593fb0fda18b4541e27a7acec06b67aeab",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-04"
8
+ }
@@ -10,7 +10,7 @@ requires-python = ">=3.8"
10
10
  dynamic = ["version"]
11
11
  dependencies = [
12
12
  "click>=8.1.8",
13
- "haplohub>=2.2.0,<3",
13
+ "haplohub>=2.16.0,<3",
14
14
  "pendulum>=3.0.0",
15
15
  "requests>=2.32.0",
16
16
  "rich>=13.9.4",
@@ -1,51 +0,0 @@
1
- import click
2
- from haplohub import CreateSampleRequest, UpdateSampleRequest
3
-
4
- from haplohub_cli.core import types
5
- from haplohub_cli.core.api.client import client
6
-
7
-
8
- @click.group()
9
- def sample():
10
- """
11
- Manage samples
12
- """
13
- pass
14
-
15
-
16
- @sample.command()
17
- @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
18
- def list(cohort: str):
19
- return client.sample.list_samples(cohort)
20
-
21
-
22
- @sample.command()
23
- @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
24
- @click.option("--patient-id", "-p", type=click.STRING, required=False)
25
- @click.option("--sample-id", "-s", type=click.STRING, required=False)
26
- def create(cohort, sample_id=None, patient_id=None):
27
- request = CreateSampleRequest(
28
- sample_id=sample_id,
29
- patient_id=patient_id,
30
- )
31
- return client.sample.create_sample(cohort, request)
32
-
33
-
34
- @sample.command()
35
- @click.argument("id")
36
- @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
37
- def delete(id, cohort):
38
- return client.sample.delete_sample(cohort, id)
39
-
40
-
41
- @sample.command()
42
- @click.argument("id")
43
- @click.option("--cohort", "-c", type=types.HAPLOHUB_ID, required=True)
44
- @click.option("--patient-id", "-p", type=click.STRING, required=False)
45
- @click.option("--sample-id", "-s", type=click.STRING, required=False)
46
- def update(id, cohort, patient_id=None, sample_id=None):
47
- request = UpdateSampleRequest(
48
- patient_id=patient_id,
49
- sample_id=sample_id,
50
- )
51
- return client.sample.update_sample(cohort, id, request)
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.5.0",
3
- "distance": 0,
4
- "node": "gcd8a204d2e893b5eed8f318fb0e8fb3ed1a25d32",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-14"
8
- }
File without changes
File without changes
File without changes
File without changes