Moved to a module-level user_settings object

This commit is contained in:
jtc42 2019-11-12 17:14:30 +00:00
parent 8afdf45d4f
commit b33ab7b22a
3 changed files with 111 additions and 115 deletions

View file

@ -68,8 +68,6 @@ class PiCameraStreamer(BaseCamera):
] ]
def __init__(self): def __init__(self):
global USER_CONFIG_DIR
# Run BaseCamera init # Run BaseCamera init
BaseCamera.__init__(self) BaseCamera.__init__(self)
# Attach to Pi camera # Attach to Pi camera

View file

@ -3,115 +3,24 @@ import os
import errno import errno
import logging import logging
import shutil import shutil
import copy
import numpy as np import numpy as np
from fractions import Fraction from fractions import Fraction
from .utilities import recursively_apply
""" """
Attributes: Attributes:
USER_CONFIG_DIR (str): Default path of the user-config directory, containing runtime-config and USER_CONFIG_DIR (str): Default path of the user-config directory, containing runtime-config and
calibration files. Obtained from ``os.path.join(os.path.expanduser("~"), ".openflexure")``. calibration files. Obtained from ``os.path.join(os.path.expanduser("~"), ".openflexure")``.
USER_CONFIG_FILE (str): Default path of the user microscope_settings.json runtime-config file. USER_CONFIG_FILE_PATH (str): Default path of the user microscope_settings.json runtime-config file.
Obtained from ``os.path.join(USER_CONFIG_DIR, "microscope_settings.json")`` Obtained from ``os.path.join(USER_CONFIG_DIR, "microscope_settings.json")``
user_settings (OpenflexureSettingsFile): Default settings file object, which handles expansion and
contraction of settings dictionaries.
""" """
# HANDLE THE DEFAULT CONFIGURATION FILE
HERE = os.path.abspath(os.path.dirname(__file__))
DEFAULT_CONFIG_PATH = os.path.join(HERE, "microscope_settings.default.json")
USER_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".openflexure")
USER_CONFIG_FILE = os.path.join(USER_CONFIG_DIR, "microscope_settings.json")
USER_CONFIG_FILE_OLD = os.path.join(USER_CONFIG_DIR, "microscoperc.json")
# Load the default config
with open(DEFAULT_CONFIG_PATH, "r") as default_rc:
DEFAULT_CONFIG = default_rc.read()
# HANDLE BASIC LOADING AND SAVING OF SETTINGS FILES
def load_json_file(config_path) -> dict:
"""
Open a .json config file
Args:
config_path (str): Path to the config JSON file. If `None`, defaults to `DEFAULT_CONFIG_PATH`
"""
config_path = os.path.expanduser(config_path)
logging.info("Loading {}...".format(config_path))
with open(config_path) as config_file:
try:
config_data = json.load(config_file)
except json.decoder.JSONDecodeError as e:
logging.error(e)
config_data = {}
# Return loaded config dictionary
return config_data
def save_json_file(config_path: str, config_dict: dict):
"""
Save a .json config file
Args:
config_dict (dict): Dictionary of config data to save.
config_path (str): Path to the config JSON file.
"""
config_path = os.path.expanduser(config_path)
logging.info("Saving {}...".format(config_path))
logging.debug(config_dict)
with open(config_path, "w") as outfile:
json.dump(config_dict, outfile, cls=JSONEncoder, indent=2, sort_keys=True)
def create_file(config_path):
if not os.path.exists(os.path.dirname(config_path)):
try:
os.makedirs(os.path.dirname(config_path))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
def initialise_file(config_path, populate: str = "{}\n"):
"""
Check if a file exists, and if not, create it
and optionally populate it with content
Args:
config_path (str): Path to the file.
populate (str): String to dump to the file, if it is being newly created
"""
config_path = os.path.expanduser(config_path)
logging.debug("Initialising {}".format(config_path))
logging.debug("Exists: {}".format(os.path.exists(config_path)))
if not os.path.exists(config_path): # If user config file doesn't exist
logging.warning("No config file found at {}. Creating...".format(config_path))
create_file(config_path)
logging.info("Populating {}...".format(config_path))
with open(config_path, "w") as outfile:
outfile.write(populate)
def settings_file_path(filename:str):
"""Generate a full file path for a filename to be stored in user settings"""
global USER_CONFIG_DIR
return os.path.join(USER_CONFIG_DIR, filename)
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
"""
A custom JSON encoder, with type conversions for PiCamera fractions, Numpy integers, and Numpy arrays
"""
def default(self, o, markers=None): def default(self, o, markers=None):
# PiCamera fractions # PiCamera fractions
if isinstance(o, Fraction): if isinstance(o, Fraction):
@ -127,6 +36,7 @@ class JSONEncoder(json.JSONEncoder):
# raising exceptions for unsupported types # raising exceptions for unsupported types
return json.JSONEncoder.default(self, o) return json.JSONEncoder.default(self, o)
# MAIN CONFIG CLASS # MAIN CONFIG CLASS
class OpenflexureSettingsFile: class OpenflexureSettingsFile:
@ -139,7 +49,7 @@ class OpenflexureSettingsFile:
""" """
def __init__(self, config_path: str = None, expand: bool = True): def __init__(self, config_path: str = None, expand: bool = True):
global DEFAULT_CONFIG, USER_CONFIG_FILE global DEFAULT_CONFIG, USER_CONFIG_FILE_PATH
self.expandable_keys = { self.expandable_keys = {
"camera_settings": None, "camera_settings": None,
@ -147,7 +57,7 @@ class OpenflexureSettingsFile:
} #: Dictionary of keys that can be passed as a file path string and expanded automatically } #: Dictionary of keys that can be passed as a file path string and expanded automatically
# Set arguments # Set arguments
self.config_path = config_path or USER_CONFIG_FILE self.config_path = config_path or USER_CONFIG_FILE_PATH
self.expand = expand self.expand = expand
# Initialise basic config file with defaults if it doesn't exist # Initialise basic config file with defaults if it doesn't exist
@ -250,3 +160,98 @@ class OpenflexureSettingsFile:
return_config[key] = value return_config[key] = value
return return_config return return_config
# HANDLE BASIC LOADING AND SAVING OF SETTINGS FILES
def load_json_file(config_path) -> dict:
"""
Open a .json config file
Args:
config_path (str): Path to the config JSON file. If `None`, defaults to `DEFAULT_CONFIG_PATH`
"""
config_path = os.path.expanduser(config_path)
logging.info("Loading {}...".format(config_path))
with open(config_path) as config_file:
try:
config_data = json.load(config_file)
except json.decoder.JSONDecodeError as e:
logging.error(e)
config_data = {}
# Return loaded config dictionary
return config_data
def save_json_file(config_path: str, config_dict: dict):
"""
Save a .json config file
Args:
config_dict (dict): Dictionary of config data to save.
config_path (str): Path to the config JSON file.
"""
config_path = os.path.expanduser(config_path)
logging.info("Saving {}...".format(config_path))
logging.debug(config_dict)
with open(config_path, "w") as outfile:
json.dump(config_dict, outfile, cls=JSONEncoder, indent=2, sort_keys=True)
def create_file(config_path):
if not os.path.exists(os.path.dirname(config_path)):
try:
os.makedirs(os.path.dirname(config_path))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
def initialise_file(config_path, populate: str = "{}\n"):
"""
Check if a file exists, and if not, create it
and optionally populate it with content
Args:
config_path (str): Path to the file.
populate (str): String to dump to the file, if it is being newly created
"""
config_path = os.path.expanduser(config_path)
logging.debug("Initialising {}".format(config_path))
logging.debug("Exists: {}".format(os.path.exists(config_path)))
if not os.path.exists(config_path): # If user config file doesn't exist
logging.warning("No config file found at {}. Creating...".format(config_path))
create_file(config_path)
logging.info("Populating {}...".format(config_path))
with open(config_path, "w") as outfile:
outfile.write(populate)
def settings_file_path(filename: str):
"""Generate a full file path for a filename to be stored in user settings"""
global USER_CONFIG_DIR
return os.path.join(USER_CONFIG_DIR, filename)
# HANDLE THE DEFAULT CONFIGURATION FILE
HERE = os.path.abspath(os.path.dirname(__file__))
DEFAULT_CONFIG_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json")
USER_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".openflexure")
USER_CONFIG_FILE_PATH = os.path.join(USER_CONFIG_DIR, "microscope_settings.json")
# Load the default config
with open(DEFAULT_CONFIG_FILE_PATH, "r") as default_rc:
DEFAULT_CONFIG = default_rc.read()
# Create the default user settings object
user_settings = OpenflexureSettingsFile(config_path=USER_CONFIG_FILE_PATH, expand=True)

View file

@ -14,7 +14,7 @@ from openflexure_microscope.camera.mock import MockStreamer
from openflexure_microscope.plugins import PluginMount from openflexure_microscope.plugins import PluginMount
from openflexure_microscope.task import TaskOrchestrator from openflexure_microscope.task import TaskOrchestrator
from openflexure_microscope.common.lock import CompositeLock from openflexure_microscope.common.lock import CompositeLock
from openflexure_microscope.config import OpenflexureSettingsFile from openflexure_microscope.config import user_settings
class Microscope: class Microscope:
@ -24,8 +24,6 @@ class Microscope:
The camera and stage should already be initialised, and passed as arguments. The camera and stage should already be initialised, and passed as arguments.
Attributes: Attributes:
settings_file (:py:class:`openflexure_microscope.config.OpenflexureSettingsFile`): Runtime-config object,
automatically created if None.
lock (:py:class:`openflexure_microscope.common.lock.CompositeLock`): Composite lock controlling thread access lock (:py:class:`openflexure_microscope.common.lock.CompositeLock`): Composite lock controlling thread access
to multiple pieces of hardware. to multiple pieces of hardware.
camera (:py:class:`openflexure_microscope.camera.base.BaseCamera`): Camera object camera (:py:class:`openflexure_microscope.camera.base.BaseCamera`): Camera object
@ -50,10 +48,8 @@ class Microscope:
# Create a task orchestrator # Create a task orchestrator
self.task = TaskOrchestrator() self.task = TaskOrchestrator()
# Attach to a settings file
self.settings_file = OpenflexureSettingsFile(expand=True)
# Apply settings loaded from file # Apply settings loaded from file
self.apply_config(self.settings_file.load()) self.apply_config(user_settings.load())
# Create plugin mount-point and attach plugins from maps # Create plugin mount-point and attach plugins from maps
self.plugin = PluginMount(self) self.plugin = PluginMount(self)
@ -223,14 +219,11 @@ class Microscope:
settings_current_stage = self.stage.read_config() settings_current_stage = self.stage.read_config()
settings_current["stage_settings"] = settings_current_stage settings_current["stage_settings"] = settings_current_stage
settings_full = self.settings_file.merge(settings_current) settings_full = user_settings.merge(settings_current)
#if json_safe:
# settings_full = settings_to_json(settings_full)
return settings_full return settings_full
def save_config(self, backup: bool = True): def save_config(self):
""" """
Merges the current settings back to disk Merges the current settings back to disk
""" """
@ -245,11 +238,11 @@ class Microscope:
self.camera.save_config() self.camera.save_config()
if self.stage: if self.stage:
self.stage.save_config() self.stage.save_config()
self.settings_file.save(current_config, backup=True) user_settings.save(current_config, backup=True)
@property @property
def config(self) -> dict: def config(self) -> dict:
logging.warn( logging.warning(
"Reading microscope through config property is deprecated.\ "Reading microscope through config property is deprecated.\
Please use read_config method instead." Please use read_config method instead."
) )
@ -257,7 +250,7 @@ class Microscope:
@config.setter @config.setter
def config(self, config: dict) -> None: def config(self, config: dict) -> None:
logging.warn( logging.warning(
"Setting microscope through config property is deprecated.\ "Setting microscope through config property is deprecated.\
Please use apply_config method instead." Please use apply_config method instead."
) )