Skip to content

Commit 118a511

Browse files
committed
Respect relative path names within configuration file
This applies to both the function file as well as module files.
1 parent 752d614 commit 118a511

File tree

6 files changed

+102
-17
lines changed

6 files changed

+102
-17
lines changed

CHANGES.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ mqttwarn changelog
66
in progress
77
===========
88

9+
10+
2021-06-03 0.22.0
11+
=================
12+
913
- [build] Fix unwanted cache hits when automatically building Docker images. Thanks, Gergő!
14+
- [core] Respect relative path names within configuration file. This applies
15+
to both the function file as well as module files.
1016

1117

1218
2021-06-03 0.21.0
@@ -20,7 +26,7 @@ in progress
2026
responds with MQTT publish. Thanks, Jörg!
2127
- [core] Remove "os.chdir" as it is apparently not needed anymore. Thanks, Dan!
2228
- [ci] Run tests on Python 3.9, remove testing on Python 3.5
23-
- [core] Load service plugins from both modules and files
29+
- [core] Load service plugins from both modules and files.
2430

2531

2632
2020-10-20 0.20.0

mqttwarn/configuration.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
# (c) 2014-2019 The mqttwarn developers
2+
# (c) 2014-2021 The mqttwarn developers
33
import os
44
import sys
55
import ast
@@ -35,6 +35,8 @@ def __init__(self, configuration_file, defaults=None):
3535
self.read_file(f)
3636
f.close()
3737

38+
self.configuration_path = os.path.dirname(configuration_file)
39+
3840
''' set defaults '''
3941
self.hostname = 'localhost'
4042
self.port = 1883
@@ -81,7 +83,20 @@ def __init__(self, configuration_file, defaults=None):
8183
self.tls_version = ssl.PROTOCOL_SSLv3
8284

8385
self.loglevelnumber = self.level2number(self.loglevel)
84-
self.functions = load_functions(self.functions)
86+
87+
# Load function file as given (backward-compatibility).
88+
if os.path.isfile(self.functions):
89+
functions_file = self.functions
90+
91+
# Load function file as given if path is absolute.
92+
elif os.path.isabs(self.functions):
93+
functions_file = self.functions
94+
95+
# Load function file relative to path of configuration file if path is relative.
96+
else:
97+
functions_file = os.path.join(self.configuration_path, self.functions)
98+
99+
self.functions = load_functions(functions_file)
85100

86101

87102
def level2number(self, level):

mqttwarn/core.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
# (c) 2014-2020 The mqttwarn developers
2+
# (c) 2014-2021 The mqttwarn developers
33
from builtins import object
44
from past.builtins import cmp
55
from builtins import chr
@@ -532,9 +532,15 @@ def load_services(services):
532532

533533
module = cf.g('config:' + service, 'module', service)
534534

535+
# Load external service from file.
536+
modulefile_candidates = []
535537
if module.endswith(".py"):
536-
modulefile = module
538+
# Add two candidates: a) Use the file as given and b) treat the file as relative to
539+
# the directory of the configuration file. That retains backward compatibility.
540+
modulefile_candidates.append(module)
541+
modulefile_candidates.append(os.path.join(cf.configuration_path, module))
537542

543+
# Load external service with module specification.
538544
elif '.' in module:
539545
logger.debug('Trying to load service "{}" from module "{}"'.format(service, module))
540546
try:
@@ -544,15 +550,19 @@ def load_services(services):
544550
except Exception as ex:
545551
logger.exception('Unable to load service "{}" from module "{}": {}'.format(service, module, ex))
546552

553+
# Load built-in service module.
547554
else:
548-
modulefile = resource_filename('mqttwarn.services', module + '.py')
555+
modulefile_candidates = [ resource_filename('mqttwarn.services', module + '.py') ]
549556

550-
logger.debug('Trying to load service "{}" from file "{}"'.format(service, modulefile))
551-
try:
552-
service_plugins[service]['module'] = load_module_from_file(modulefile)
553-
logger.info('Successfully loaded service "{}"'.format(service))
554-
except Exception as ex:
555-
logger.exception('Unable to load service "{}" from file "{}": {}'.format(service, modulefile, ex))
557+
for modulefile in modulefile_candidates:
558+
if not os.path.isfile(modulefile):
559+
continue
560+
logger.debug('Trying to load service "{}" from file "{}"'.format(service, modulefile))
561+
try:
562+
service_plugins[service]['module'] = load_module_from_file(modulefile)
563+
logger.info('Successfully loaded service "{}"'.format(service))
564+
except Exception as ex:
565+
logger.exception('Unable to load service "{}" from file "{}": {}'.format(service, modulefile, ex))
556566

557567

558568
def connect():

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33

44
# Configuration- and function files used by the test harness
55
configfile = 'tests/selftest.ini'
6+
configfile_v2 = 'tests/selftest_v2.ini'
67
funcfile = 'tests/selftest.py'
78
bad_funcfile = 'tests/bad_funcs.py'

tests/selftest_v2.ini

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# -*- coding: utf-8 -*-
2+
# (c) 2014-2021 The mqttwarn developers
3+
#
4+
# mqttwarn configuration file for testing function and module loading with relative paths.
5+
#
6+
7+
; ------------------------------------------
8+
; Base configuration
9+
; ------------------------------------------
10+
11+
[defaults]
12+
13+
14+
; --------
15+
; Services
16+
; --------
17+
18+
; path to file containing self-defined functions for formatmap and datamap
19+
; generate with "mqttwarn make-samplefuncs"
20+
functions = 'selftest.py'
21+
22+
; name the service providers you will be using.
23+
launch = tests.acme.foobar, acme/foobar.py
24+
25+
26+
[config:tests.acme.foobar]
27+
targets = {
28+
'default' : [ 'default' ],
29+
}
30+
31+
[config:acme/foobar.py]
32+
targets = {
33+
'default' : [ 'default' ],
34+
}
35+
36+
37+
; -------
38+
; Targets
39+
; -------
40+
41+
[test/plugin-module]
42+
; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/plugin-module -l
43+
targets = tests.acme.foobar:default
44+
format = {name}: {value}
45+
46+
[test/plugin-file]
47+
; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/plugin-file -l
48+
targets = acme/foobar.py:default
49+
format = {name}: {value}

tests/test_core.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from builtins import str
88
import logging
99

10+
import pytest
11+
1012
from mqttwarn.core import make_service, decode_payload
11-
from tests import configfile
13+
from tests import configfile, configfile_v2
1214
from tests.util import core_bootstrap, send_message
1315

1416

@@ -167,9 +169,10 @@ def test_message_file_unicode():
167169
assert u'Räuber Hotzenplotz' in content, content
168170

169171

170-
def test_plugin_module(caplog):
172+
@pytest.mark.parametrize("configfile", [configfile, configfile_v2])
173+
def test_plugin_module(caplog, configfile):
171174
"""
172-
Check if using a module with dotted name also works.
175+
Check if loading a service module with dotted name works.
173176
"""
174177

175178
with caplog.at_level(logging.DEBUG):
@@ -184,9 +187,10 @@ def test_plugin_module(caplog):
184187
assert 'Plugin invoked' in caplog.text, caplog.text
185188

186189

187-
def test_plugin_file(caplog):
190+
@pytest.mark.parametrize("configfile", [configfile, configfile_v2])
191+
def test_plugin_file(caplog, configfile):
188192
"""
189-
Check if using a module with dotted name also works.
193+
Check if loading a service module from a file works.
190194
"""
191195

192196
with caplog.at_level(logging.DEBUG):

0 commit comments

Comments
 (0)