from __future__ import unicode_literals, absolute_import
from six import string_types
try:
from functools import lru_cache
except ImportError:
from backports.functools_lru_cache import lru_cache
from catpy.applications.base import CatmaidClientApplication
[docs]def name_to_id(fn):
def wrapper(instance, id_or_name, *args, **kwargs):
if isinstance(id_or_name, int):
return id_or_name
elif isinstance(id_or_name, string_types):
return fn(instance, id_or_name, *args, **kwargs)
else:
raise TypeError("Argument was neither integer ID nor string name")
return wrapper
[docs]def id_to_name(fn):
def wrapper(instance, id_or_name, *args, **kwargs):
if isinstance(id_or_name, string_types):
return id_or_name
elif isinstance(id_or_name, int):
return fn(instance, id_or_name, *args, **kwargs)
else:
raise TypeError("Argument was neither integer ID nor string name")
return wrapper
[docs]class NameResolverException(ValueError):
pass
[docs]class NoMatchingNamesException(NameResolverException):
pass
[docs]class MultipleMatchingNamesException(NameResolverException):
pass
[docs]class NameResolver(CatmaidClientApplication):
"""Catmaid client application which looks up integer database IDs for string names for various objects.
For convenience, lookup methods short-circuit if given an int (i.e. you can transparently use either
the ID or the name of an object).
HTTP responses are cached where possible, so there may be a performance benefit to sharing NameResolver instances.
Furthermore, subsequent lookups of IDs of the same object type (e.g. stack, user)
should be much faster than the first.
Lookup methods ensure that one object matches the given name/title for the given project,
raising a NoMatchingNamesException if there are zero matches,
and a MultipleMatchingNamesException if there are more than one,
both of which subclass NameResolverException, which subclasses ValueError.
"""
def _ensure_one(self, match_set, name, obj):
if len(match_set) == 0:
raise NoMatchingNamesException(
"Zero {} objects found with name {} in project {}".format(
obj, repr(name), self.project_id
)
)
elif len(match_set) == 1:
return match_set.pop()
else:
raise MultipleMatchingNamesException(
"Multiple {} objects ({}) found with name {} in project {}".format(
obj,
", ".join(str(i) for i in sorted(match_set)),
name,
self.project_id,
)
)
@lru_cache(1)
def _get_stacks(self):
return self.get((self.project_id, "stacks"))
[docs] @name_to_id
def get_stack_id(self, title):
"""Get the ID of the stack with the given title.
Parameters
----------
title : str or int
Stack title
Returns
-------
int
"""
matching_ids = set()
for stack in self._get_stacks():
if stack["title"] == title:
matching_ids.add(stack["id"])
return self._ensure_one(matching_ids, title, "stack")
@lru_cache(1)
def _get_user_list(self):
return self.get("user-list")
[docs] @name_to_id
def get_user_id(self, name):
"""Get the ID of the user with the given login or full name
Parameters
----------
name : str or int
Returns
-------
int
"""
matching_ids = set()
for user in self._get_user_list():
if name in [user["login"], user["full_name"]]:
matching_ids.add(user["id"])
return self._ensure_one(matching_ids, name, "user")
[docs] def get_neuron_names(self, *skeleton_ids):
"""Get a dict of skeleton IDs to neuron names.
Parameters
----------
skeleton_ids
Returns
-------
dict of int to str
"""
# todo: lru cache
return self.post((self.project_id, "skeleton", "neuronnames"), {"skids": skeleton_ids})
[docs] @id_to_name
def get_neuron_name(self, skeleton_id):
"""Get the neuron name associated with the given skeleton ID.
Utilises an LRU cache and can handle being given the name (just returns the name),
so useful for ensuring that a given argument resolves to a name either way.
Parameters
----------
skeleton_id
Returns
-------
str
"""
return self.get((self.project_id, "skeleton", skeleton_id, "neuronname"))["neuronname"]