Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save more provenance information #308

Merged
merged 10 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
70 changes: 44 additions & 26 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ boutiques = "^0.5.25"
more-itertools = ">=8,<10"
cached-property = "^1.5.2"
pvandyken-deprecated = "0.0.3"
importlib_metadata = [ { version = "==1.4", python = "<3.8" } ]

# Below are non-direct dependencies (i.e. dependencies of other depenencies)
# specified to ensure a version with a pre-built wheel is installed depending
Expand Down
37 changes: 36 additions & 1 deletion snakebids/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
import snakemake
from snakemake.io import load_configfile

if sys.version_info >= (3, 8):
from importlib import metadata
else:
import importlib_metadata as metadata

from snakebids.cli import (
SnakebidsArgs,
add_dynamic_args,
Expand Down Expand Up @@ -68,6 +73,30 @@ def wrapper(self: "SnakeBidsApp"):
return wrapper


def _get_app_version(self: SnakeBidsApp) -> str | None:
"""Attempt to get the app version, returning None if we can't.

This will succeed only if the following conditions are true:

1. The Snakebids app is a distribution package installed in the current
environment.
2. The app's distribution package has the same name as this
SnakeBidsApp's snakemake_dir
"""
try:
return metadata.version(self.snakemake_dir.name)
except metadata.PackageNotFoundError:
logger.warning(
"This Snakebids app's version could not be found. This will not generally "
tkkuehn marked this conversation as resolved.
Show resolved Hide resolved
"affect the app's functioning, but its version will be recorded as "
'"unknown" in the output config file. If you\'ve installed the app into '
"your python environment, this is likely caused by the app maintainer's "
"version implementation being non-standard, and only the app maintainer "
"could correct the versioning. In that case, this message can be ignored."
)
return None


@attr.define(slots=False)
class SnakeBidsApp:
"""Snakebids app with config and arguments.
Expand Down Expand Up @@ -128,6 +157,7 @@ class SnakeBidsApp:
lambda self: load_configfile(self.snakemake_dir / self.configfile_path),
takes_self=True,
)
version: Optional[str] = attr.Factory(_get_app_version, takes_self=True)
args: Optional[SnakebidsArgs] = None

def run_snakemake(self) -> None:
Expand Down Expand Up @@ -207,7 +237,12 @@ def run_snakemake(self) -> None:
# Write the config file
write_config_file(
config_file=new_config_file,
data=app.config,
data=dict(
app.config,
snakemake_version=metadata.version("snakemake"),
snakebids_version=metadata.version("snakebids"),
app_version=app.version or "unknown",
),
force_overwrite=True,
)

Expand Down
27 changes: 27 additions & 0 deletions snakebids/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import copy
import json
import sys
from pathlib import Path
from typing import Any, cast

Expand All @@ -20,6 +21,11 @@
from ..app import SnakeBidsApp
from .mock.config import config

if sys.version_info >= (3, 8):
from importlib import metadata
else:
import importlib_metadata as metadata


@pytest.fixture
def app(mocker: MockerFixture):
Expand Down Expand Up @@ -142,6 +148,9 @@ def test_runs_in_correct_mode(
"pybidsdb_reset": True,
"snakefile": Path("Snakefile"),
"output_dir": outputdir.resolve(),
"snakemake_version": metadata.version("snakemake"),
"snakebids_version": metadata.version("snakebids"),
"app_version": "unknown", # not installing a snakebids app here
}
)
if root == "app" and not tail:
Expand Down Expand Up @@ -234,6 +243,24 @@ def plugin(my_app: SnakeBidsApp):

assert app.foo == "bar" # type: ignore

def test_get_app_version_no_package(self, app: SnakeBidsApp):
assert app.version is None

def test_get_app_version_package(self, mocker: MockerFixture):
metadata_pkg = (
"importlib.metadata" if sys.version_info >= (3, 8) else "importlib_metadata"
)
mock = mocker.patch(f"{metadata_pkg}.version", return_value="0.1.0")
app = SnakeBidsApp(
Path("my_app"),
snakefile_path=Path("Snakefile"),
configfile_path=Path("mock/config.yaml"),
config=copy.deepcopy(config),
)

assert app.version == "0.1.0"
mock.assert_called_once_with("my_app")


class TestGenBoutiques:
def test_boutiques_descriptor(self, tmp_path: Path, app: SnakeBidsApp):
Expand Down
15 changes: 15 additions & 0 deletions typings/importlib_metadata/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
def version(distribution_name: str) -> str:
"""Get the version string for the named package.

:param distribution_name: The name of the distribution package to query.
:return: The version string for the package as defined in the package's
"Version" metadata key.
"""
...

class PackageNotFoundError(ModuleNotFoundError):
"""The package was not found."""

def __str__(self) -> str: ...
@property
def name(self) -> str: ...