"""This module gives a way to access the configuration of a locally installed
instance of Snips, a Snips assistant and a Snips skill.
Classes:
- :class:`.AppConfig`: Gives access to the configuration of a Snips app,
stored in an INI file.
- :class:`.AssistantConfig`: Gives access to the configuration of a Snips
assistant, stored in a JSON file.
- :class:`.MQTTAuthConfig`: Represents the authentication settings for a
connection to an MQTT broker.
- :class:`.MQTTConfig`: Represents the configuration for a connection to an
MQTT broker.
- :class:`.MQTTTLSConfig`: Represents the TLS settings for a connection to an
MQTT broker.
- :class:`.SnipsConfig`: Gives access to the configuration of a locally
installed instance of Snips, stored in a TOML file.
"""
from collections import UserDict
from configparser import ConfigParser
import json
from pathlib import Path
from snipskit.exceptions import AssistantConfigNotFoundError, \
SnipsConfigNotFoundError
from snipskit.tools import find_path
import toml
SEARCH_PATH_SNIPS = ['/etc/snips.toml', '/usr/local/etc/snips.toml']
SEARCH_PATH_ASSISTANT = ['/usr/share/snips/assistant/assistant.json',
'/usr/local/share/snips/assistant/assistant.json']
DEFAULT_BROKER = 'localhost:1883'
[docs]class AppConfig(ConfigParser):
"""This class gives access to the configuration of a Snips app as a
:class:`configparser.ConfigParser` object.
Attributes:
filename (str): The filename of the configuration file.
Example:
>>> config = AppConfig() # Use default file config.ini
>>> config['secret']['api-key']
'foobar'
>>> config['secret']['api-key'] = 'barfoo'
>>> config.write()
"""
[docs] def __init__(self, filename=None):
"""Initialize an :class:`.AppConfig` object.
Args:
filename (optional): A filename for the configuration file. If the
filename is not specified, the default filename 'config.ini'
in the current directory is chosen.
"""
ConfigParser.__init__(self)
if not filename:
filename = 'config.ini'
self.filename = filename
config_path = Path(filename)
with config_path.open('rt') as config:
self.read_file(config)
[docs] def write(self, *args, **kwargs):
"""Write the current configuration to the app's configuration file.
If this method is called without any arguments, the configuration is
written to the :attr:`filename` attribute of this object.
If this method is called with any arguments, they are forwarded to the
:meth:`configparser.ConfigParser.write` method of its superclass.
"""
if len(args) + len(kwargs):
super().write(*args, **kwargs)
else:
with Path(self.filename).open('wt') as config:
super().write(config)
[docs]class AssistantConfig(UserDict):
"""This class gives access to the configuration of a Snips assistant as a
:class:`dict`.
Attributes:
filename (str): The filename of the configuration file.
Example:
>>> assistant = AssistantConfig('/opt/assistant/assistant.json')
>>> assistant['language']
'en'
"""
[docs] def __init__(self, filename=None):
"""Initialize an :class:`.AssistantConfig` object.
Args:
filename (str, optional): The path of the assistant's configuration
file.
If the argument is not specified, the configuration file is
searched for in the following locations, in this order:
- /usr/share/snips/assistant/assistant.json
- /usr/local/share/snips/assistant/assistant.json
Raises:
:exc:`FileNotFoundError`: If the specified filename doesn't exist.
:exc:`.AssistantConfigNotFoundError`: If there's no assistant
configuration found in the search path.
:exc:`json.JSONDecodeError`: If the assistant's configuration
file doesn't have a valid JSON syntax.
Examples:
>>> assistant = AssistantConfig() # default configuration
>>> assistant2 = AssistantConfig('/opt/assistant/assistant.json')
"""
if filename:
self.filename = filename
assistant_file = Path(filename)
else:
self.filename = find_path(SEARCH_PATH_ASSISTANT)
if not self.filename:
raise AssistantConfigNotFoundError()
assistant_file = Path(self.filename)
# Open the assistant's file. This raises FileNotFoundError if the
# file doesn't exist.
with assistant_file.open('rt') as json_file:
# Create a dict with our configuration.
# This raises JSONDecodeError if the file doesn't have a
# valid JSON syntax.
UserDict.__init__(self, json.load(json_file))
[docs]class MQTTAuthConfig:
"""This class represents the authentication settings for a connection to an
MQTT broker.
.. versionadded:: 0.6.0
Attributes:
username (str): The username to authenticate to the MQTT broker. `None`
if there's no authentication.
password (str): The password to authenticate to the MQTT broker. Can be
`None`.
"""
[docs] def __init__(self, username=None, password=None):
"""Initialize a :class:`.MQTTAuthConfig` object.
Args:
username (str, optional): The username to authenticate to the MQTT
broker. `None` if there's no authentication.
password (str, optional): The password to authenticate to the MQTT
broker. Can be `None`.
All arguments are optional.
"""
self.username = username
self.password = password
@property
def enabled(self):
"""Check whether authentication is enabled.
Returns:
bool: True if the username is not `None`.
"""
return self.username is not None
[docs]class MQTTTLSConfig:
"""This class represents the TLS settings for a connection to an MQTT
broker.
.. versionadded:: 0.6.0
Attributes:
hostname (str, optional): The TLS hostname of the MQTT broker.
`None` if no TLS is used.
ca_file (str, optional): Path to the Certificate Authority file.
Can be `None`.
ca_path (str, optional): Path to the Certificate Authority files.
Can be `None`.
client_key (str, optional): Path to the private key file. Can be
`None`.
client_cert (str, optional): Path to the client certificate file.
Can be `None`.
disable_root_store (bool, optional): Whether the TLS root store is
disabled.
"""
[docs] def __init__(self, hostname=None, ca_file=None, ca_path=None,
client_key=None, client_cert=None, disable_root_store=False):
"""Initialize a :class:`.MQTTTLSConfig` object.
Args:
hostname (str, optional): The TLS hostname of the MQTT broker.
`None` if no TLS is used.
ca_file (str, optional): Path to the Certificate Authority
file. Can be `None`.
ca_path (str, optional): Path to the Certificate Authority
files. Can be `None`.
client_key (str, optional): Path to the private key file. Can
be `None`.
client_cert (str, optional): Path to the client certificate
file. Can be `None`.
disable_root_store (bool, optional): Whether the TLS root store
is disabled. Defaults to `False`.
All arguments are optional.
"""
self.hostname = hostname
self.ca_file = ca_file
self.ca_path = ca_path
self.client_key = client_key
self.client_cert = client_cert
self.disable_root_store = disable_root_store
@property
def enabled(self):
"""Check whether TLS is enabled.
Returns:
bool: True if the hostname is not `None`.
"""
return self.hostname is not None
[docs]class MQTTConfig:
"""This class represents the configuration for a connection to an
MQTT broker.
.. versionadded:: 0.4.0
Attributes:
broker_address (str, optional): The address of the MQTT broker, in the
form 'host:port'.
auth (:class:`.MQTTAuthConfig`, optional): The authentication
settings (username and password) for the MQTT broker.
tls (:class:`.MQTTTLSConfig`, optional): The TLS settings for the MQTT
broker.
"""
[docs] def __init__(self, broker_address='localhost:1883', auth=None, tls=None):
"""Initialize a :class:`.MQTTConfig` object.
Args:
broker_address (str, optional): The address of the MQTT broker, in
the form 'host:port'.
auth (:class:`.MQTTAuthConfig`, optional): The authentication
settings (username and password) for the MQTT broker. Defaults
to a default :class:`.MQTTAuthConfig` object.
tls (:class:`.MQTTTLSConfig`, optional): The TLS settings for the
MQTT broker. Defaults to a default :class:`.MQTTTLSConfig`
object.
All arguments are optional.
"""
self.broker_address = broker_address
if auth is None:
self.auth = MQTTAuthConfig()
else:
self.auth = auth
if tls is None:
self.tls = MQTTTLSConfig()
else:
self.tls = tls
[docs]class SnipsConfig(UserDict):
"""This class gives access to a snips.toml configuration file as a
:class:`dict`.
Attributes:
filename (str): The filename of the configuration file.
mqtt (:class:`.MQTTConfig`): The MQTT options of the Snips
configuration.
Example:
>>> snips = SnipsConfig()
>>> snips['snips-hotword']['audio']
['default@mqtt', 'bedroom@mqtt']
"""
[docs] def __init__(self, filename=None):
"""Initialize a :class:`.SnipsConfig` object.
The :attr:`mqtt` attribute is initialized with the MQTT connection
settings from the configuration file, or the default value
'localhost:1883' for the broker address if the settings are not
specified.
Args:
filename (str, optional): The full path of the config file. If
the argument is not specified, the file snips.toml is searched
for in the following locations, in this order:
- /etc/snips.toml
- /usr/local/etc/snips.toml
Raises:
:exc:`FileNotFoundError`: If :attr:`filename` is specified but
doesn't exist.
:exc:`.SnipsConfigNotFoundError`: If there's no snips.toml found
in the search path.
:exc:`TomlDecodeError`: If :attr:`filename` doesn't have a valid
TOML syntax.
Examples:
>>> snips = SnipsConfig() # Tries to find snips.toml.
>>> snips_local = SnipsConfig('/usr/local/etc/snips.toml')
"""
if filename:
if not Path(filename).is_file():
raise FileNotFoundError('{} not found'.format(filename))
self.filename = filename
else:
self.filename = find_path(SEARCH_PATH_SNIPS)
if not self.filename:
raise SnipsConfigNotFoundError()
# Create a dict with our configuration.
# This raises TomlDecodeError if the file doesn't have a valid TOML
# syntax.
UserDict.__init__(self, toml.load(self.filename))
# Now find all the MQTT options in the configuration file and use
# sensible defaults for options that aren't specified.
try:
# Basic MQTT connection settings.
broker_address = self['snips-common'].get('mqtt', DEFAULT_BROKER)
# MQTT authentication
username = self['snips-common'].get('mqtt_username', None)
password = self['snips-common'].get('mqtt_password', None)
# MQTT TLS configuration
tls_hostname = self['snips-common'].get('mqtt_tls_hostname', None)
tls_ca_file = self['snips-common'].get('mqtt_tls_cafile', None)
tls_ca_path = self['snips-common'].get('mqtt_tls_capath', None)
tls_client_key = self['snips-common'].get('mqtt_tls_client_key',
None)
tls_client_cert = self['snips-common'].get('mqtt_tls_client_cert',
None)
tls_disable_root_store = self['snips-common'].get('mqtt_tls_disable_root_store',
False)
# Store the MQTT connection settings in an MQTTConfig object.
self.mqtt = MQTTConfig(broker_address,
MQTTAuthConfig(username, password),
MQTTTLSConfig(tls_hostname, tls_ca_file,
tls_ca_path, tls_client_key,
tls_client_cert,
tls_disable_root_store))
except KeyError:
# The 'snips-common' section isn't in the configuration file, so we
# use a sensible default: 'localhost:1883'.
self.mqtt = MQTTConfig()