common.py 7.03 KB
Newer Older
Yannick Roehlly's avatar
Yannick Roehlly committed
1
# -*- coding: utf-8 -*-
2
3
4
# Copyright (C) 2012, 2013 Centre de données Astrophysiques de Marseille
# Licensed under the CeCILL-v2 licence - see Licence_CeCILL_V2-en.txt
# Author: Yannick Roehlly <yannick.roehlly@oamp.fr>
Yannick Roehlly's avatar
Yannick Roehlly committed
5

6
7
import os
import inspect
8
from importlib import import_module
9
from collections import OrderedDict
Yannick Roehlly's avatar
Yannick Roehlly committed
10
11


12
13
14
15
16
17
18
19
def complete_parameters(given_parameters, parameter_list):
    """Complete the given parameter list with the default values

    Complete the given_parameters dictionary with missing parameters that have
    a default value in the parameter_list. If a parameter from parameter_list
    have no default value and is not present in given_parameters, raises an
    error. If a parameter is present in given_parameters and not in
    parameter_list, an exception is also raised.
20
21
    Returns an ordered dictionary with the same key order as the parameter
    list.
22
23
24
25
26

    Parameters
    ----------
    given_parameters : dictionary
        Parameter dictionary used to configure the module.
27
    parameter_list : OrderedDict
28
29
30
31
        Parameter list from the module.

    Returns
    -------
32
33
34
    parameters : OrderedDict
        Ordered dictionary combining the given parameters with the default
        values for the missing ones.
35
36
37
38
39
40

    Raises
    ------
    KeyError when the given parameters are different from the expected ones.

    """
41
    # Complete the given parameters with default values when needed.
42
43
44
45
    for key in parameter_list:
        if (not key in given_parameters) and (
                parameter_list[key][2] is not None):
            given_parameters[key] = parameter_list[key][2]
46
47
    # Check parameter consistency between the parameter list and the given
    # parameters.
48
49
50
51
52
53
54
55
56
57
58
59
60
    if not set(given_parameters.keys()) == set(parameter_list.keys()):
        missing_parameters = (set(parameter_list.keys())
                              - set(given_parameters.keys()))
        unexpected_parameters = (set(given_parameters.keys())
                                 - set(parameter_list.keys()))
        message = ""
        if missing_parameters:
            message += ("Missing parameters: " +
                        ", ".join(missing_parameters) +
                        ". ")
        if unexpected_parameters:
            message += ("Unexpected parameters: " +
                        ", ".join(unexpected_parameters) +
61
                        ". ")
62
        raise KeyError("The parameters passed are different from the "
63
                       "expected one. " + message)
64

65
66
67
68
69
70
    # We want the result to be ordered as the parameter_list of the module is.
    result = OrderedDict()
    for key in parameter_list.keys():
        result[key] = given_parameters[key]

    return result
71
72


Yannick Roehlly's avatar
Yannick Roehlly committed
73
74
75
76
class SEDCreationModule(object):
    """Abstract class, the pCigale SED creation modules are based on.
    """

77
78
79
80
81
82
83
84
85
86
87
88
    # parameter_list is an ordered dictionary containing all the parameters
    # used by the module. Each parameter name is associate to a tuple
    # (variable type, description [string], default value). Each module must
    # define its parameter list, unless it does not use any parameter. Using
    # None means that there is no description or default value. If None should
    # be the default value, use the 'None' string instead.
    parameter_list = OrderedDict()

    # out_parameter_list is an ordered dictionary containing all the SED
    # parameters that are added to the SED info dictionary and for which a
    # statistical analysis may be done. Each parameter name is associated with
    # its description. In the SED info dictionary, the parameter name in
89
90
    # is postfixed with a same postfix used in the module name, to allow
    # the use of repeated modules.
91
    out_parameter_list = OrderedDict()
Yannick Roehlly's avatar
Yannick Roehlly committed
92

93
94
95
96
97
    # comments is the text that is used to comment the module section in
    # the configuration file. For instance, it can be used to give special
    # instructions for the configuration.
    comments = ""

98
    def __init__(self, name=None, blank=False, **kwargs):
Yannick Roehlly's avatar
Yannick Roehlly committed
99
100
        """Instantiate a SED creation module

101
        A name can be given to the module. This can be useful when a same
Yannick Roehlly's avatar
Yannick Roehlly committed
102
        module is used several times with different parameters in the SED
103
104
        creation process.

Yannick Roehlly's avatar
Yannick Roehlly committed
105
        The module parameters must be passed as keyword parameters. If a
106
107
108
109
110
111
112
113
114
        parameter is not given but exists in the parameter_list with a default
        value, this value is used. If a parameter is missing or if an
        unexpected parameter is given, an error will be raised.

        Parameters
        ----------
        name : string
            Name of the module.
        blank : boolean
Yannick Roehlly's avatar
Yannick Roehlly committed
115
            If true, return a non-parametrised module that will be used only
116
117
            to query the module parameter list.

Yannick Roehlly's avatar
Yannick Roehlly committed
118
        The module parameters must be given as keyword parameters.
119
120
121
122
123
124

        Raises
        ------
        KeyError : when not all the needed parameters are given or when an
                   unexpected parameter is given.

Yannick Roehlly's avatar
Yannick Roehlly committed
125
        """
126
127
128
129
130
131
132
133
134
135
        # If a name is not given, we take if from the file in which the
        # module class is coded.
        self.name = name or os.path.basename(inspect.getfile(self))[:4]

        # We want to postfix the various keys of the SED with the same
        # postfix as the module name, if any.
        if '.' in name:
            self.postfix = "." + name.split(".", 1)
        else:
            self.postfix = ""
136

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
        if not blank:
            # Parameters given in constructor.
            parameters = kwargs

            # Complete the parameter dictionary and "export" it to the module
            self.parameters = complete_parameters(parameters,
                                                  self.parameter_list)

            # Run the initialisation code specific to the module.
            self._init_code()

    def _init_code(self):
        """Initialisation code specific to the module.

        For instance, a module taking data in the database can use this method
        to do so, only one time when the module instantiates.

        """
        pass
Yannick Roehlly's avatar
Yannick Roehlly committed
156

157
    def process(self, sed):
Yannick Roehlly's avatar
Yannick Roehlly committed
158
159
160
161
162
        """Process a SED object with the module

        The SED object is updated during the process, one must take care of
        copying it before, if needed.

Yannick Roehlly's avatar
Yannick Roehlly committed
163
        Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
164
165
166
167
        ----------
        sed  : pcigale.sed.SED object

        """
168
        raise NotImplementedError()
Yannick Roehlly's avatar
Yannick Roehlly committed
169

170
171

def get_module(name, **kwargs):
172
    """Get a SED creation module from its name
Yannick Roehlly's avatar
Yannick Roehlly committed
173

Yannick Roehlly's avatar
Yannick Roehlly committed
174
    Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
175
    ----------
Yannick Roehlly's avatar
Yannick Roehlly committed
176
    module_name : string
177
178
        The name of the module we want to get the class. This name can be
        prefixed by anything using a dot, then the part before the dot is
179
180
        used to determine the module to load (e.g. 'dh2002.1' will return
        the 'dh2002' module).
Yannick Roehlly's avatar
Yannick Roehlly committed
181
182
183

    Returns
    -------
184
    a pcigale.sed.modules.Module instance
Yannick Roehlly's avatar
Yannick Roehlly committed
185
    """
186
187
188
    # Determine the real module name by removing the dotted prefix.
    module_name = name.split('.')[0]

Yannick Roehlly's avatar
Yannick Roehlly committed
189
    try:
190
        module = import_module("." + module_name, 'pcigale.sed.modules')
191
        return module.Module(name=name, **kwargs)
Yannick Roehlly's avatar
Yannick Roehlly committed
192
    except ImportError:
Yannick Roehlly's avatar
Yannick Roehlly committed
193
        print('Module ' + module_name + ' does not exists!')
Yannick Roehlly's avatar
Yannick Roehlly committed
194
        raise