more debug output, minor fixes
lgz committed
Oct 17, 2017 at 16:14 UTC
3ff50459a93f4ebe0f6f454d8ccf5e9b673b8b62
3 files changed
+151
-135
plugins.d/python.d.plugin
+91
-81
@@ -1,4 +1,3 @@
1
-
1
#!/usr/bin/env bash
2
'''':; exec "$(command -v python || command -v python3 || command -v python2 ||
3
echo "ERROR python IS NOT AVAILABLE IN THIS SYSTEM")" "$0" "$@" # '''
@@ -39,7 +38,7 @@ from bases.loggers import PythonDLogger
38
from bases.collection import setdefault_values, run_and_exit
39
40
BASE_CONFIG = {'update_every': os.getenv('NETDATA_UPDATE_EVERY', 1),
42
- 'retries': 10,
41
+ 'retries': 20,
42
'priority': 60000,
43
'autodetection_retry': 0,
44
'name': str()}
@@ -48,11 +47,6 @@ BASE_CONFIG = {'update_every': os.getenv('NETDATA_UPDATE_EVERY', 1),
47
MODULE_EXTENSION = '.chart.py'
48
OBSOLETE_MODULES = ['apache_cache', 'gunicorn_log', 'nginx_log']
49
51
-JOB_INITIALIZE_MSG = 'Job initialization {status}. Module: "{module_name}", job: "{job_name}"{error}.'
52
-JOB_CREATE_CHARTS_MSG = 'CREATE {status}. Number of charts: {number_of_charts}.'
53
-MODULE_LOAD_MSG = 'Module load {status}. Module: "{module_name}", initialized jobs: {jobs_number}.'
54
-UNHANDLED_EXCEPTION = 'Unhandled exception in {method_name}(). Error: {error}.'
55
-
50
51
def module_ok(m):
52
return m.endswith(MODULE_EXTENSION) and m[:-len(MODULE_EXTENSION)] not in OBSOLETE_MODULES
@@ -73,18 +67,17 @@ def multi_job_check(config):
67
68
69
class Job(object):
76
- def __init__(self, initialized_job, module_name, internal_name):
70
+ def __init__(self, initialized_job, job_id):
71
"""
78
- :param initialized_job: <Class Service>
79
- :param module_name: <str>
80
- :param internal_name: <str>
72
+ :param initialized_job: instance of <Class Service>
73
+ :param job_id: <str>
74
"""
75
self.job = initialized_job
83
- self.internal_name = internal_name # key in Modules.jobs
84
- self.module_name = module_name # needed in Plugin.delete_job
76
+ self.id = job_id # key in Modules.jobs()
77
+ self.module_name = self.job.__module__ # used in Plugin.delete_job()
78
self.recheck_every = self.job.configuration.pop('autodetection_retry')
86
- self.checked = False # needed in Plugin.check_job
87
- self.created = False # needed in Plugin.create_job_charts
79
+ self.checked = False # used in Plugin.check_job()
80
+ self.created = False # used in Plugin.create_job_charts()
81
if OVERRIDE_UPDATE_EVERY:
82
self.job.update_every = int(OVERRIDE_UPDATE_EVERY)
83
@@ -112,13 +105,14 @@ class Module(object):
105
"""
106
self.service = service
107
self.name = service.__name__
115
- self.config = self.build_jobs_configurations(config)
108
+ self.config = self.jobs_configurations_builder(config)
109
self.jobs = OrderedDict()
110
+ self.counter = 1
111
112
self.initialize_jobs()
113
114
def __repr__(self):
121
- return '<Class Module "{name}">'.format(name=self.name)
115
+ return "<Class Module '{name}'>".format(name=self.name)
116
117
def __iter__(self):
118
return iter(OrderedDict(self.jobs).values())
@@ -133,58 +127,64 @@ class Module(object):
127
return len(self.jobs)
128
129
def __bool__(self):
136
- return bool(len(self.jobs))
130
+ return bool(self.jobs)
131
132
def __nonzero__(self):
133
return self.__bool__()
134
141
- def build_jobs_configurations(self, config):
135
+ @staticmethod
136
+ def jobs_configurations_builder(config):
137
"""
138
:param config: <dict>
139
:return:
140
"""
141
+ counter = 0
142
+ job_base_config = dict()
143
+
144
+ for attr in BASE_CONFIG:
145
+ job_base_config[attr] = config.pop(attr, BASE_CONFIG[attr])
146
+
147
if not config:
147
- return {self.name: dict(BASE_CONFIG)}
148
- if not multi_job_check(config):
149
- return {self.name: setdefault_values(config, BASE_CONFIG)}
150
- else:
151
- jobs, jobs_base_config = OrderedDict(), dict()
152
- for attr in BASE_CONFIG:
153
- jobs_base_config[attr] = config.pop(attr, BASE_CONFIG[attr])
154
- for job_name in config:
155
- if not isinstance(config[job_name], dict):
156
- continue
157
-
158
- defaulted = setdefault_values(config[job_name], base_dict=jobs_base_config)
159
- config[job_name]['name'] = sub(r'\s+', '_', config[job_name]['name'])
160
- job_name = sub(r'\s+', '_', job_name)
161
- job_internal_name = '_'.join([self.name, job_name])
162
- jobs[job_internal_name] = defaulted
163
- return jobs
148
+ config = {str(): dict()}
149
+ elif not multi_job_check(config):
150
+ config = {str(): config}
151
+
152
+ for job_name in config:
153
+ if not isinstance(config[job_name], dict):
154
+ continue
155
+
156
+ job_config = setdefault_values(config[job_name], base_dict=job_base_config)
157
+ job_name = sub(r'\s+', '_', job_name)
158
+ config[job_name]['name'] = sub(r'\s+', '_', config[job_name]['name'])
159
+ counter += 1
160
+ job_id = 'job' + str(counter).zfill(3)
161
+
162
+ yield job_id, job_name, job_config
163
164
def initialize_jobs(self):
166
- for job_internal_name in self.config:
167
- job_name = job_internal_name[len(self.name) + 1:]
168
- job_override_name = self.config[job_internal_name].pop('name')
169
- self.config[job_internal_name]['module_name'] = self.name
170
- self.config[job_internal_name]['job_name'] = job_name
171
- self.config[job_internal_name]['override_name'] = job_override_name
165
+ """
166
+ :return:
167
+ """
168
+ for job_id, job_name, job_config in self.config:
169
+ job_config['job_name'] = job_name
170
+ job_config['override_name'] = job_config.pop('name')
171
+
172
try:
173
- initialized_job = self.service.Service(configuration=self.config[job_internal_name])
173
+ initialized_job = self.service.Service(configuration=job_config)
174
except Exception as error:
175
- Logger.error(JOB_INITIALIZE_MSG.format(status='FAILED',
176
- module_name=self.name,
177
- job_name=job_name,
178
- error=', error: {0}'.format(error)))
175
+ Logger.error("job initialization: '{module_name} {job_name}' "
176
+ "=> [{status}] ({error})".format(status='FAILED',
177
+ module_name=self.name,
178
+ job_name=job_name,
179
+ error=error))
180
continue
181
else:
181
- Logger.debug(JOB_INITIALIZE_MSG.format(status='SUCCESS',
182
- module_name=self.name,
183
- job_name=job_name,
184
- error=''))
185
- self.jobs[job_internal_name] = Job(initialized_job=initialized_job,
186
- module_name=self.name,
187
- internal_name=job_internal_name)
182
+ Logger.debug("job initialization: '{module_name} {job_name}' "
183
+ "=> [{status}]".format(status='OK',
184
+ module_name=self.name,
185
+ job_name=job_name or self.name))
186
+ self.jobs[job_id] = Job(initialized_job=initialized_job,
187
+ job_id=job_id)
188
del self.config
189
del self.service
190
@@ -194,7 +194,7 @@ class Plugin(object):
194
self.loader = ModuleAndConfigLoader()
195
self.modules = OrderedDict()
196
self.sleep_time = 1
197
- self.number_of_runs = 0
197
+ self.runs_counter = 0
198
self.config, error = self.loader.load_config_from_file(PLUGIN_CONFIG_DIR + 'python.d.conf')
199
if error:
200
run_and_exit(Logger.error)(error)
@@ -207,7 +207,7 @@ class Plugin(object):
207
run_and_exit(Logger.info)('No modules to run. Exit...')
208
209
def __iter__(self):
210
- return iter(dict(self.modules).values())
210
+ return iter(OrderedDict(self.modules).values())
211
212
@property
213
def jobs(self):
@@ -240,19 +240,31 @@ class Plugin(object):
240
241
def load_and_initialize_modules(self):
242
for mod, mod_name, mod_path, conf_path in self.enabled_modules():
243
+
244
+ # Load module from file ------------------------------------------------------------
245
loaded_module, error = self.loader.load_module_from_file(mod_name, mod_path)
246
+ log = Logger.error if error else Logger.debug
247
+ log("module load source: '{module_name}' => [{status}]".format(status='FAILED' if error else 'OK',
248
+ module_name=mod_name))
249
if error:
245
- Logger.error('Module load failed. Module: {name}, error: {error}.'.format(name=mod,
246
- error=error))
250
+ Logger.error("load source error : {0}".format(error))
251
continue
252
+
253
+ # Load module config from file ------------------------------------------------------
254
loaded_config, error = self.loader.load_config_from_file(conf_path)
255
+ log = Logger.error if error else Logger.debug
256
+ log("module load config: '{module_name}' => [{status}]".format(status='FAILED' if error else 'OK',
257
+ module_name=mod_name))
258
if error:
250
- Logger.error('Config load failed. Module {module}, error: {error}.'.format(module=mod_name,
251
- error=error))
259
+ Logger.error('load config error : {0}'.format(error))
260
+
261
+ # Service instance initialization ---------------------------------------------------
262
initialized_module = Module(service=loaded_module, config=loaded_config)
253
- Logger.debug(MODULE_LOAD_MSG.format(status='OK' if initialized_module else 'FAILED',
254
- module_name=initialized_module.name,
255
- jobs_number=len(initialized_module)))
263
+ Logger.debug("module status: '{module_name}' => [{status}] "
264
+ "(jobs: {jobs_number})".format(status='OK' if initialized_module else 'FAILED',
265
+ module_name=initialized_module.name,
266
+ jobs_number=len(initialized_module)))
267
+
268
if initialized_module:
269
self.modules[initialized_module.name] = initialized_module
270
@@ -263,17 +275,11 @@ class Plugin(object):
275
:return:
276
"""
277
try:
266
- check_ok = job.check()
278
+ check_ok = bool(job.check())
279
except Exception as error:
268
- job.error(UNHANDLED_EXCEPTION.format(method_name='check',
269
- error=error))
270
- return False
280
+ job.error('check() unhandled exception: {error}'.format(error=error))
281
+ return None
282
else:
272
- log = job.info if check_ok else job.error
273
- log('CHECK {status}'.format(status='OK' if check_ok else 'FAILED'))
274
- if job.is_autodetect():
275
- if not check_ok:
276
- job.error('Next CHECK in {0} seconds.'.format(job.recheck_every))
283
return check_ok
284
285
@staticmethod
@@ -285,12 +291,9 @@ class Plugin(object):
291
try:
292
create_ok = job.create()
293
except Exception as error:
288
- job.error(UNHANDLED_EXCEPTION.format(method_name='create',
289
- error=error))
294
+ job.error('create() unhandled exception: {error}'.format(error=error))
295
return False
296
else:
292
- job.debug(JOB_CREATE_CHARTS_MSG.format(status='OK' if create_ok else 'FAILED',
293
- number_of_charts=len(job.charts)))
297
return create_ok
298
299
def delete_job(self, job):
@@ -298,31 +301,38 @@ class Plugin(object):
301
:param job: <Job>
302
:return:
303
"""
301
- del self.modules[job.module_name][job.internal_name]
304
+ del self.modules[job.module_name][job.id]
305
306
def run_check(self):
307
checked = list()
308
for job in self.jobs:
309
if job.name in checked:
307
- Logger.info('DROPPED: {job_name}. Already served by another job.'.format(job_name=job.internal_name))
310
+ job.info('check() => [DROPPED] (already served by another job)')
311
self.delete_job(job)
312
continue
313
ok = self.check_job(job)
314
if ok:
315
+ job.info('check() => [OK]')
316
checked.append(job.name)
317
job.checked = True
318
continue
315
- if not job.is_autodetect():
319
+ if not job.is_autodetect() or ok is None:
320
+ job.error('check() => [FAILED]')
321
self.delete_job(job)
322
+ else:
323
+ job.error('check() => [RECHECK] (autodetection_retry: {0})'.format(job.recheck_every))
324
325
def run_create(self):
326
for job in self.jobs:
327
if not job.checked:
328
+ # skip autodetection_retry jobs
329
continue
330
ok = self.create_job_charts(job)
331
if ok:
332
+ job.debug('create() => [OK] (charts: {0})'.format(len(job.charts)))
333
job.created = True
334
continue
335
+ job.error('create() => [FAILED] (charts: {0})'.format(len(job.charts)))
336
self.delete_job(job)
337
338
def start(self):
@@ -348,9 +358,9 @@ class Plugin(object):
358
del self.modules[mod.name]
359
360
def autodetect_retry(self):
351
- self.number_of_runs += self.sleep_time
361
+ self.runs_counter += self.sleep_time
362
for job in self.autodetect_jobs:
353
- if self.number_of_runs % job.recheck_every == 0:
363
+ if self.runs_counter % job.recheck_every == 0:
364
checked = self.check_job(job)
365
if checked:
366
created = self.create_job_charts(job)
python.d/python_modules/bases/FrameworkServices/SimpleService.py
+43
-42
@@ -16,12 +16,6 @@ from bases.loggers import PythonDLimitedLogger
16
17
CHART_OBSOLETE_PENALTY = 10
18
19
-START_MSG = 'STARTED. Update frequency: {freq}, retries: {retries}.'
20
-UPDATE_MSG = 'UPDATE {status}. Elapsed time: {elapsed}, retries left: {retries}.'
21
-STOP_MSG = 'STOPPED after {retries_max} data collection failures in a row.'
22
-SLEEP_MSG = 'SLEEPING for {sleep_time} to reach frequency of {freq} sec.'
23
-UNHANDLED_EXCEPTION = 'Unhandled exception in {method_name}(). Error: {error}.'
24
-
19
RUNTIME_CHART_UPDATE = 'BEGIN netdata.runtime_{job_name} {since_last}\n' \
20
'SET run_time = {elapsed}\n' \
21
'END\n'
@@ -32,14 +26,14 @@ class RuntimeCounters:
26
"""
27
:param configuration: <dict>
28
"""
35
- self.FREQ = int(configuration.pop('update_every', 1))
29
+ self.FREQ = int(configuration.pop('update_every'))
30
self.START_RUN = 0
31
self.NEXT_RUN = 0
32
self.PREV_UPDATE = 0
33
self.SINCE_UPDATE = 0
34
self.ELAPSED = 0
35
self.RETRIES = 0
42
- self.RETRIES_MAX = configuration.pop('retries', 10)
36
+ self.RETRIES_MAX = configuration.pop('retries')
37
38
def is_sleep_time(self):
39
return self.START_RUN < self.NEXT_RUN
@@ -60,15 +54,17 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
54
PythonDLimitedLogger.__init__(self)
55
OldVersionCompatibility.__init__(self)
56
self.configuration = configuration
63
- self.module_name = configuration.pop('module_name')
57
+ self.order = list()
58
+ self.definitions = dict()
59
+
60
+ self.module_name = self.__module__
61
self.job_name = configuration.pop('job_name')
62
self.override_name = configuration.pop('override_name')
63
self.fake_name = None
67
- self.order = list()
68
- self.definitions = dict()
64
+
65
self._runtime_counters = RuntimeCounters(configuration=configuration)
66
self.charts = Charts(job_name=self.actual_name,
71
- priority=configuration.pop('priority', 60000),
67
+ priority=configuration.pop('priority'),
68
update_every=self.update_every)
69
70
def __repr__(self):
@@ -101,29 +97,24 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
97
check() prototype
98
:return: boolean
99
"""
104
- try:
105
- data = self._get_data()
106
- except Exception as error:
107
- self.debug('CHECK {{error: {error}}}'.format(error=error))
108
- else:
109
- if data and isinstance(data, dict):
110
- return True
111
- self.debug('CHECK returned no data')
112
- return False
100
+ self.debug("job doesn't implement check() method. Using default which simply invokes get_data().")
101
+ data = self.get_data()
102
+ if data and isinstance(data, dict):
103
+ return True
104
+ self.debug('returned value is wrong: {0}'.format(data))
105
+ return False
106
107
@create_runtime_chart
108
def create(self):
109
for chart_name in self.order:
110
chart_config = self.definitions.get(chart_name)
111
if not chart_config:
119
- self.debug('{chart_name} not in definitions'.format(chart_name=chart_name))
112
+ self.debug("create() chart '{chart_name}' not in definitions. "
113
+ "Skipping it.".format(chart_name=chart_name))
114
continue
115
122
- chart_params = ([chart_name] + chart_config['options'])
123
- ok = self.charts.add_chart(params=chart_params)
124
- if not ok:
125
- self.debug('"{chart}" chart no added'.format(chart=chart_name))
126
- continue
116
+ chart_params = [chart_name] + chart_config['options']
117
+ self.charts.add_chart(params=chart_params)
118
119
for line in chart_config['lines']:
120
self.charts[chart_name].add_dimension(line)
@@ -131,12 +122,10 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
122
del self.order
123
del self.definitions
124
134
- if self.charts.empty():
135
- return None
136
-
125
for chart in self.charts:
126
safe_print(chart.create())
139
- return True
127
+
128
+ return bool(self.charts)
129
130
def run(self):
131
"""
@@ -145,7 +134,8 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
134
:return: None
135
"""
136
job = self._runtime_counters
148
- self.debug(START_MSG.format(freq=job.FREQ, retries=job.RETRIES_MAX - job.RETRIES))
137
+ self.debug('started, update frequency: {freq}, '
138
+ 'retries: {retries}'.format(freq=job.FREQ, retries=job.RETRIES_MAX - job.RETRIES))
139
140
while True:
141
job.START_RUN = time()
@@ -160,8 +150,7 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
150
try:
151
updated = self.update(interval=job.SINCE_UPDATE)
152
except Exception as error:
163
- self.debug(UNHANDLED_EXCEPTION.format(method_name='update',
164
- error=error))
153
+ self.debug('update() unhandled exception: {error}'.format(error=error))
154
updated = False
155
156
if not updated:
@@ -175,9 +164,10 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
164
safe_print(RUNTIME_CHART_UPDATE.format(job_name=self.name,
165
since_last=job.SINCE_UPDATE,
166
elapsed=job.ELAPSED))
178
- self.debug(UPDATE_MSG.format(status='OK' if updated else 'FAILED',
179
- elapsed=job.ELAPSED if updated else '-',
180
- retries=job.RETRIES_MAX - job.RETRIES))
167
+ self.debug('update => [{status}] (elapsed time: {elapsed}, '
168
+ 'retries left: {retries})'.format(status='OK' if updated else 'FAILED',
169
+ elapsed=job.ELAPSED if updated else '-',
170
+ retries=job.RETRIES_MAX - job.RETRIES))
171
172
def update(self, interval):
173
"""
@@ -186,16 +176,22 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
176
"""
177
data = self.get_data()
178
if not data:
189
- return None
179
+ self.debug('get_data() returns no data')
180
+ return False
181
+ elif not isinstance(data, dict):
182
+ self.debug('get_data() returns incorrect type data')
183
+ return False
184
+
185
charts_updated = False
186
187
for chart in self.charts.penalty_exceeded(penalty_max=CHART_OBSOLETE_PENALTY):
188
safe_print(chart.obsolete())
189
del self.charts[chart.params['id']]
190
+ self.error('chart "{0}" was removed due to non updating'.format(chart.name))
191
192
for chart in self.charts:
193
dimension_updated = str()
198
- for dimension in chart.dimensions:
194
+ for dimension in chart:
195
try:
196
value = int(data[dimension.params['id']])
197
except (KeyError, TypeError):
@@ -208,12 +204,17 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
204
dimension_updated, 'END\n']))
205
else:
206
chart.penalty += 1
207
+
208
+ if not charts_updated:
209
+ self.debug('none of the charts have been updated')
210
+
211
return charts_updated
212
213
def manage_retries(self):
214
self._runtime_counters.RETRIES += 1
215
if self._runtime_counters.RETRIES >= self._runtime_counters.RETRIES_MAX:
216
- self.error(STOP_MSG.format(retries_max=self._runtime_counters.RETRIES_MAX))
216
+ self.error('stopped after {retries_max} data '
217
+ 'collection failures in a row'.format(retries_max=self._runtime_counters.RETRIES_MAX))
218
return False
219
return True
220
@@ -223,8 +224,8 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec
224
# sleep() is interruptable
225
while job.is_sleep_time():
226
sleep_time = job.NEXT_RUN - job.START_RUN
226
- self.debug(SLEEP_MSG.format(sleep_time=sleep_time,
227
- freq=job.FREQ))
227
+ self.debug('sleeping for {sleep_time} to reach frequency of {freq} sec'.format(sleep_time=sleep_time,
228
+ freq=job.FREQ))
229
sleep(sleep_time)
230
job.START_RUN = time()
231
python.d/python_modules/bases/charts.py
+17
-12
@@ -39,7 +39,7 @@ def create_runtime_chart(func):
39
return wrapper
40
41
42
-class ReAddingError(Exception):
42
+class DuplicateError(Exception):
43
pass
44
45
@@ -73,8 +73,11 @@ class Charts:
73
def __delitem__(self, key):
74
del self.charts[key]
75
76
- def empty(self):
77
- return not bool(self.charts)
76
+ def __bool__(self):
77
+ return bool(self.charts)
78
+
79
+ def __nonzero__(self):
80
+ return self.__bool__()
81
82
def penalty_exceeded(self, penalty_max):
83
"""
@@ -91,7 +94,7 @@ class Charts:
94
params = [self.job_name()] + params
95
chart_id = params[1]
96
if chart_id in self.charts:
94
- raise ReAddingError('{chart} already in charts'.format(chart=chart_id))
97
+ raise DuplicateError('{chart} already in charts'.format(chart=chart_id))
98
else:
99
new_chart = Chart(params)
100
new_chart.params['update_every'] = self.update_every
@@ -106,11 +109,12 @@ class Chart:
109
"""
110
:param params: <list>
111
"""
109
- if not (params and isinstance(params, list)):
110
- raise ValueError('Chart params must be a not empty list')
112
+ if not (params and isinstance(params, list) and len(params) >= 8):
113
+ raise ValueError('Chart params must be a list with 7 items')
114
115
self.params = dict(zip(CHART_PARAMS, (p or str() for p in params)))
113
- self.name = '.'.join([self.params['type'], self.params['id']])
116
+ self.name = '{type}.{id}'.format(type=self.params['type'],
117
+ id=self.params['id'])
118
if self.params.get('chart_type') not in CHART_TYPES:
119
self.params['chart_type'] = 'absolute'
120
@@ -135,8 +139,8 @@ class Chart:
139
:return:
140
"""
141
if dimension[0] in [repr(d) for d in self.dimensions]:
138
- raise ReAddingError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
139
- chart=self.name))
142
+ raise DuplicateError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
143
+ chart=self.name))
144
self.dimensions.append(Dimension(dimension))
145
146
def add_dimension_and_push_chart(self, dimension):
@@ -145,8 +149,8 @@ class Chart:
149
:return:
150
"""
151
if dimension[0] in [repr(d) for d in self.dimensions]:
148
- raise ReAddingError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
149
- chart=self.name))
152
+ raise DuplicateError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
153
+ chart=self.name))
154
dimension = Dimension(dimension)
155
self.dimensions.append(dimension)
156
safe_print(self.create(dimension))
@@ -185,7 +189,8 @@ class Dimension:
189
raise ValueError('Dimension params must be a not empty list')
190
191
self.params = dict(zip(DIMENSION_PARAMS, (p or str() for p in params)))
188
- self.params['name'] = self.params.get('name') or self.params.get('id')
192
+ self.params['name'] = self.params.get('name') or self.params['id']
193
+
194
if self.params.get('algorithm') not in DIMENSION_ALGORITHMS:
195
self.params['algorithm'] = 'absolute'
196
if not str(self.params.get('multiplier')).isdigit():