Skip to content

Commit

Permalink
Merge pull request #128 from qkitgroup/fix-cfg-not-found
Browse files Browse the repository at this point in the history
Fixes qkit.cfg and installer incompatibility
  • Loading branch information
thilokru authored Oct 31, 2024
2 parents efcde1f + 877af4a commit 87b2fb9
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 137 deletions.
140 changes: 9 additions & 131 deletions src/qkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,141 +3,19 @@
# HR@KIT/2017/2018
__all__ = ['config','gui','measure','tools', 'analysis','core','instruments','services','storage','logs']

import os.path
import logging
class QkitCfgError(Exception):
'''
If something with qkit.cfg does not fit to what the user wants to do, this is the error to throw.
'''
pass

class ConfClass(dict):
def __init__(self, *args):
dict.__init__(self, args)
def preset_analyse(self,verbose = False):
""" Sets basic settings, most of the services are not loaded (default)
The file index service is run and the UUID registry is populated.
"""

self['load_info_service'] = False
self['load_ri_service'] = False
self['load_visa'] = False
if verbose:
print ("Not starting the info_service, ri_service and visa.")
def preset_measure(self,verbose = False):
""" Setup of the measurement settings, services are loaded or initialized.
"""
self['load_info_service'] = True
self['load_ri_service'] = True
self['load_visa'] = True

if self['datadir'] == os.path.join(self['qkitdir'],'data'):
print("Please set a valid data directory! (datadir)")
if verbose:
print ("Starting the info_service, ri_service and visa.")

def get(self, item,default=None):
try:
return self[item]
except KeyError:
if default is not None:
self[item]=default
return default
cfg = ConfClass()

# load configuration from $QKITDIR/config/*

try:
from qkit.config.environment import cfg as cfg_local
cfg.update(cfg_local)
except ImportError:
pass

# if a local.py file is defined, load cfg dict and overwrite environment entries.
_config_sources = []
try:
from qkit.config.local import cfg_local
cfg.update(cfg_local)
_config_sources += ["[qkit]src/qkit/config/local.py - cfg_local"]
logging.warning("DEPRECATED: Loaded qkit.config.local.py! This has been deprecated in favour of qkit_local_config.py, see README!")
except ImportError:
pass

try:
from qkit.config.local import cfg as cfg_local
cfg.update(cfg_local)
_config_sources += ["[qkit]src/qkit/config/local.py - cfg"]
logging.warning("DEPRECATED: Loaded qkit.config.local.py! This has been deprecated in favour of qkit_local_config.py, see README!")
except ImportError:
pass


def _load_user_config():
def __getattr__(name):
"""
Loads user configuration from the file system, determined by known file names or environment variables.
We usually run in a jupyter notebook. If qkit is installed as a package, a local config is not possible,
and in the long run we should migrate towards a config not in the qkit directory.
We will use two strategies here:
1. Check if the environment variable 'QKIT_LOCAL_CONFIG' is set. Use the file it points to.
2. Use current working directory:
a. check if a qkit_conf.py file exists. If it does, use it.
b. Else go to parrent directory and repeat
If a file was found by either way, load it.
We log information to stdout, as logging is not yet available. This code is executed before S10_logging.py,
and will already have become unavailable once logging is initialized.
Lazy Loading support for qkit configuration. Based on PEP 562: Module __getattr__ and __dir__
"""
global _config_sources
import os
from pathlib import Path
from itertools import chain

user_config = None
environment_variable_name = 'QKIT_LOCAL_CONFIG'
config_file_names = ['qkit_local_config.py', 'local.py']

if environment_variable_name in os.environ:
# Inspect environment variable, should it exist
print("Found environment variable '%s' for configuration." % environment_variable_name)
user_config = os.environ[environment_variable_name]
else:
current_directory = Path(os.getcwd())
for dir in chain([current_directory], current_directory.parents):
for (index, config_file_name) in enumerate(config_file_names):
test_path = dir / config_file_name
if test_path.exists():
if test_path.is_file():
user_config = test_path
if index != 0:
print("WARNING: Found legacy configuration file name. Consider migrating.")
# We are done here
break
else:
print("WARNING: Found correct file system name %s, but was not a file?!" % test_path)

# Load if found
if user_config is not None:
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
import importlib.util
spec = importlib.util.spec_from_file_location("userconfig", user_config)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
cfg.update(module.cfg)
_config_sources += [user_config]

_load_user_config()
del _load_user_config

if len(_config_sources) > 1:
logging.warn("Loaded more than one configuration file! This may cause conflicts! Sources:")
logging.warn(_config_sources)
del _config_sources
global cfg
if name == "cfg":
print("Lazy-Loading configuration...")
from qkit.config.config_holder import ConfClass
cfg = ConfClass()
return cfg
raise AttributeError("qkit has no attribute " + name)

# clean up
del cfg_local
# init message
print ("QKIT configuration initialized -> available as qkit.cfg[...]")

"""
startup functions
Expand Down
151 changes: 151 additions & 0 deletions src/qkit/config/config_holder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@

import os.path
import logging

class QkitCfgError(Exception):
'''
If something with qkit.cfg does not fit to what the user wants to do, this is the error to throw.
'''
pass

class ConfClass(dict):

def __init__(self, *args):
"""
Load configuration from the following sources:
- Default configuraiton from qkit.config.environment
- Package overrides from qkit.config.local
- Configuration file qkit_local_config.py or QKIT_LOCAL_CONFIG environment variable.
"""
dict.__init__(self, args)

self._load_default_config()
sources = self._load_config_overrides() + self._find_and_load_user_config()

if len(sources) > 1:
logging.warning("Loaded more than one configuration file! This may cause conflicts! Sources:")
logging.warning(sources)

# init message
print ("QKIT configuration initialized -> available as qkit.cfg[...]")


def preset_analyse(self, verbose = False):
""" Sets basic settings, most of the services are not loaded (default)
The file index service is run and the UUID registry is populated.
"""

self['load_info_service'] = False
self['load_ri_service'] = False
self['load_visa'] = False
if verbose:
print ("Not starting the info_service, ri_service and visa.")

def preset_measure(self, verbose = False):
""" Setup of the measurement settings, services are loaded or initialized.
"""
self['load_info_service'] = True
self['load_ri_service'] = True
self['load_visa'] = True

if self['datadir'] == os.path.join(self['qkitdir'],'data'):
print("Please set a valid data directory! (datadir)")
if verbose:
print ("Starting the info_service, ri_service and visa.")

def get(self, item,default=None):
try:
return self[item]
except KeyError:
if default is not None:
self[item]=default
return default

def _load_default_config(self):
# load configuration from $QKITDIR/config/*
try:
from qkit.config.environment import cfg as cfg_local
self.update(cfg_local)
except ImportError:
pass


def _load_config_overrides(self) -> list[str]:
# if a local.py file is defined, load cfg dict and overwrite environment entries.
_config_sources = []
try:
from qkit.config.local import cfg_local
self.update(cfg_local)
_config_sources += ["[qkit]src/qkit/config/local.py - cfg_local"]
logging.warning("DEPRECATED: Loaded qkit.config.local.py! This has been deprecated in favour of qkit_local_config.py, see README!")
except ImportError:
pass

try:
from qkit.config.local import cfg as cfg_local
self.update(cfg_local)
_config_sources += ["[qkit]src/qkit/config/local.py - cfg"]
logging.warning("DEPRECATED: Loaded qkit.config.local.py! This has been deprecated in favour of qkit_local_config.py, see README!")
except ImportError:
pass

return _config_sources


def _find_and_load_user_config(self) -> list[str]:
"""
Loads user configuration from the file system, determined by known file names or environment variables.
We usually run in a jupyter notebook. If qkit is installed as a package, a local config is not possible,
and in the long run we should migrate towards a config not in the qkit directory.
We will use two strategies here:
1. Check if the environment variable 'QKIT_LOCAL_CONFIG' is set. Use the file it points to.
2. Use current working directory:
a. check if a qkit_conf.py file exists. If it does, use it.
b. Else go to parrent directory and repeat
If a file was found by either way, load it.
We log information to stdout, as logging is not yet available. This code is executed before S10_logging.py,
and will already have become unavailable once logging is initialized.
"""
global _config_sources
import os
from pathlib import Path
from itertools import chain

user_config = None
environment_variable_name = 'QKIT_LOCAL_CONFIG'
config_file_names = ['qkit_local_config.py', 'local.py']

if environment_variable_name in os.environ:
# Inspect environment variable, should it exist
print("Found environment variable '%s' for configuration." % environment_variable_name)
user_config = os.environ[environment_variable_name]
else:
current_directory = Path(os.getcwd())
for dir in chain([current_directory], current_directory.parents):
for (index, config_file_name) in enumerate(config_file_names):
test_path = dir / config_file_name
if test_path.exists():
if test_path.is_file():
user_config = test_path
if index != 0:
print("WARNING: Found legacy configuration file name. Consider migrating.")
# We are done here
break
else:
print("WARNING: Found correct file system name %s, but was not a file?!" % test_path)

# Load if found
if user_config is not None:
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
try:
import importlib.util
spec = importlib.util.spec_from_file_location("userconfig", user_config)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
self.update(module.cfg)
return [str(user_config)]
except Exception as e:
logging.exception("Could not load user config", e)
return []
12 changes: 6 additions & 6 deletions src/qkit/install/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ def windows_admin_required(f: Callable) -> Callable:
import ctypes
import functools

if not ctypes.windll.shell32.IsUserAnAdmin():
@functools.wraps(f)
def warning(*args, **kwargs):
@functools.wraps(f)
def wrapped(*args, **kwargs):
if not ctypes.windll.shell32.IsUserAnAdmin():
logger.warning("Admin privileges required for this task. Skipping.")
return warning
else:
return f
else:
return f(*args, **kwargs)
return wrapped


# Get binary path for installed command:
Expand Down

0 comments on commit 87b2fb9

Please sign in to comment.