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

Fede dev #49

Merged
merged 23 commits into from Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 7 additions & 3 deletions brainatlas_api/__init__.py
@@ -1,5 +1,7 @@
from rich import print as rprint

from brainatlas_api import bg_atlas
from brainatlas_api.list_atlases import list_atlases
from brainatlas_api.list_atlases import show_atlases

available_atlases = [
cls for cls in map(bg_atlas.__dict__.get, bg_atlas.__all__)
Expand All @@ -14,6 +16,8 @@ def get_atlas_class_from_name(name):
if name in atlases.keys():
return atlases[name]
else:
print(f"Could not find atlas with name {name}. Available atlases:\n")
list_atlases()
rprint(
f"[red1][b]Brainglobe_api[/b]: Could not find atlas with name {name}. Available atlases:[red1]"
)
show_atlases()
return None
29 changes: 28 additions & 1 deletion brainatlas_api/bg_atlas.py
@@ -1,5 +1,6 @@
from pathlib import Path
import tarfile
from rich import print as rprint

from brainatlas_api import utils
from brainatlas_api import config
Expand All @@ -22,6 +23,10 @@ def _version_tuple_from_str(version_str):
return tuple([int(n) for n in version_str.split(".")])


def _version_str_from_tuple(version_tuple):
return f"{version_tuple[0]}.{version_tuple[1]}"


class BrainGlobeAtlas(core.Atlas):
"""Add download functionalities to Atlas class.

Expand Down Expand Up @@ -59,12 +64,17 @@ def __init__(self, brainglobe_dir=None, interm_download_dir=None):

# Look for this atlas in local brainglobe folder:
if self.local_full_name is None:
print(0, f"{self.atlas_name} not found locally. Downloading...")
rprint(
f"[magenta2]Bgatlas_api: {self.atlas_name} not found locally. Downloading...[magenta2]"
)
self.download_extract_file()

# Instantiate after eventual download:
super().__init__(self.brainglobe_dir / self.local_full_name)

# Compare atlas local version with latest online
self.check_latest_version()

@property
def local_version(self):
"""If atlas is local, return actual version of the downloaded files;
Expand Down Expand Up @@ -134,6 +144,23 @@ def download_extract_file(self):

destination_path.unlink()

def check_latest_version(self):
"""
Checks if the local version is the latest available
and prompts the user to update if not
"""
local = _version_str_from_tuple(self.local_version)
online = _version_str_from_tuple(self.remote_version)

if local != online:
rprint(
f"[b][magenta2]Brainatlas_api[/b]: [b]{self.atlas_name}[/b] version [b]{local}[/b] is not the latest available ([b]{online}[/b]). "
+ "To update the atlas run in the terminal:[/magenta2]\n"
+ f" [gold1]brainatlas_update -a {self.atlas_name}[/gold1]"
)
return False
return True


class ExampleAtlas(BrainGlobeAtlas):
atlas_name = "example_mouse_100um"
Expand Down
105 changes: 77 additions & 28 deletions brainatlas_api/list_atlases.py
@@ -1,64 +1,113 @@
from pathlib import Path
from rich.table import Table
from rich.table import Table, box, Style
from rich import print as rprint
import click

from brainatlas_api import config
from brainatlas_api import bg_atlas
from brainatlas_api.bg_atlas import BrainGlobeAtlas
from brainatlas_api import utils


"""
Some functionality to list all available and downloaded brainglobe atlases
"""


def list_atlases():
# Parse config
def show_atlases(show_local_path=False):
"""
Print's a formatted table with the name and version of local (downloaded)
and online (available) atlases.

Downloads the latest atlas version and compares it with what it's stored
locally.
"""
if not utils.check_internet_connection():
print(
"Sorry, we need a working internet connection to retriev the latest metadata"
)
return

# --------------------------- Get available_atlases -------------------------- #
available_atlases = utils.conf_from_url(
BrainGlobeAtlas._remote_url_base.format("last_versions.conf")
)
available_atlases = dict(available_atlases["atlases"])

# ----------------------------- Get local atlases ---------------------------- #
# Get brainglobe directory
conf = config.read_config()
brainglobe_dir = Path(conf["default_dirs"]["brainglobe_dir"])

# ----------------------------- Get local atlases ---------------------------- #
# Get downloaded atlases
atlases = {}
for elem in brainglobe_dir.iterdir():
if elem.is_dir():
atlases[elem.name] = dict(
downloaded=True,
local=str(elem),
online=bg_atlas.BrainGlobeAtlas._remote_url_base.format(
elem.name
),
)
name = elem.name.split("_v")[0]
if name in available_atlases.keys():
atlases[name] = dict(
downloaded=True,
local=str(elem),
version=elem.name.split("_v")[-1],
latest_version=str(available_atlases[name]),
updated=str(available_atlases[name])
== elem.name.split("_v")[-1],
)

# ---------------------- Get atlases not yet downloaded ---------------------- #
available_atlases = [
cls for cls in map(bg_atlas.__dict__.get, bg_atlas.__all__)
]
for atlas in available_atlases:
name = f"{atlas.atlas_name}_v{atlas.local_version}"
if name not in atlases.keys():
atlases[str(name)] = dict(
for atlas in available_atlases.keys():
if atlas not in atlases.keys():
atlases[str(atlas)] = dict(
downloaded=False,
local="[red]---[/red]",
online=atlas._remote_url_base.format(name),
version="[red]---[/red]",
latest_version=str(available_atlases[str(name)]),
updated=None,
)

# -------------------------------- print table ------------------------------- #
table = Table(
show_header=True,
header_style="bold green",
title="\n\nBrainglobe Atlases",
expand=False,
box=box.ROUNDED,
)
table.add_column("Name")
table.add_column("Downloaded")
table.add_column("Local path")
table.add_column("Online path", style="dim")

for atlas, info in atlases.items():
table.add_column("Name", no_wrap=True, width=32)
table.add_column("Downloaded", justify="center")
table.add_column("Local version", justify="center")
table.add_column("Latest version", justify="center")
if show_local_path:
table.add_column("Local path")

for n, (atlas, info) in enumerate(atlases.items()):
if info["downloaded"]:
downloaded = "[green]:heavy_check_mark:[/green]"
else:
downloaded = "[red]---[/red]"
table.add_row(
"[b]" + atlas + "[/b]", downloaded, info["local"], info["online"]
)

row = [
"[b]" + atlas + "[/b]",
downloaded,
info["version"],
info["latest_version"],
]

if show_local_path:
row.append(info["local"])

table.add_row(*row,)

if info["updated"] is not None:
if not info["updated"]:
table.row_styles.append(
Style(color="black", bgcolor="magenta2")
)

rprint(table)


@click.command()
@click.option("-s", "--show_local_path", is_flag=True)
def cli_show_atlases(show_local_path=False):
return show_atlases(show_local_path=show_local_path)
67 changes: 67 additions & 0 deletions brainatlas_api/update.py
@@ -0,0 +1,67 @@
from rich import print as rprint
import shutil
import click

import brainatlas_api
from brainatlas_api.bg_atlas import _version_str_from_tuple


def update_atlas(atlas_name=None, force=False):
"""
Updates a brainatlas_api atlas from the latest
available version online.

Arguments:
----------
atlas_name: str, name of the atlas to update
force: bool, if False it checks if the atlas is already
at the latest version and doesn't update if
that's the case.
"""
# Check input
if not isinstance(atlas_name, str):
raise ValueError(f"atlas name should be a string, not {atlas_name}")

# Get atlas class
atlasclass = brainatlas_api.get_atlas_class_from_name(atlas_name)
if atlasclass is None:
return

atlas = atlasclass()

# Check if we need to update
if not force:
if atlas.check_latest_version():
rprint(
f"[b][magenta2]Brainatlas_api: {atlas.atlas_name} is already updated "
+ f"(version: {_version_str_from_tuple(atlas.local_version)})[/b]"
)
return

# Delete atlas folder
rprint(
f"[b][magenta2]Brainatlas_api: updating {atlas.atlas_name}[/magenta2][/b]"
)
fld = atlas.brainglobe_dir / atlas.local_full_name
shutil.rmtree(fld)
if fld.exists():
raise ValueError(
"Something went wrong while tryint to delete the old version of the atlas, aborting."
)

# Download again
atlas.download_extract_file()

# Check that everything went well
atlasclass()
rprint(
f"[b][magenta2]Brainatlas_api: {atlas.atlas_name} updated to version: "
+ f"{_version_str_from_tuple(atlas.remote_version)}[/magenta2][/b]"
)


@click.command()
FedeClaudi marked this conversation as resolved.
Show resolved Hide resolved
@click.option("-a", "--atlas_name")
@click.option("-f", "--force", is_flag=True)
def cli_update_atlas_command(atlas_name, force=False):
return update_atlas(atlas_name, force=force)
9 changes: 8 additions & 1 deletion brainatlas_api/utils.py
Expand Up @@ -2,9 +2,9 @@
import tifffile
import numpy as np
import requests
from tqdm.auto import tqdm
import logging
import configparser
from tqdm.auto import tqdm

logging.getLogger("urllib3").setLevel(logging.WARNING)

Expand Down Expand Up @@ -65,6 +65,13 @@ def conf_from_url(url):
return config


def get_latest_atlases_version():
# TODO download version from online

versions = read_json("docs/atlases/latest_version.json")
return versions


# --------------------------------- File I/O --------------------------------- #
def read_json(path):
with open(path, "r") as f:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
@@ -1,4 +1,3 @@
tqdm>=4.46.1
numpy
tifffile
treelib
Expand All @@ -7,3 +6,4 @@ requests
meshio
click
rich
tqdm>=4.46.1
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -31,7 +31,8 @@
entry_points={
"console_scripts": [
"brainatlas_config = brainatlas_api.config:cli_modify_config",
"brainatlas_list_atlases = brainatlas_api.list_atlases:list_atlases",
"brainatlas_list_atlases = brainatlas_api.list_atlases:cli_show_atlases",
"brainatlas_update = brainatlas_api.update:cli_update_atlas_command",
]
},
packages=find_namespace_packages(exclude=("atlas_gen", "docs", "tests*")),
Expand Down
11 changes: 9 additions & 2 deletions tests/test_list_atlases.py
@@ -1,9 +1,16 @@
import brainatlas_api
from brainatlas_api import list_atlases
import pytest
from click.testing import CliRunner


def test_list_atlases():
brainatlas_api.list_atlases()
def test_show_atlases():
list_atlases.show_atlases(show_local_path=True)


def test_cli_show_atlases():
runner = CliRunner()
runner.invoke(list_atlases.cli_show_atlases, ["s"])


@pytest.mark.parametrize(
Expand Down
23 changes: 23 additions & 0 deletions tests/test_update_atlas.py
@@ -0,0 +1,23 @@
from brainatlas_api import update
from click.testing import CliRunner


def test_update():
update.update_atlas(atlas_name="example_mouse_100um")

update.update_atlas(atlas_name="example_mouse_100um", force=True)


def test_update_wrong_name():
update.update_atlas("allen_madasadsdouse_25um")


def test_update_command():
runner = CliRunner()

# Test printing of config file:
result = runner.invoke(
update.cli_update_atlas_command, ["-a", "example_mouse_100um"]
)

assert result.exit_code == 0