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 4 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
2 changes: 1 addition & 1 deletion 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 = "^6.6.0", python = "<3.8" } ]
tkkuehn marked this conversation as resolved.
Show resolved Hide resolved

# 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
27 changes: 26 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 @@ -207,7 +212,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.get_app_version() or "unknown",
),
force_overwrite=True,
)

Expand Down Expand Up @@ -240,6 +250,21 @@ def create_descriptor(self, out_file: PathLike[str] | str) -> None:
)
new_descriptor.save(out_file) # type: ignore

def get_app_version(self) -> str | None:
tkkuehn marked this conversation as resolved.
Show resolved Hide resolved
"""Attempt to get the app version, returning None if we can't.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're moving in this direction, I wonder if we should allow (and encourage) the passing of explicit modules (or string names of modules) to snakemake_dir in addition to paths. This would actually simplify the run.py file a bit. And this would create some extra liberty e.g. if the snakemake package was in some subpackage foo.bar, we'd be able to support that a little better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily a goal for this PR, but maybe something to think about for the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't address this for now, but is probably worth a separate issue for discussion.


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:
return None


def update_config(config: dict[str, Any], snakebids_args: SnakebidsArgs) -> None:
"""Add snakebids arguments to config in-place."""
Expand Down
22 changes: 22 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": "0.0.0", # poetry-dynamic-versioning
tkkuehn marked this conversation as resolved.
Show resolved Hide resolved
"app_version": "unknown", # not installing a snakebids app here
}
)
if root == "app" and not tail:
Expand Down Expand Up @@ -234,6 +243,19 @@ def plugin(my_app: SnakeBidsApp):

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

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

def test_get_app_version_package(self, mocker: MockerFixture, app: SnakeBidsApp):
app.snakemake_dir = Path("my_app")

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")
assert app.get_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: ...