Skip to content

Commit

Permalink
Fix circular imports and refactor submodules (#8)
Browse files Browse the repository at this point in the history
* Fix circular imports
Add unit tests of `locations` module
Refactor modules for clarity

* Fix bugs in tests

* Update test patches to use new import locations

* Troubleshooting config test failures

* Add mycroft.conf to package_data

* Troubleshooting GH actions failures

* Remove failing test case

* Fix test mock for Pytest compat.

* Refactor `meta_config` to `meta`
Drop `base` backwards-compat wrapper

* Fix typo in log

Co-authored-by: Daniel McKnight <daniel@neon.ai>
  • Loading branch information
NeonDaniel and NeonDaniel authored Jul 6, 2022
1 parent 1b46de9 commit 9879605
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 229 deletions.
2 changes: 1 addition & 1 deletion ovos_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
set_default_tz, set_default_lang, get_default_tz, get_default_lang, \
get_config_tz, get_primary_lang_code, load_languages, load_language
from ovos_config.locations import SYSTEM_CONFIG, USER_CONFIG, DEFAULT_CONFIG, get_xdg_config_locations
from ovos_config.ovos import get_ovos_config
from ovos_config.meta import get_ovos_config
2 changes: 1 addition & 1 deletion ovos_config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from mycroft_bus_client import Message

from ovos_config.base import LocalConf, MycroftDefaultConfig, MycroftSystemConfig, MycroftUserConfig, RemoteConf
from ovos_config.models import LocalConf, MycroftDefaultConfig, MycroftSystemConfig, MycroftUserConfig, RemoteConf
from ovos_config.locations import OLD_USER_CONFIG, get_xdg_config_save_path, get_xdg_config_locations
from ovos_config.utils import FileWatcher

Expand Down
32 changes: 18 additions & 14 deletions ovos_config/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,45 @@
import os
from os.path import join, dirname, expanduser, exists, isfile
from time import sleep
from ovos_utils.system import search_mycroft_core_location, is_running_from_module
from ovos_config.ovos import get_xdg_base, is_using_xdg, get_config_filename, get_ovos_config
from ovos_utils.system import search_mycroft_core_location
import ovos_config.meta as _ovos_config
from ovos_utils.xdg_utils import xdg_config_dirs, xdg_config_home, xdg_data_dirs, xdg_data_home, xdg_cache_home


def get_xdg_config_dirs(folder=None):
""" return list of possible XDG config dirs taking into account ovos.conf """
folder = folder or get_xdg_base()
folder = folder or _ovos_config.get_xdg_base()
xdg_dirs = xdg_config_dirs() + [xdg_config_home()]
return [join(path, folder) for path in xdg_dirs]


def get_xdg_data_dirs(folder=None):
""" return list of possible XDG data dirs taking into account ovos.conf """
folder = folder or get_xdg_base()
folder = folder or _ovos_config.get_xdg_base()
return [join(path, folder) for path in xdg_data_dirs()]


def get_xdg_config_save_path(folder=None):
""" return base XDG config save path taking into account ovos.conf """
folder = folder or get_xdg_base()
folder = folder or _ovos_config.get_xdg_base()
return join(xdg_config_home(), folder)


def get_xdg_data_save_path(folder=None):
""" return base XDG data save path taking into account ovos.conf """
folder = folder or get_xdg_base()
folder = folder or _ovos_config.get_xdg_base()
return join(xdg_data_home(), folder)


def get_xdg_cache_save_path(folder=None):
""" return base XDG cache save path taking into account ovos.conf """
folder = folder or get_xdg_base()
folder = folder or _ovos_config.get_xdg_base()
return join(xdg_cache_home(), folder)


def find_user_config():
""" return user config full file path taking into account ovos.conf """
path = join(get_xdg_config_save_path(), get_config_filename())
path = join(get_xdg_config_save_path(), _ovos_config.get_config_filename())
if isfile(path):
return path
old, path = get_config_locations(default=False, web_cache=False,
Expand All @@ -69,7 +69,7 @@ def get_config_locations(default=True, web_cache=True, system=True,
old_user=True, user=True):
"""return list of all possible config files paths sorted by priority taking into account ovos.conf"""
locs = []
ovos_cfg = get_ovos_config()
ovos_cfg = _ovos_config.get_ovos_config()
if default:
locs.append(ovos_cfg["default_config_path"])
if system:
Expand All @@ -93,7 +93,7 @@ def get_xdg_config_locations():
# This includes both the user config and
# /etc/xdg/mycroft/mycroft.conf
xdg_paths = list(reversed(
[join(p, get_config_filename())
[join(p, _ovos_config.get_config_filename())
for p in get_xdg_config_dirs()]
))
return xdg_paths
Expand All @@ -114,14 +114,18 @@ def find_default_config():

DEFAULT_CONFIG = find_default_config()
SYSTEM_CONFIG = os.environ.get('MYCROFT_SYSTEM_CONFIG',
f'/etc/{get_xdg_base()}/{get_config_filename()}')
f'/etc/{_ovos_config.get_xdg_base()}/'
f'{_ovos_config.get_config_filename()}')
# TODO: remove in 22.02
# Make sure we support the old location still
# Deprecated and will be removed eventually
OLD_USER_CONFIG = join(expanduser('~'), '.' + get_xdg_base(), get_config_filename())
USER_CONFIG = join(get_xdg_config_save_path(), get_config_filename())
OLD_USER_CONFIG = join(expanduser('~'), '.' + _ovos_config.get_xdg_base(),
_ovos_config.get_config_filename())
USER_CONFIG = join(get_xdg_config_save_path(),
_ovos_config.get_config_filename())
REMOTE_CONFIG = "mycroft.ai"
WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE') or get_webcache_location()
WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE') or \
get_webcache_location()


def __ensure_folder_exists(path):
Expand Down
211 changes: 211 additions & 0 deletions ovos_config/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
"""This file will check a system level OpenVoiceOS specific config file
The ovos config is json with comment support like the regular mycroft.conf
Default locations tried by order until a file is found
- /etc/OpenVoiceOS/ovos.conf
- /etc/mycroft/ovos.conf
XDG locations are then merged over the select default config (if found)
Examples config:
{
// the "name of the core",
// eg, OVOS, Neon, Chatterbox...
// all XDG paths should respect this
// {xdg_path}/{base_folder}/some_resource
// "mycroft.conf" paths are derived from this
// ~/.{base_folder}/mycroft.conf
"base_folder": "OpenVoiceOS",
// the filename of "mycroft.conf",
// eg, ovos.conf, chatterbox.conf, neon.conf...
// "mycroft.conf" paths are derived from this
// ~/.{base_folder}/{config_filename}
"config_filename": "mycroft.conf",
// override the default.conf location, allows changing the default values
// eg, disable backend, disable skills, configure permissions
"default_config_path": "/etc/OpenVoiceOS/default_mycroft.conf",
// this is intended for derivative products, if a module name is present
// in sys.modules then the values below will be used instead
// eg, chatterbox/mycroft/ovos/neon can coexist in the same machine
"module_overrides": {
"chatterbox": {
"xdg": false,
"base_folder": "chatterbox",
"config_filename": "chatterbox.conf",
"default_config_path": "/opt/chatterbox/chatterbox.conf"
},
"neon_core": {
"xdg": true,
"base_folder": "neon",
"config_filename": "neon.conf",
"default_config_path": "/opt/neon/neon.conf"
}
},
// essentially aliases for the above, useful for microservice architectures
"submodule_mappings": {
"chatterbox_stt": "chatterbox",
"chatterbox_playback": "chatterbox",
"chatterbox_admin": "chatterbox",
"chatterbox_blockly": "chatterbox",
"chatterbox_gpio_service": "chatterbox",
"neon_speech": "neon_core",
"neon_audio": "neon_core",
"neon_enclosure": "neon_core"
}
}
"""
from os.path import isfile, join, dirname

from json_database import JsonStorage

import ovos_config.locations as _oloc
from ovos_utils.json_helper import load_commented_json, merge_dict
from ovos_utils.log import LOG
from ovos_utils.system import is_running_from_module


def get_ovos_config():
""" load ovos.conf
goes trough all possible ovos.conf paths and loads them in order
submodule overrides are applied to the final config if overrides are defined for the caller module
eg, if neon-core is calling this method then neon config overrides are loaded
"""
# populate default values
config = {"xdg": True,
"base_folder": "mycroft",
"config_filename": "mycroft.conf"}
try:
config["default_config_path"] = _oloc.find_default_config()
except FileNotFoundError: # not a mycroft device
config["default_config_path"] = join(dirname(__file__), "mycroft.conf")

# load ovos.conf
for path in get_ovos_default_config_paths():
try:
config = merge_dict(config, load_commented_json(path))
except:
# tolerate bad json TODO proper exception (?)
pass

# let's check for derivatives specific configs
# the assumption is that these cores are exclusive to each other,
# this will never find more than one override
# TODO this works if using dedicated .venvs what about system installs?
cores = config.get("module_overrides") or {}
for k in cores:
if is_running_from_module(k):
config = merge_dict(config, cores[k])
break
else:
subcores = config.get("submodule_mappings") or {}
for k in subcores:
if is_running_from_module(k):
config = merge_dict(config, cores[subcores[k]])
break

return config


def save_ovos_config(new_config):
""" update ovos.conf contents at ~/.config/OpenVoiceOS/ovos.conf """
OVOS_CONFIG = join(_oloc.get_xdg_config_save_path("OpenVoiceOS"),
"ovos.conf")
cfg = JsonStorage(OVOS_CONFIG)
cfg.update(new_config)
cfg.store()
return cfg


def get_ovos_default_config_paths():
""" return a list of all existing ovos.conf file locations by order of precedence
eg. ["/etc/OpenVoiceOS/ovos.conf", "/home/user/.config/OpenVoiceOS/ovos.conf"]
"""
paths = []
if isfile("/etc/OpenVoiceOS/ovos.conf"):
paths.append("/etc/OpenVoiceOS/ovos.conf")
elif isfile("/etc/mycroft/ovos.conf"):
LOG.warning("found /etc/mycroft/ovos.conf\n"
"This location has been DEPRECATED!\n"
"Please move your config to /etc/OpenVoiceOS/ovos.conf")
paths.append("/etc/mycroft/ovos.conf")

# This includes both the user config and
# /etc/xdg/OpenVoiceOS/ovos.conf
for p in _oloc.get_xdg_config_dirs("OpenVoiceOS"):
if isfile(join(p, "ovos.conf")):
paths.append(join(p, "ovos.conf"))

return paths


def is_using_xdg():
""" BACKWARDS COMPAT: logs warning and always returns True"""
LOG.warning("is_using_xdg has been deprecated! XDG specs are always honoured, this method will be removed in a future release")
return True


def get_xdg_base():
""" base folder name to be used when building paths of the format {$XDG_XXX}/{base}
different derivative cores may change this folder, this value is derived from ovos.conf
eg, "mycroft", "hivemind", "neon" ....
"""

return get_ovos_config().get("base_folder") or "mycroft"


def set_xdg_base(folder_name):
""" base folder name to be used when building paths of the format {$XDG_XXX}/{base}
different derivative cores may change this folder, this value is derived from ovos.conf
eg, "mycroft", "hivemind", "neon" ....
NOTE: this value will be set globally, per core overrides in ovos.conf take precedence
"""
LOG.info(f"XDG base folder set to: '{folder_name}'")
save_ovos_config({"base_folder": folder_name})


def set_config_filename(file_name, core_folder=None):
""" base config file name to be used when building paths
different derivative cores may change this filename, this value is derived from ovos.conf
eg, "mycroft.conf", "hivemind.json", "neon.yaml" ....
NOTE: this value will be set globally, per core overrides in ovos.conf take precedence
"""
if core_folder:
set_xdg_base(core_folder)
LOG.info(f"config filename set to: '{file_name}'")
save_ovos_config({"config_filename": file_name})


def get_config_filename():
""" base config file name to be used when building paths
different derivative cores may change this filename, this value is derived from ovos.conf
eg, "mycroft.conf", "hivemind.json", "neon.yaml" ....
"""
return get_ovos_config().get("config_filename") or "mycroft.conf"


def set_default_config(file_path=None):
""" full path to default config file to be used
NOTE: this is a full path, not a directory! "config_filename" parameter is not used here
different derivative cores may change this file, this value is derived from ovos.conf
NOTE: this value will be set globally, per core overrides in ovos.conf take precedence
"""
file_path = file_path or _oloc.find_default_config()
LOG.info(f"default config file changed to: {file_path}")
save_ovos_config({"default_config_path": file_path})
File renamed without changes.
Loading

0 comments on commit 9879605

Please sign in to comment.