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
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
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
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
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
))
51 values
.append((plugin
, value
))
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
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
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())
79 names
= ', '.join(sorted(getattr(x
, name_attr
) for x
in conflicting
))
80 raise RuntimeError('Non-unique object names: {}'.format(names
))