Commit a1a20fe3 authored by Matias Guijarro's avatar Matias Guijarro Committed by Sebastien Petitdemange
Browse files

Global map (#847) + fix #727, fix #716, issue #615 + aliases refactoring

In order to make it nicer for users, the global objects are initialized
in bliss/__init__.py ; leaking module-level globals from imports are
made 'private' with '_' (do not show)
The logging_startup function is moved from logtools to __init__.py as
well.

Aliases refactoring:
* removed AliasMixin class
* removed 'alias_or_fullname, alias_or_name properties
    - code explicitely ask for name, fullname or alias instead
* only objects found in env dict can be aliased, or counters
    - the CounterWrapper object is a proxy that evaluates to the corresponding
      counter at runtime, in order to make sure the alias is always in sync
      with the counter. The proxy code is a modified version of the proxy
      from the 'wrapt' project, from 'lazy-object-proxy' (PyPI)
* added set() and remove()
parent 5cbe2651
......@@ -20,7 +20,6 @@
shell
tango
"""
from . import release
__version__ = release.version
......@@ -28,10 +27,50 @@ __author__ = release.author
__license__ = release.license
version_info = release.version_info
from gevent import monkey
from gevent import monkey as _monkey
_monkey.patch_all(thread=False)
from redis import selector as _selector
_selector._DEFAULT_SELECTOR = _selector.SelectSelector
from bliss.common.proxy import Proxy as _Proxy
def get_current_session():
from bliss.common import session
return session.CURRENT_SESSION
current_session = _Proxy(get_current_session)
from bliss.common.alias import MapWithAliases as _MapWithAliases
global_map = _MapWithAliases(current_session)
from bliss.common.logtools import Log as _Log
global_log = _Log(map=global_map)
def logging_startup(
log_level="WARNING", fmt="%(levelname)s %(asctime)-15s %(name)s: %(message)s"
):
"""
Provides basicConfig functionality to bliss activating at proper level the root loggers
"""
import logging # this is not to pollute the global namespace
monkey.patch_all(thread=False)
# save log messages format
global_log.set_log_format(fmt)
global_log._LOG_DEFAULT_LEVEL = log_level # to restore level of non-BlissLoggers
from redis import selector
# setting startup level for session and bliss logger
logging.getLogger("session").setLevel(log_level)
logging.getLogger("bliss").setLevel(log_level)
selector._DEFAULT_SELECTOR = selector.SelectSelector
# install an additional handler, only for debug messages
# (debugon / debugoff)
global_log.set_debug_handler(logging.StreamHandler())
......@@ -8,7 +8,7 @@
from warnings import warn
from .embl import ExporterClient
from bliss.common.logtools import *
from bliss.common import session
from bliss import global_map
import gevent
from gevent.queue import Queue
......@@ -59,7 +59,7 @@ class Exporter(ExporterClient.ExporterClient):
self.events_queue = Queue()
self.events_processing_task = None
session.get_current().map.register(
global_map.register(
self, parents_list=["comms"], tag=f"exporter: {address}:{port}"
)
......
......@@ -31,8 +31,8 @@ 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 session
from bliss.common.logtools import *
from bliss import global_map
__TMO_TUPLE = (
0.0,
......@@ -136,8 +136,8 @@ class Prologix:
hostname = match.group(2)
port = match.group(3) and int(match.group(3)) or 1234
self._sock = Socket(hostname, port, timeout=keys.get("timeout"))
session.get_current().map.register(self, children_list=["comms", self._sock])
log_debug(self, f"Prologix::__init__() host = {hostname} port = {port}")
global_map.register(self, children_list=["comms", self._sock])
self._gpib_kwargs = keys
def init(self):
......@@ -237,7 +237,7 @@ class TangoDeviceServer:
self._pad = keys["pad"]
self._sad = keys.get("sad", 0)
self._pad_sad = self._pad + (self._sad << 8)
session.get_current().map.register(self)
global_map.register(self)
def init(self):
log_debug(self, "TangoDeviceServer::init()")
......@@ -283,7 +283,7 @@ class LocalGpib:
raise LocalGpibError("LocalGpib: url is not valid (%s)" % url)
self._gpib_kwargs = keys
session.get_current().map.register(self, tag=str(self))
global_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:
self._raw_handler = None
self._data = b""
session.get_current().map.register(self, tag=str(self))
global_map.register(self, tag=str(self))
def __str__(self):
opts = self._gpib_kwargs
......
......@@ -35,8 +35,8 @@
import socket, sys
from struct import *
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
# debug = ["io", "ignore_not_impl"] # "dummy_io", "rw"
debug = ["ignore_not_impl"]
......@@ -75,12 +75,12 @@ class EnetSocket:
self.sta = self.err = self.cnt = 0
self.enet1000 = False
self._extra_socket = list()
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
global_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))
session.get_current().map.register(
global_map.register(
self, parents_list=["comms"], children_list=[self._sock], tag=str(self)
)
......
......@@ -19,8 +19,8 @@ from .exceptions import CommunicationError, CommunicationTimeout
from ..common.greenlet_utils import KillMask, protect_from_kill
from . import serial
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
class ModbusError(CommunicationError):
......@@ -95,7 +95,7 @@ class Modbus_RTU:
self._serial = serial.Serial(*args, **kwargs)
self.node = node
self._lock = lock.RLock()
session.get_current().map.register(self, children_list=[self._serial])
global_map.register(self, children_list=[self._serial])
def __del__(self):
self._serial.close()
......
......@@ -42,8 +42,8 @@ import numpy
from .util import get_interface
from .exceptions import CommunicationError, CommunicationTimeout
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
def decode_IDN(s):
......@@ -413,7 +413,7 @@ class SCPI:
def __init__(self, interface=None, commands=COMMANDS, **kwargs):
self.interface = interface
session.get_current().map.register(
global_map.register(
self, parents_list=["comms"], children_list=[self.interface], tag=str(self)
)
self._strict_query = kwargs.get("strict_query", True)
......@@ -665,9 +665,7 @@ class BaseSCPIDevice:
commands = kwargs.pop("commands", {})
self.interface = interface
self.language = SCPI(interface=interface, commands=commands)
session.get_current().map.register(
self, children_list=[self.language], tag=str(self)
)
global_map.register(self, children_list=[self.language], tag=str(self))
def __str__(self):
return "{0}({1})".format(type(self).__name__, self.language)
......
......@@ -19,8 +19,8 @@ from gevent import socket, select, lock, event
from ..common.greenlet_utils import KillMask
from bliss.common.cleanup import capture_exceptions
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
import serial
......@@ -747,7 +747,7 @@ class Serial:
self._timeout = timeout
self._raw_handler = None
self._lock = lock.RLock()
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
global_map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......@@ -772,7 +772,7 @@ class Serial:
self._raw_handler = TangoSerial(self, **self._serial_kwargs)
else: # LOCAL
self._raw_handler = LocalSerial(self, **self._serial_kwargs)
session.get_current().map.register(
global_map.register(
self,
parents_list=["comms"],
children_list=[self._raw_handler],
......
......@@ -19,8 +19,8 @@ import socket
import weakref
from functools import wraps
from bliss.common import event
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
from .error import SpecClientNotConnectedError
from .channel import SpecChannel
from .message import *
......@@ -187,7 +187,7 @@ class SpecConnection:
self.port = None
self.scanport = True
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
global_map.register(self, parents_list=["comms"], tag=str(self))
def __str__(self):
return "<connection to Spec, host=%s, port=%s>" % (
......
......@@ -24,8 +24,8 @@ from .exceptions import CommunicationError, CommunicationTimeout
from ..common.greenlet_utils import KillMask
from bliss.common.cleanup import error_cleanup, capture_exceptions
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
class SocketTimeout(CommunicationTimeout):
......@@ -92,7 +92,7 @@ class BaseSocket:
self._event = event.Event()
self._raw_read_task = None
self._lock = lock.RLock()
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
global_map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......@@ -460,7 +460,7 @@ class Command:
self._raw_read_task = None
self._transaction_list = []
self._lock = lock.RLock()
session.get_current().map.register(self, parents_list=["comms"], tag=str(self))
global_map.register(self, parents_list=["comms"], tag=str(self))
def __del__(self):
self.close()
......
This diff is collapsed.
......@@ -38,7 +38,6 @@ from bliss.common.utils import Null, with_custom_members
from bliss.common.encoder import Encoder
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.common.logtools import *
import gevent
......@@ -543,7 +542,7 @@ def lazy_init(func):
@with_custom_members
class Axis(AliasMixin):
class Axis:
"""
Bliss motor axis
......
......@@ -14,15 +14,26 @@ from louie import robustapply
from louie import saferef
def _get_sender(sender):
try:
sender = sender.__wrapped__
except AttributeError:
pass
return sender
def send(sender, signal, *args, **kwargs):
sender = _get_sender(sender)
dispatcher.send(signal, sender, *args, **kwargs)
def connect(sender, signal, callback):
sender = _get_sender(sender)
dispatcher.connect(callback, signal, sender)
def disconnect(sender, signal, callback):
sender = _get_sender(sender)
try:
dispatcher.disconnect(callback, signal, sender)
except Exception:
......
......@@ -9,8 +9,8 @@
how to use motion hooks in your system"""
import weakref
from bliss.common import session
from bliss.common.logtools import *
from bliss import global_map
__all__ = ["MotionHook"]
......@@ -31,7 +31,7 @@ class MotionHook:
axis (Axis): new axis to be added to the hook
"""
self.__axes[axis.name] = axis
session.get_current().map.register(self, children_list=list(self.axes.values()))
global_map.register(self, children_list=list(self.axes.values()))
@property
def axes(self):
......
......@@ -7,7 +7,7 @@
import logging
import contextlib
from logging import Logger, StreamHandler, NullHandler, Formatter
from logging import Logger, NullHandler, Formatter
import re
from fnmatch import fnmatch, fnmatchcase
import networkx as nx
......@@ -15,7 +15,7 @@ from functools import wraps
from bliss.common.utils import autocomplete_property
from bliss.common.mapping import format_node, map_id
from bliss.common import session
from bliss import global_map
__all__ = [
......@@ -29,8 +29,6 @@ __all__ = [
"set_log_format",
"hexify",
"asciify",
"debugon",
"debugoff",
"get_logger",
]
......@@ -79,27 +77,6 @@ def _hex_format(ch):
return "\\x%02x" % ord(ch)
def logging_startup(
log_level=logging.WARNING, fmt="%(levelname)s %(asctime)-15s %(name)s: %(message)s"
):
"""
Provides basicConfig functionality to bliss activating at proper level the root loggers.
"""
# save log messages format
session.get_current().log.set_log_format(fmt)
session.get_current().log._LOG_DEFAULT_LEVEL = (
log_level
) # to restore level of non-BlissLoggers
# setting startup level for session and bliss logger
logging.getLogger("session").setLevel(log_level)
logging.getLogger("bliss").setLevel(log_level)
# install an additional handler, only for debug messages
# (debugon / debugoff)
session.get_current().log.set_debug_handler(StreamHandler())
def get_logger(instance):
"""
Provides a way to retrieve the logger for a give instance.
......@@ -110,12 +87,11 @@ def get_logger(instance):
Returns:
BlissLogger instance for the specific instance
"""
m = session.get_current().map
id_ = map_id(instance)
if id_ in m.G:
return m.G.node[id_]["_logger"]
m.register(instance)
return m[instance]["_logger"]
if id_ in global_map.G:
return global_map.G.node[id_]["_logger"]
global_map.register(instance)
return global_map[instance]["_logger"]
LOG_DOCSTRING = """
......@@ -521,15 +497,3 @@ def map_update_loggers(G):
new_logger_name = create_logger_name(G, node) # get proper name
with bliss_logger():
node_dict["_logger"] = logging.getLogger(new_logger_name)
@wraps(Log.debugon)
def debugon(glob_logger_pattern_or_obj):
return session.get_current().log.debugon(glob_logger_pattern_or_obj)
@wraps(Log.debugoff)
def debugoff(glob_logger_pattern_or_obj):
return session.get_current().log.debugoff(glob_logger_pattern_or_obj)
......@@ -50,6 +50,11 @@ class Map:
self.register("counters", parents_list=["session"])
self.register("axes", parents_list=["session"])
def clear(self):
for node_id in list(self):
if not node_id in ("session", "controllers", "comms", "counters", "axes"):
self.delete(node_id)
def _create_node(self, instance):
logger.debug(f"register: Creating node:{instance} id:{id(instance)}")
if isinstance(instance, weakref.ProxyTypes):
......
......@@ -14,9 +14,10 @@ import weakref
from collections import namedtuple
import enum
from bliss.common.alias import AliasMixin
from bliss.common import session
from bliss.common.utils import autocomplete_property
from bliss.scanning.acquisition.calc import CalcAcquisitionDevice
from bliss.scanning.channel import AcquisitionChannel
from bliss import global_map
def add_conversion_function(obj, method_name, function):
......@@ -38,52 +39,12 @@ def add_conversion_function(obj, method_name, function):
# Counter namespaces
def flat_namespace(dct):
"""A namespace allowing names with dots."""
mapping = dict(dct)
class getter(object):
def __init__(self, parent, prefix):
self.parent = parent
self.prefix = prefix
def __getattr__(self, key):
return getattr(self.parent, self.prefix + key)
class namespace(tuple):
__slots__ = ()
_fields = sorted(mapping)
__dict__ = property(lambda _: mapping)
def __getattr__(self, arg):
if arg in mapping:
return mapping[arg]
if arg.startswith("__"):
raise AttributeError(arg)
for field in self._fields:
if field.startswith(arg + "."):
return getter(self, arg + ".")
raise AttributeError(arg)
def __setattr__(self, arg, value):
raise AttributeError("can't set attribute")
def __info__(self):
reprs = ("{}={!r}".format(field, mapping[field]) for field in self._fields)
return "{}({})".format("namespace", ", ".join(reprs))
return namespace(mapping[field] for field in namespace._fields)
def namespace(dct):
if any("." in key for key in dct):
return flat_namespace(dct)
return namedtuple("namespace", sorted(dct))(**dct)
def counter_namespace(counters):
return namespace({counter.name: counter for counter in counters})
if isinstance(counters, dict):
dct = counters
else:
dct = {counter.name: counter for counter in counters}
return namedtuple("namespace", sorted(dct))(**dct)
# Base counter class
......@@ -115,7 +76,7 @@ class GroupedReadMixin(object):
pass
class BaseCounter(AliasMixin, object):
class BaseCounter:
"""Define a standard counter interface."""
# Properties
......@@ -158,20 +119,7 @@ class BaseCounter(AliasMixin, object):
"""A unique name within the session scope.
The standard implementation defines it as:
`<master_controller_name>.<controller_name>.<counter_name>`.
"""
fullctrlname = self.fullcontrollername
if fullctrlname:
return fullctrlname + "." + self.name
else:
return self.name
@property
def fullcontrollername(self):
"""Name of the controllers attached to this counter if there are any.
The standard implementation defines it as:
`<master_controller_name>.<controller_name>
`<master_controller_name>.<controller_name>.<counter_name>`
"""
args = []
# Master controller
......@@ -180,11 +128,8 @@ class BaseCounter(AliasMixin, object):
# Controller
if self.controller is not None:
args.append(self.controller.name)
# Name
if len(args) > 0:
return ".".join(args)
else:
return None
args.append(self.name)
return ":".join(args)
class Counter(BaseCounter):
......@@ -205,10 +150,8 @@ class Counter(BaseCounter):
self._unit = unit
if grouped_read_handler:
Counter.GROUPED_READ_HANDLERS[self] = grouped_read_handler
parents_list = (
["counters", controller] if controller is not None else ["counters"]
)
session.get_current().map.register(self, parents_list, tag=self.name)
parents_list = ["counters"] + [controller] if controller is not None else []
global_map.register(self, parents_list, tag=self.name)
# Standard interface
......@@ -606,7 +549,7 @@ class CalcCounter(BaseCounter):
self.__name = name
self.__dependent_counters = dependent_counters
self.__calc_function = calc_function
session.get_current().map.register(self, ["counters"], tag=name)
global_map.register(self, ["counters"], tag=name)
@property
def name(self):
......@@ -640,15 +583,10 @@ class CalcCounter(BaseCounter):
@property
def acquisition_channels(self):
# Avoid circular import
from bliss.scanning.channel import AcquisitionChannel
return [AcquisitionChannel(self.controller, self.name, self.dtype, self.shape)]
def create_acquisition_device(self, scan_pars, device_dict=None, **settings):
# Avoid circular import
from bliss.scanning.acquisition.calc import CalcAcquisitionDevice
acq_devices = set()
counters = self.counters
counters.pop(0) # remove self
......
......@@ -5,9 +5,10 @@
# Copyright (c) 2015-2019 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import itertools
from bliss import setup_globals
from bliss.config import settings
from .session import get_current as _current_session
from bliss import current_session
class _active_mg_proxy(object):
......@@ -80,8 +81,7 @@ def get_active_name():
* return None (NoneType) if not found.
* !! this is only the name, the MG object may not exist.
"""
session = _current_session()
session_name = session.name if session is not None else "unnamed"
session_name = current_session.name
active_mg_name = settings.SimpleSetting("%s:active_measurementgroup" % session_name)
return active_mg_name.get()
......@@ -92,8 +92,7 @@ def set_active_name(name):
if name not in all_mg_names:
raise ValueError