grz-cli 1.5.1__tar.gz → 1.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.
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.0](https://github.com/BfArM-MVH/grz-tools/compare/grz-cli-v1.5.1...grz-cli-v1.6.0) (2026-05-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **grz-cli,grz-common:** Add version checks to upload/submit ([#477](https://github.com/BfArM-MVH/grz-tools/issues/477)) ([289683d](https://github.com/BfArM-MVH/grz-tools/commit/289683d6154df7fb175ad4db9671f28e1cdd0eb3))
9
+ * **grz-cli,grzctl,grz-common,grz-check:** use grz check python bindings ([#554](https://github.com/BfArM-MVH/grz-tools/issues/554)) ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
10
+ * **grz-cli,grzctl,grz-common:** use grz-check python bindings instead ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
11
+ * **grzctl,grz-cli,grz-common:** Merge multiple configuration files ([#490](https://github.com/BfArM-MVH/grz-tools/issues/490)) ([8ae881d](https://github.com/BfArM-MVH/grz-tools/commit/8ae881d4f65882ae5d8823bdb00971eec246a395))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **grz-check:** actually calculate checksums in python exposed API ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
17
+ * **grz-check:** add return types to public python functions ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
18
+ * **grz-check:** do not silently ignore pair errors ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
19
+ * **grz-check:** expose validate_raw* functions that also return a ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
20
+ * **grz-check:** set source.name as path in report as a fallback ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
21
+ * **grz-check:** use 8MB as buffer size to match python codebase defaults ([5dce167](https://github.com/BfArM-MVH/grz-tools/commit/5dce16733e549e92de546f298d821f6202bcee9f))
22
+ * **grz-common,grzctl,grz-cli:** add pynacl to dependencies, update crypt4gh ([#560](https://github.com/BfArM-MVH/grz-tools/issues/560)) ([9060876](https://github.com/BfArM-MVH/grz-tools/commit/9060876094d9f2a97ea7d351e0adcfc34a6bbd39))
23
+ * **grz-common,grzctl,grz-cli:** update crypt4gh dependency version ([9060876](https://github.com/BfArM-MVH/grz-tools/commit/9060876094d9f2a97ea7d351e0adcfc34a6bbd39))
24
+ * **grz-common:** add pynacl to dependencies ([9060876](https://github.com/BfArM-MVH/grz-tools/commit/9060876094d9f2a97ea7d351e0adcfc34a6bbd39))
25
+ * **repo:** Update dependencies ([#498](https://github.com/BfArM-MVH/grz-tools/issues/498)) ([368dfdb](https://github.com/BfArM-MVH/grz-tools/commit/368dfdbaa703f17f0c290ea051be30f9be4bebf3))
26
+
3
27
  ## [1.5.1](https://github.com/BfArM-MVH/grz-tools/compare/grz-cli-v1.5.0...grz-cli-v1.5.1) (2025-12-04)
4
28
 
5
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grz-cli
3
- Version: 1.5.1
3
+ Version: 1.6.0
4
4
  Summary: Tool for validation, encryption and upload of MV submissions to GDCs.
5
5
  Project-URL: Homepage, https://github.com/BfArM-MVH/grz-tools
6
6
  Project-URL: Repository, https://github.com/BfArM-MVH/grz-tools
@@ -15,14 +15,13 @@ Classifier: Programming Language :: Python :: 3
15
15
  Requires-Python: >=3.12
16
16
  Requires-Dist: boto3<2,>=1.36
17
17
  Requires-Dist: click<9,>=8.2
18
- Requires-Dist: crypt4gh<2,>=1.7
18
+ Requires-Dist: crypt4gh<2,>=1.8.6
19
19
  Requires-Dist: grz-common<2,>=1.5.0
20
20
  Requires-Dist: grz-pydantic-models<3,>=2.5
21
21
  Requires-Dist: jsonschema<5,>=4.23.0
22
22
  Requires-Dist: platformdirs<5,>=4.3.6
23
23
  Requires-Dist: pydantic-settings<3,>=2.11
24
24
  Requires-Dist: pydantic<3,>=2.12
25
- Requires-Dist: pysam==0.23.*
26
25
  Requires-Dist: pyyaml<7,>=6.0.2
27
26
  Requires-Dist: requests<3,>=2.32.3
28
27
  Requires-Dist: rich==13.*
@@ -15,7 +15,7 @@ dynamic = ["version"]
15
15
  dependencies = [
16
16
  "boto3 >=1.36,<2",
17
17
  "click >=8.2,<9",
18
- "crypt4gh >=1.7,<2",
18
+ "crypt4gh >=1.8.6,<2",
19
19
  "jsonschema >=4.23.0,<5",
20
20
  "pyyaml >=6.0.2,<7",
21
21
  "tqdm >=4.66.5,<5",
@@ -23,7 +23,6 @@ dependencies = [
23
23
  "pydantic-settings >=2.11,<3",
24
24
  "platformdirs >=4.3.6,<5",
25
25
  "grz-pydantic-models >=2.5,<3",
26
- "pysam ==0.23.*",
27
26
  "rich ==13.*",
28
27
  "requests >=2.32.3,<3",
29
28
  "grz-common >=1.5.0,<2",
@@ -38,9 +37,7 @@ keywords = ["GRZ", "GDC", "S3"]
38
37
  [dependency-groups]
39
38
  dev = ["tox", "tox-uv"]
40
39
 
41
- [tool.uv.sources]
42
- grz-common = { path = "../grz-common", editable = true }
43
- grz-pydantic-models = { path = "../grz-pydantic-models", editable = true }
40
+
44
41
 
45
42
  [project.urls]
46
43
  Homepage = "https://github.com/BfArM-MVH/grz-tools"
@@ -0,0 +1 @@
1
+ __version__ = "1.6.0"
@@ -2,16 +2,18 @@
2
2
  CLI module for handling command-line interface operations.
3
3
  """
4
4
 
5
- import logging
6
5
  import logging.config
7
6
  import shutil
8
7
  import subprocess
9
8
  import sys
10
9
  from importlib.metadata import version
10
+ from pathlib import Path
11
11
  from textwrap import dedent
12
12
 
13
13
  import click
14
14
  import grz_pydantic_models.submission.metadata
15
+ from grz_common.cli import config_file
16
+ from grz_common.cli.dump_config import dump_config
15
17
  from grz_common.logging import setup_cli_logging
16
18
 
17
19
  from .commands.encrypt import encrypt
@@ -52,7 +54,7 @@ def build_cli():
52
54
  grz-pydantic-models v{version("grz-pydantic-models")}
53
55
  """)
54
56
  + (
55
- subprocess.run(["grz-check", "--version"], capture_output=True, text=True).stdout.strip() # noqa: S603, S607
57
+ subprocess.run(["grz-check", "--version"], capture_output=True, text=True).stdout.strip() # noqa: S607
56
58
  if shutil.which("grz-check") is not None
57
59
  else ""
58
60
  ),
@@ -64,7 +66,9 @@ def build_cli():
64
66
  type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
65
67
  help="Set the log level (default: INFO)",
66
68
  )
67
- def cli(log_file: str | None = None, log_level: str = "INFO"):
69
+ @config_file
70
+ @click.pass_context
71
+ def cli(ctx, config_file: tuple[Path], log_file: str | None = None, log_level: str = "INFO"):
68
72
  """
69
73
  Command-line interface function for setting up logging.
70
74
 
@@ -81,6 +85,7 @@ def build_cli():
81
85
  cli.add_command(upload)
82
86
  cli.add_command(submit)
83
87
  cli.add_command(get_id)
88
+ cli.add_command(dump_config)
84
89
 
85
90
  return cli
86
91
 
@@ -4,9 +4,10 @@ import logging
4
4
  import sys
5
5
  from pathlib import Path
6
6
  from tempfile import NamedTemporaryFile
7
+ from typing import Any
7
8
 
8
9
  import click
9
- from grz_common.cli import config_file, force, submission_dir
10
+ import grz_common.cli as grzcli
10
11
  from grz_common.workers.worker import Worker
11
12
 
12
13
  from ..models.config import EncryptConfig
@@ -15,23 +16,23 @@ log = logging.getLogger(__name__)
15
16
 
16
17
 
17
18
  @click.command()
18
- @submission_dir
19
- @config_file
20
- @force
19
+ @grzcli.configuration
20
+ @grzcli.submission_dir
21
+ @grzcli.force
21
22
  @click.option(
22
23
  "--check-validation-logs/--no-check-validation-logs",
23
24
  "check_validation_logs",
24
25
  default=True,
25
26
  help="Check validation logs before encrypting.",
26
27
  )
27
- def encrypt(submission_dir, config_file, force, check_validation_logs):
28
+ def encrypt(configuration: dict[str, Any], submission_dir, force, check_validation_logs, **kwargs):
28
29
  """
29
30
  Encrypt a submission.
30
31
 
31
32
  Encryption is done with the recipient's public key.
32
33
  Sub-folders 'encrypted_files' and 'logs' are created within the submission directory.
33
34
  """
34
- config = EncryptConfig.from_path(config_file)
35
+ config = EncryptConfig.model_validate(configuration)
35
36
 
36
37
  submitter_privkey_path = config.keys.submitter_private_key_path
37
38
  if submitter_privkey_path == "":
@@ -3,6 +3,7 @@
3
3
  import logging
4
4
 
5
5
  import click
6
+ from grz_cli.utils.version_check import check_version_and_exit_if_needed
6
7
 
7
8
  from .encrypt import encrypt
8
9
  from .upload import upload
@@ -10,14 +11,16 @@ from .validate import validate
10
11
 
11
12
  log = logging.getLogger(__name__)
12
13
 
13
- from grz_common.cli import config_file, force, submission_dir, threads
14
+ import grz_common.cli as grzcli
15
+
16
+ from ..models.config import UploadConfig
14
17
 
15
18
 
16
19
  @click.command("submit")
17
- @submission_dir
18
- @config_file
19
- @threads
20
- @force
20
+ @grzcli.submission_dir
21
+ @grzcli.config_file
22
+ @grzcli.threads
23
+ @grzcli.force
21
24
  @click.pass_context
22
25
  def submit(ctx, submission_dir, config_file, threads, force):
23
26
  """
@@ -28,6 +31,9 @@ def submit(ctx, submission_dir, config_file, threads, force):
28
31
  2. Encrypt the submission
29
32
  3. Upload the encrypted submission
30
33
  """
34
+ config = UploadConfig.from_path(config_file)
35
+ check_version_and_exit_if_needed(config.s3)
36
+
31
37
  click.echo("Starting submission process...")
32
38
  ctx.invoke(validate, submission_dir=submission_dir, config_file=config_file, force=force)
33
39
  ctx.invoke(encrypt, submission_dir=submission_dir, config_file=config_file, force=force, check_validation_logs=True)
@@ -2,30 +2,34 @@
2
2
 
3
3
  import logging
4
4
  from pathlib import Path
5
+ from typing import Any
5
6
 
6
7
  import click
7
8
  from grz_common.workers.worker import Worker
8
9
 
9
10
  log = logging.getLogger(__name__)
10
11
 
11
- from grz_common.cli import config_file, submission_dir, threads
12
+ import grz_common.cli as grzcli
13
+ from grz_cli.utils.version_check import check_version_and_exit_if_needed
12
14
 
13
15
  from ..models.config import UploadConfig
14
16
 
15
17
 
16
18
  @click.command()
17
- @submission_dir
18
- @config_file
19
- @threads
19
+ @grzcli.submission_dir
20
+ @grzcli.threads
21
+ @grzcli.configuration
20
22
  def upload(
23
+ configuration: dict[str, Any],
21
24
  submission_dir,
22
- config_file,
23
25
  threads,
26
+ **kwargs,
24
27
  ):
25
28
  """
26
29
  Upload a submission to a GRZ/GDC.
27
30
  """
28
- config = UploadConfig.from_path(config_file)
31
+ config = UploadConfig.model_validate(configuration)
32
+ check_version_and_exit_if_needed(config.s3)
29
33
 
30
34
  log.info("Starting upload...")
31
35
 
@@ -2,35 +2,43 @@
2
2
 
3
3
  import logging
4
4
  from pathlib import Path
5
+ from typing import Any
5
6
 
6
7
  import click
8
+ import grz_common.cli as grzcli
7
9
  from grz_cli.models.config import ValidateConfig
8
- from grz_common.cli import config_file, force, submission_dir, threads
9
10
  from grz_common.workers.worker import Worker
10
11
 
11
12
  log = logging.getLogger(__name__)
12
13
 
13
14
 
14
15
  @click.command()
15
- @submission_dir
16
- @config_file
17
- @force
18
- @threads
16
+ @grzcli.configuration
17
+ @grzcli.submission_dir
18
+ @grzcli.force
19
+ @grzcli.threads
19
20
  @click.option(
20
- "--with-grz-check/--no-grz-check",
21
- "with_grz_check",
21
+ "--mmap/--no-mmap",
22
+ "mmap",
22
23
  default=True,
23
24
  hidden=True,
24
- help="Whether to use grz-check to perform validation",
25
+ help="Whether to use mmap.",
25
26
  )
26
- def validate(submission_dir, config_file, force, threads, with_grz_check):
27
+ def validate(
28
+ configuration: dict[str, Any],
29
+ submission_dir,
30
+ force,
31
+ threads,
32
+ mmap,
33
+ **kwargs,
34
+ ):
27
35
  """
28
36
  Validate the submission.
29
37
 
30
38
  This validates the submission by checking its checksums, as well as performing basic sanity checks on the supplied metadata.
31
39
  Must be executed before calling `encrypt` and `upload`.
32
40
  """
33
- config = ValidateConfig.from_path(config_file)
41
+ config = ValidateConfig.model_validate(configuration)
34
42
 
35
43
  log.info("Starting validation...")
36
44
 
@@ -43,6 +51,6 @@ def validate(submission_dir, config_file, force, threads, with_grz_check):
43
51
  encrypted_files_dir=submission_dir / "encrypted_files",
44
52
  threads=threads,
45
53
  )
46
- worker_inst.validate(identifiers=config.identifiers, force=force, with_grz_check=with_grz_check)
54
+ worker_inst.validate(identifiers=config.identifiers, force=force, no_mmap=not mmap)
47
55
 
48
56
  log.info("Validation finished!")
@@ -0,0 +1,89 @@
1
+ import logging
2
+ import sys
3
+ from datetime import UTC, datetime
4
+ from importlib.metadata import version
5
+
6
+ from grz_common.models.s3 import S3Options
7
+ from grz_common.models.version import VersionFile, VersionInfo
8
+ from packaging import version as pkg_version
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _select_active_policy(
14
+ policies: list[VersionInfo],
15
+ now: datetime,
16
+ ) -> VersionInfo | None:
17
+ """Select the version policy that is active for the given datetime.
18
+
19
+ :param policies: A non-empty list of available version policies.
20
+ Each policy defines version constraints and an
21
+ enforcement start datetime.
22
+ :param now: The reference datetime used to determine which policy
23
+ is currently active (typically ``datetime.now(UTC)``).
24
+ :returns: The policy that should be applied for the given datetime.
25
+ :raises ValueError: If policies is empty.
26
+ """
27
+ if not policies:
28
+ return None
29
+
30
+ applicable = [p for p in policies if p.enforced_from <= now]
31
+
32
+ if not applicable:
33
+ return None
34
+
35
+ return max(applicable, key=lambda p: p.enforced_from)
36
+
37
+
38
+ def check_version_and_exit_if_needed(
39
+ s3_options: S3Options,
40
+ version_file_key: str = "version.json",
41
+ ) -> None:
42
+ """Validate the installed grz-cli version against the policy defined in version.json."""
43
+ version_file = VersionFile.from_s3(s3_options, version_file_key)
44
+
45
+ current_version = pkg_version.Version(version("grz-cli"))
46
+ now = datetime.now(UTC)
47
+
48
+ policy = _select_active_policy(version_file.grzcli_version, now)
49
+
50
+ if policy is None:
51
+ logger.debug("No active version policy found — skipping version check.")
52
+ return
53
+
54
+ minimal_version = policy.minimal_version
55
+ recommended_version = policy.recommended_version
56
+ max_version = policy.max_version
57
+ enforced_from = policy.enforced_from
58
+
59
+ logger.debug(f"Current grz-cli version: {current_version}")
60
+ logger.debug(
61
+ f"Active policy: "
62
+ f"minimal={minimal_version}, "
63
+ f"recommended={recommended_version}, "
64
+ f"max_version={max_version}, "
65
+ f"enforced_from={enforced_from}"
66
+ )
67
+
68
+ # old version
69
+ if current_version < minimal_version:
70
+ logger.error(
71
+ f"Your grz-cli version ({current_version}) is not supported. Minimum required version is {minimal_version}."
72
+ )
73
+ sys.exit(1)
74
+
75
+ # supported but behind recommended — skip if recommended_version not set
76
+ if recommended_version is not None and minimal_version <= current_version < recommended_version:
77
+ logger.warning(
78
+ f"You are using grz-cli {current_version}, but the recommended version is "
79
+ f"{recommended_version}. Upgrading is strongly recommended."
80
+ )
81
+ return
82
+
83
+ # too new — skip if max_version not set
84
+ if max_version is not None and current_version > max_version:
85
+ logger.error(f"grz-cli version {current_version} is newer than the maximum supported version ({max_version}).")
86
+ sys.exit(1)
87
+
88
+ # best case
89
+ logger.info(f"grz-cli {current_version} is within the supported and tested range.")
@@ -1 +0,0 @@
1
- __version__ = "1.5.1"