Commit 9f079ee0 authored by Matias Guijarro's avatar Matias Guijarro
Browse files

fix issue #767: use map to retrieve axes in .get_axes_iter()

* Mapping, logtools refactoring: attach mapping to Session object
* Register motor controller in `controllers` category
* Register axes to their own category
* Register all Counter objects to 'counters'
* Added 'instance_iter' method to Map, to simplify specific objects lookup
* Added get_counters_iter() in utils
parent fcde1fcc
......@@ -7,11 +7,6 @@
"""Bliss main package
For your convenience, configuration motion and scan APIs have been made available
directly at this level.
Here are the main bliss sub-systems:
.. autosummary::
:toctree:
......@@ -40,5 +35,3 @@ monkey.patch_all(thread=False)
from redis import selector
selector._DEFAULT_SELECTOR = selector.SelectSelector
from bliss.common import logtools
......@@ -32,7 +32,7 @@ from ...common.greenlet_utils import KillMask, protect_from_kill
from bliss.comm.util import HexMsg
from bliss.common.tango import DeviceProxy
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
__TMO_TUPLE = (
......@@ -137,7 +137,7 @@ class Prologix(LogMixin):
hostname = match.group(2)
port = match.group(3) and int(match.group(3)) or 1234
self._sock = Socket(hostname, port, timeout=keys.get("timeout"))
mapping.register(self, children_list=["comms", self._sock])
session.get_current().map.register(self, children_list=["comms", self._sock])
self._logger.debug(f"Prologix::__init__() host = {hostname} port = {port}")
self._gpib_kwargs = keys
......@@ -237,7 +237,7 @@ class TangoDeviceServer(LogMixin):
self._pad = keys["pad"]
self._sad = keys.get("sad", 0)
self._pad_sad = self._pad + (self._sad << 8)
mapping.register(self)
session.get_current().map.register(self)
def init(self):
self._logger.debug("TangoDeviceServer::init()")
......@@ -283,7 +283,7 @@ class LocalGpib(LogMixin):
raise LocalGpibError("LocalGpib: url is not valid (%s)" % url)
self._gpib_kwargs = keys
mapping.register(self, tag=str(self))
session.get_current().map.register(self, tag=str(self))
def __str__(self):
return "{0}(board={1})".format(type(self).__name__, self.board_index)
......@@ -373,7 +373,7 @@ class Gpib(LogMixin):
self._raw_handler = None
self._data = b""
mapping.register(self, tag=str(self))
session.get_current().map.register(self, tag=str(self))
def __str__(self):
opts = self._gpib_kwargs
......
......@@ -35,7 +35,7 @@
import socket, sys
from struct import *
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
# debug = ["io", "ignore_not_impl"] # "dummy_io", "rw"
......@@ -75,12 +75,12 @@ class EnetSocket(LogMixin):
self.sta = self.err = self.cnt = 0
self.enet1000 = False
self._extra_socket = list()
mapping.register(self, parents_list=["comms"], tag=str(self))
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
def _open(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.connect((self._host, self._port))
mapping.register(
session.get_current().map.register(
self, parents_list=["comms"], children_list=[self._sock], tag=str(self)
)
......
......@@ -18,7 +18,7 @@ from .exceptions import CommunicationError, CommunicationTimeout
from ..common.greenlet_utils import KillMask, protect_from_kill
from . import serial
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
......@@ -94,7 +94,7 @@ class Modbus_RTU(LogMixin):
self._serial = serial.Serial(*args, **kwargs)
self.node = node
self._lock = lock.RLock()
mapping.register(self, children_list=[self._serial])
session.get_current().map.register(self, children_list=[self._serial])
def __del__(self):
self._serial.close()
......
......@@ -19,7 +19,7 @@ from gevent import socket, select, lock, event
from ..common.greenlet_utils import KillMask
from bliss.common.cleanup import capture_exceptions
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
import serial
......@@ -746,7 +746,7 @@ class Serial(LogMixin):
self._timeout = timeout
self._raw_handler = None
self._lock = lock.RLock()
mapping.register(self, parents_list=["comms"], tag=str(self))
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......@@ -771,7 +771,7 @@ class Serial(LogMixin):
self._raw_handler = TangoSerial(self, **self._serial_kwargs)
else: # LOCAL
self._raw_handler = LocalSerial(self, **self._serial_kwargs)
mapping.register(
session.get_current().map.register(
self,
parents_list=["comms"],
children_list=[self._raw_handler],
......
......@@ -23,7 +23,7 @@ from .exceptions import CommunicationError, CommunicationTimeout
from ..common.greenlet_utils import KillMask
from bliss.common.cleanup import error_cleanup, capture_exceptions
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
......@@ -90,7 +90,7 @@ class BaseSocket(LogMixin):
self._event = event.Event()
self._raw_read_task = None
self._lock = lock.RLock()
mapping.register(self, parents_list=["comms"], tag=str(self))
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......@@ -411,7 +411,7 @@ class Command(LogMixin):
self.__transaction = transaction
self.__clear_transaction = clear_transaction
self.data = b""
mapping.register(
session.get_current().map.register(
self, children_list=[self.__socket], parents_list=["comms"]
)
......@@ -461,7 +461,7 @@ class Command(LogMixin):
self._raw_read_task = None
self._transaction_list = []
self._lock = lock.RLock()
mapping.register(self, parents_list=["comms"], tag=str(self))
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......@@ -501,7 +501,7 @@ class Command(LogMixin):
return True
self._fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mapping.register(
session.get_current().map.register(
self._fd,
parents_list=[self, "comms"],
tag=f"Socket[{local_host}:{local_port}",
......
......@@ -18,6 +18,7 @@ from tabulate import tabulate
from bliss.config import static
from bliss import setup_globals
from bliss.common import session
from bliss.common.utils import get_counters_iter
class AliasMixin(object):
......@@ -173,14 +174,13 @@ class Alias(object):
# check if there is a counter around that can be linked to this alias
if not disable_link_search:
from bliss.common.utils import counter_dict
for key, item in counter_dict().items():
for cnt in get_counters_iter():
key = cnt.fullname
if key == original_name:
self._link_to(item)
self._link_to(cnt)
break
elif item.name == original_name:
self._link_to(item)
elif cnt.name == original_name:
self._link_to(cnt)
break
print(f"Alias '{alias_name}' added for '{original_name}'")
......
......@@ -40,8 +40,6 @@ from bliss.common.hook import MotionHook
from bliss.config.channels import Channel
from bliss.common.alias import AliasMixin
from bliss.physics.trajectory import LinearTrajectory
from bliss import setup_globals
from bliss.common import mapping
from bliss.common.logtools import LogMixin
import gevent
import re
......@@ -590,7 +588,6 @@ class Axis(AliasMixin, LogMixin):
disabled_cache.extend(config.get("disabled_cache", [])) # get it for this axis
for settings_name in disabled_cache:
self.settings.disable_cache(settings_name)
mapping.register(self, parents_list=[self.__controller], tag=f"axis.{name}")
self._unit = self.config.get("unit", str, None)
def __close__(self):
......
......@@ -11,10 +11,11 @@ from fnmatch import fnmatch, fnmatchcase
import functools
import networkx as nx
from bliss.common.utils import common_prefix, autocomplete_property
from bliss.common.mapping import _BEAMLINE_MAP, BEAMLINE_GRAPH, format_node
from bliss.common.utils import autocomplete_property
from bliss.common.mapping import format_node
from bliss.common import session
__all__ = ["log", "lslog", "lsdebug"]
__all__ = ["lslog", "lsdebug"]
def logging_startup(
......@@ -31,12 +32,11 @@ def logging_startup(
class LogMixin:
@autocomplete_property
def _logger(self, *args, **kwargs):
m = session.get_current().map
id_ = id(self)
if id_ not in BEAMLINE_GRAPH:
raise UnboundLocalError(
"Instance should be registered with mapping.register before using _logger"
)
return BEAMLINE_GRAPH.node[id_]["_logger"]
if id_ not in m.G:
return None
return m.G.node[id_]["_logger"]
def improve_logger(logger_instance):
......@@ -156,9 +156,9 @@ class Log:
def __init__(self, map_beamline):
self.map_beamline = map_beamline
logging.getLogger("beamline").setLevel(
logging.WARNING
) # setting starting level
map_beamline.add_map_handler(map_update_loggers)
logging.getLogger("session").setLevel(logging.WARNING) # setting starting level
map_beamline.trigger_update()
def _check_log_level(self: (str, int), level):
"""
......@@ -513,20 +513,9 @@ def map_update_loggers(G):
G.node[node]["_logger"].name = logger_name
def set_log(map_beamline):
"""
Instantiates a logger bliss instance and creates global references to it
"""
global log
global lslog
global lsdebug
log = Log(map_beamline=map_beamline)
log.map_beamline.add_map_handler(map_update_loggers)
log.map_beamline.trigger_update()
lslog = log.lslog # shortcut
lsdebug = log.lsdebug # shortcut
def lslog(glob: str = None, level: int = None, inherited: bool = True):
return session.get_current().log.lslog(glob, level, inherited)
set_log(_BEAMLINE_MAP)
def lsdebug(inherited=True):
return session.get_current().log.lsdebug(inherited)
......@@ -5,21 +5,11 @@
# Copyright (c) 2015-2019 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.import logging
import networkx as nx
from functools import wraps
from functools import wraps, partial
import weakref
import logging
from functools import partial
__all__ = [
"BEAMLINE_GRAPH", # networkX GRAPH of the beamline
# will be copied in the node dictionary
# you can attach here dinamically
# and on node instantiation they will be
# taken from the instance
"map_draw_matplotlib",
"map_draw_pygraphviz",
]
__all__ = ["Map", "format_node"]
logger = logging.getLogger(__name__)
......@@ -35,7 +25,7 @@ def map_id(node):
return node if isinstance(node, str) else id(node)
class _BeamlineMap:
class Map:
def __init__(self):
self.G = nx.DiGraph()
self.handlers_list = []
......@@ -43,17 +33,9 @@ class _BeamlineMap:
self.G.find_children = self.find_children
self.G.find_predecessors = self.find_predecessors
self.node_attributes_list = ["name", "address", "plugin"]
self.__trash_queue = []
self.__lock = False
def register(
self,
instance,
parents_list=None,
children_list=None,
tag: str = None,
attrs={},
**kwargs,
self, instance, parents_list=None, children_list=None, tag: str = None, **kwargs
):
"""
Registers a devicename and instance inside a global device graph
......@@ -79,12 +61,10 @@ class _BeamlineMap:
parents_list: list of parent's instances
children_list: list of children's instances
tag: user tag to describe the instance in the more appropriate way
attrs: more key,value pairs attributes to be attached to the node
kwargs: more key,value pairs attributes to be attached to the node
ToDo:
* Avoid recreation of nodes/edges if not necessary
"""
# get always a list of arguments
if parents_list is None:
......@@ -96,7 +76,6 @@ class _BeamlineMap:
raise TypeError("parents_list and children_list should be of type list")
# First create this node
logger.debug(f"register: Creating node:{instance} id:{id(instance)}")
self.G.add_node(
map_id(instance),
......@@ -117,7 +96,7 @@ class _BeamlineMap:
if hasattr(instance, attr):
self.G.node[map_id(instance)][attr] = getattr(instance, attr)
for name, value in attrs:
for name, value in kwargs:
# populating self defined attributes
if self.G.node[map_id(instance)].get(name):
logger.debug("Overwriting node {name}")
......@@ -159,38 +138,38 @@ class _BeamlineMap:
return self.G.node.get(map_id(instance)) # return the dictionary of the node
def _trash_node(self, *args, id_=None):
self.__trash_queue.append(id_)
if not self.__lock:
self.trigger_update()
if id_ is None:
return
self.delete(id_)
self.trigger_update()
def __len__(self):
return len(self.G)
def instance_iter(self, tag):
node_list = list(self.G[tag])
for node_id in node_list:
node = self.G.node.get(node_id)
if node is not None:
inst_ref = self.G.node.get(node_id)["instance"]
inst = inst_ref()
if inst:
yield inst
def trigger_update(self):
"""
Triggers execution of handler functions on the map
"""
self.__lock = True
self.add_parent_if_missing()
try:
while self.__trash_queue:
self.delete(self.__trash_queue.pop()) # deleting nodes
logger.debug(f"trigger_update: executing")
for func in self.handlers_list:
try:
func(self.G)
except Exception:
logger.exception(
f"Failed trigger_update on map handlers for {func.__name__}"
)
finally:
self.__lock = False
if self.__trash_queue:
# if in the meanwhile there are new nodes to trash trigger the update
self.trigger_update()
logger.debug(f"trigger_update: executing")
for func in self.handlers_list:
try:
func(self.G)
except Exception:
logger.exception(
f"Failed trigger_update on map handlers for {func.__name__}"
)
def find_predecessors(self, node):
"""
......@@ -242,19 +221,18 @@ class _BeamlineMap:
True: The node was removed
False: The node was not in the graph
"""
if id_:
logger.debug(f"Calling mapping.delete for {id_}")
if id_ in self.G:
logger.debug(f"mapping.delete: Removing node id:{id_}")
predecessors_id = self.find_predecessors(id_)
children_id = self.find_children(id_)
self.G.remove_node(id_)
# Remaps parents edges on children
if predecessors_id and children_id:
for pred in predecessors_id:
for child in children_id:
self.G.add_edge(pred, child)
return True
logger.debug(f"Calling mapping.delete for {id_}")
if id_ in self.G:
logger.debug(f"mapping.delete: Removing node id:{id_}")
predecessors_id = self.find_predecessors(id_)
children_id = self.find_children(id_)
self.G.remove_node(id_)
# Remaps parents edges on children
if predecessors_id and children_id:
for pred in predecessors_id:
for child in children_id:
self.G.add_edge(pred, child)
return True
return False
def get_node_name(self, node):
......@@ -294,6 +272,9 @@ class _BeamlineMap:
node_name = self.G.node[id_]
return node_name
def format_node(self, node, format_string="tag->inst.name->inst.__class__->id"):
return format_node(self.G, node, format_string)
def add_map_handler(self, func):
self.handlers_list.append(func)
......@@ -384,107 +365,64 @@ class _BeamlineMap:
"""
for n in self.G:
value = self.format_node(self.G, n, format_string=format_string)
value = self.format_node(n, format_string=format_string)
self.G.node[n][dict_key] = value
@staticmethod
def format_node(graph, node, format_string="tag->inst.name->inst.__class__->id"):
"""
It inspects the node attributes to create a proper representation
It recognizes the following operators:
* inst.
* -> : apply a hierarchy, if the first on left is found it stops,
otherwise continues searching for an attribute
* + : links two attributes in one
Typical attribute names are:
* id: id of instance
* tag: defined argument during instantiation
* inst: representation of instance
* inst.name: attribute "name" of the instance (if present)
* inst.__class__: class of the instance
* user defined: as long as they are defined inside the node's
dictionary using register or later modifications
Args:
graph: DiGraph instance
node: id of the node
format_string: formatting string
Returns:
str: representation of the node according to the format string
"""
G = graph
n = node
format_arguments = format_string.split("->")
value = "" # clears the dict_key
for format_arg in format_arguments:
# known arguments
all_args = []
for arg in format_arg.split("+"):
if arg == "id":
all_args.append(str(n))
elif arg.startswith("inst"):
attr_name = arg[5:] # separates inst. from the rest
reference = G.node[n].get("instance")
inst = reference if isinstance(reference, str) else reference()
if len(attr_name) == 0: # requested only instance
all_args.append(str(inst))
if hasattr(inst, attr_name):
# if finds the attr assigns to dict_key
attr = getattr(inst, attr_name)
all_args.append(str(attr))
else:
val = G.node[n].get(arg)
if val:
# if finds the value assigns to dict_key
all_args.append(str(val))
if len(all_args):
value = " ".join(all_args)
break
return value
def BeamlineMap(singleton=True):
def format_node(graph, node, format_string="tag->inst.name->inst.__class__->id"):
"""
Instantiates a beamline map and creates global references to it
It inspects the node attributes to create a proper representation
It recognizes the following operators:
* inst.
* -> : apply a hierarchy, if the first on left is found it stops,
otherwise continues searching for an attribute
* + : links two attributes in one
Typical attribute names are:
* id: id of instance
* tag: defined argument during instantiation
* inst: representation of instance
* inst.name: attribute "name" of the instance (if present)
* inst.__class__: class of the instance
* user defined: as long as they are defined inside the node's
dictionary using register or later modifications
Args:
singleton: True if singleton, false for new instance
returns:
instance of _BeamlineMap
graph: DiGraph instance
node: id of the node
format_string: formatting string
Returns:
str: representation of the node according to the format string
"""
global _BEAMLINE_MAP
global BEAMLINE_GRAPH
global register
global map_draw_matplotlib
global map_draw_pygraphviz
global format_node
if singleton:
if _BEAMLINE_MAP is None:
_BEAMLINE_MAP = _BeamlineMap()
# shortcuts for external usage
BEAMLINE_GRAPH = _BEAMLINE_MAP.G
register = _BEAMLINE_MAP.register
map_draw_matplotlib = _BEAMLINE_MAP.map_draw_matplotlib
map_draw_pygraphviz = _BEAMLINE_MAP.map_draw_pygraphviz
format_node = _BeamlineMap.format_node
# base nodes
register("beamline", tag="beamline") # Root node
register("devices", parents_list=["beamline"], tag="devices")
register("sessions", parents_list=["beamline"], tag="sessions")
register("comms", parents_list=["beamline"], tag="comms")
register("counters", parents_list=["beamline"], tag="counters")
return _BEAMLINE_MAP
else:
# return a new instance
return _BeamlineMap()
_BEAMLINE_MAP = None
BeamlineMap()
G = graph
n = node
format_arguments = format_string.split("->")
value = "" # clears the dict_key
for format_arg in format_arguments:
# known arguments
all_args = []
for arg in format_arg.split("+"):
if arg == "id":
all_args.append(str(n))