Fix day filter
[cds-indico.git] / indico / util / signals.py
blobf96e1c15ef02b71518721ab1d1f9a10672740b25
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from itertools import izip_longest
18 from types import GeneratorType
21 def values_from_signal(signal_response, single_value=False, skip_none=True, as_list=False,
22 multi_value_types=GeneratorType, return_plugins=False):
23 """Combines the results from both single-value and multi-value signals.
25 The signal needs to return either a single object (which is not a
26 generator) or a generator (usually by returning its values using
27 `yield`).
29 :param signal_response: The return value of a Signal's `.send()` method
30 :param single_value: If each return value should be treated as a single
31 value in all cases (disables the generator check)
32 :param skip_none: If None return values should be skipped
33 :param as_list: If you want a list instead of a set (only use this if
34 you need non-hashable return values, the order is still
35 not defined!)
36 :param multi_value_types: Types which should be considered multi-value.
37 It is used in an `isinstance()` call and if
38 the check succeeds, the value is passed to
39 `list.extend()`
40 :param return_plugins: return `(plugin, value)` tuples instead of just
41 the values. `plugin` can be `None` if the signal
42 was not registered in a plugin.
43 :return: A set/list containing the results
44 """
45 values = []
46 for func, value in signal_response:
47 plugin = getattr(func, 'indico_plugin', None)
48 if not single_value and isinstance(value, multi_value_types):
49 values.extend(izip_longest([plugin], value, fillvalue=plugin))
50 else:
51 values.append((plugin, value))
52 if skip_none:
53 values = [(p, v) for p, v in values if v is not None]
54 if not return_plugins:
55 values = [v for p, v in values]
56 return values if as_list else set(values)
59 def named_objects_from_signal(signal_response, name_attr='name', plugin_attr=None):
60 """Returns a dict of objects based on an unique attribute on each object.
62 The signal needs to return either a single object (which is not a
63 generator) or a generator (usually by returning its values using
64 `yield`).
66 :param signal_response: The return value of a Signal's `.send()` method
67 :param name_attr: The attribute containing each object's unique name
68 :param plugin_attr: The attribute that will be set to the plugin containing
69 the object (set to `None` for objects in the core)
70 :return: dict mapping object names to objects
71 """
72 objects = values_from_signal(signal_response, return_plugins=True)
73 mapping = {getattr(cls, name_attr): cls for _, cls in objects}
74 if plugin_attr is not None:
75 for plugin, cls in objects:
76 setattr(mapping[getattr(cls, name_attr)], plugin_attr, plugin)
77 conflicting = {cls for _, cls in objects} - set(mapping.viewvalues())
78 if conflicting:
79 names = ', '.join(sorted(getattr(x, name_attr) for x in conflicting))
80 raise RuntimeError('Non-unique object names: {}'.format(names))
81 return mapping