Skip to content
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" } ]
Comment thread
tkkuehn marked this conversation as resolved.
Outdated

# 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:
Comment thread
tkkuehn marked this conversation as resolved.
Outdated
"""Attempt to get the app version, returning None if we can't.

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Comment thread
tkkuehn marked this conversation as resolved.
Outdated
"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: ...