summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/feature_2164_config-split1
-rw-r--r--pkg/requirements.pip5
-rw-r--r--src/leap/config/baseconfig.py186
-rw-r--r--src/leap/config/leapsettings.py2
-rw-r--r--src/leap/config/pluggableconfig.py475
-rw-r--r--src/leap/config/prefixers.py133
-rw-r--r--src/leap/config/providerconfig.py2
-rw-r--r--src/leap/services/eip/eipconfig.py2
8 files changed, 6 insertions, 800 deletions
diff --git a/changes/feature_2164_config-split b/changes/feature_2164_config-split
new file mode 100644
index 00000000..6fae00de
--- /dev/null
+++ b/changes/feature_2164_config-split
@@ -0,0 +1 @@
+ o Moves BaseConfig to leap.common.config. Closes:#2164
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 89917a53..0051380a 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -1,16 +1,15 @@
# in order of addition to the project.
# do not change the ordering.
#
+argparse
+#
PySide
# Use LEAP_VENV_SKIP_PYSIDE to avoid installing it!
-jsonschema<=0.8
requests
srp>=1.0.2
pyopenssl
keyring
-pyxdg
-argparse
python-dateutil
psutil
diff --git a/src/leap/config/baseconfig.py b/src/leap/config/baseconfig.py
deleted file mode 100644
index f5c07184..00000000
--- a/src/leap/config/baseconfig.py
+++ /dev/null
@@ -1,186 +0,0 @@
-# -*- coding: utf-8 -*-
-# baseconfig.py
-# Copyright (C) 2013 LEAP
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Implements the abstract base class for configuration
-"""
-
-import copy
-import logging
-import functools
-import os
-
-from abc import ABCMeta, abstractmethod
-
-from leap.common.check import leap_assert
-from leap.common.files import mkdir_p
-from leap.config.pluggableconfig import PluggableConfig
-from leap.config.prefixers import get_platform_prefixer
-
-logger = logging.getLogger(__name__)
-
-
-class BaseConfig:
- """
- Abstract base class for any JSON based configuration
- """
-
- __metaclass__ = ABCMeta
-
- """
- Standalone is a class wide parameter
-
- @param standalone: if True it will return the prefix for a
- standalone application. Otherwise, it will return the system
- default for configuration storage.
- @type standalone: bool
- """
- standalone = False
-
- def __init__(self):
- self._data = {}
- self._config_checker = None
-
- @abstractmethod
- def _get_spec(self):
- """
- Returns the spec object for the specific configuration
- """
- return None
-
- def _safe_get_value(self, key):
- """
- Tries to return a value only if the config has already been loaded
-
- @rtype: depends on the config structure, dict, str, array, int
- @return: returns the value for the specified key in the config
- """
- leap_assert(self._config_checker, "Load the config first")
- return self._config_checker.config[key]
-
- def get_path_prefix(self):
- """
- Returns the platform dependant path prefixer
-
- """
- return get_platform_prefixer().get_path_prefix(
- standalone=self.standalone)
-
- def loaded(self):
- """
- Returns True if the configuration has been already
- loaded. False otherwise
- """
- return self._config_checker is not None
-
- def save(self, path_list):
- """
- Saves the current configuration to disk
-
- @param path_list: list of components that form the relative
- path to configuration. The absolute path will be calculated
- depending on the platform.
- @type path_list: list
-
- @return: True if saved to disk correctly, False otherwise
- """
- config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1]))
- mkdir_p(config_path)
-
- try:
- self._config_checker.serialize(os.path.join(config_path,
- path_list[-1]))
- except Exception as e:
- logger.warning("%s" % (e,))
- raise
- return True
-
- def load(self, path="", data=None, mtime=None):
- """
- Loads the configuration from disk
-
- @type path: str
- @param path: relative path to configuration. The absolute path
- will be calculated depending on the platform
-
- @return: True if loaded from disk correctly, False otherwise
- """
-
- config_path = os.path.join(self.get_path_prefix(),
- path)
-
- self._config_checker = PluggableConfig(format="json")
- self._config_checker.options = copy.deepcopy(self._get_spec())
-
- try:
- if data is None:
- self._config_checker.load(fromfile=config_path, mtime=mtime)
- else:
- self._config_checker.load(data, mtime=mtime)
- except Exception as e:
- logger.warning("Something went wrong while loading " +
- "the config from %s\n%s" % (config_path, e))
- self._config_checker = None
- return False
- return True
-
-
-class LocalizedKey(object):
- """
- Decorator used for keys that are localized in a configuration
- """
-
- def __init__(self, func, **kwargs):
- self._func = func
-
- def __call__(self, instance, lang="en"):
- """
- Tries to return the string for the specified language, otherwise
- informs the problem and returns an empty string
-
- @param lang: language code
- @type lang: str
-
- @return: localized value from the possible values returned by
- self._func
- """
- descriptions = self._func(instance)
- description_lang = ""
- config_lang = "en"
- for key in descriptions.keys():
- if lang.startswith(key):
- config_lang = key
- break
-
- description_lang = descriptions[config_lang]
- return description_lang
-
- def __get__(self, instance, instancetype):
- """
- Implement the descriptor protocol to make decorating instance
- method possible.
- """
- # Return a partial function with the first argument is the instance
- # of the class decorated.
- return functools.partial(self.__call__, instance)
-
-if __name__ == "__main__":
- try:
- config = BaseConfig() # should throw TypeError for _get_spec
- except Exception as e:
- assert isinstance(e, TypeError), "Something went wrong"
- print "Abstract BaseConfig class is working as expected"
diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py
index 19ec4a9a..df9c9f11 100644
--- a/src/leap/config/leapsettings.py
+++ b/src/leap/config/leapsettings.py
@@ -24,7 +24,7 @@ import logging
from PySide import QtCore
from leap.common.check import leap_assert, leap_assert_type
-from leap.config.prefixers import get_platform_prefixer
+from leap.common.config.prefixers import get_platform_prefixer
logger = logging.getLogger(__name__)
diff --git a/src/leap/config/pluggableconfig.py b/src/leap/config/pluggableconfig.py
deleted file mode 100644
index 8535fa6b..00000000
--- a/src/leap/config/pluggableconfig.py
+++ /dev/null
@@ -1,475 +0,0 @@
-# -*- coding: utf-8 -*-
-# pluggableconfig.py
-# Copyright (C) 2013 LEAP
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-generic configuration handlers
-"""
-import copy
-import json
-import logging
-import os
-import time
-import urlparse
-
-import jsonschema
-
-#from leap.base.util.translations import LEAPTranslatable
-from leap.common.check import leap_assert
-
-
-logger = logging.getLogger(__name__)
-
-
-__all__ = ['PluggableConfig',
- 'adaptors',
- 'types',
- 'UnknownOptionException',
- 'MissingValueException',
- 'ConfigurationProviderException',
- 'TypeCastException']
-
-# exceptions
-
-
-class UnknownOptionException(Exception):
- """exception raised when a non-configuration
- value is present in the configuration"""
-
-
-class MissingValueException(Exception):
- """exception raised when a required value is missing"""
-
-
-class ConfigurationProviderException(Exception):
- """exception raised when a configuration provider is missing, etc"""
-
-
-class TypeCastException(Exception):
- """exception raised when a
- configuration item cannot be coerced to a type"""
-
-
-class ConfigAdaptor(object):
- """
- abstract base class for config adaotors for
- serialization/deserialization and custom validation
- and type casting.
- """
- def read(self, filename):
- raise NotImplementedError("abstract base class")
-
- def write(self, config, filename):
- with open(filename, 'w') as f:
- self._write(f, config)
-
- def _write(self, fp, config):
- raise NotImplementedError("abstract base class")
-
- def validate(self, config, schema):
- raise NotImplementedError("abstract base class")
-
-
-adaptors = {}
-
-
-class JSONSchemaEncoder(json.JSONEncoder):
- """
- custom default encoder that
- casts python objects to json objects for
- the schema validation
- """
- def default(self, obj):
- if obj is str:
- return 'string'
- if obj is unicode:
- return 'string'
- if obj is int:
- return 'integer'
- if obj is list:
- return 'array'
- if obj is dict:
- return 'object'
- if obj is bool:
- return 'boolean'
-
-
-class JSONAdaptor(ConfigAdaptor):
- indent = 2
- extensions = ['json']
-
- def read(self, _from):
- if isinstance(_from, file):
- _from_string = _from.read()
- if isinstance(_from, str):
- _from_string = _from
- return json.loads(_from_string)
-
- def _write(self, fp, config):
- fp.write(json.dumps(config,
- indent=self.indent,
- sort_keys=True))
-
- def validate(self, config, schema_obj):
- schema_json = JSONSchemaEncoder().encode(schema_obj)
- schema = json.loads(schema_json)
- jsonschema.validate(config, schema)
-
-
-adaptors['json'] = JSONAdaptor()
-
-#
-# Adaptors
-#
-# Allow to apply a predefined set of types to the
-# specs, so it checks the validity of formats and cast it
-# to proper python types.
-
-# TODO:
-# - HTTPS uri
-
-
-class DateType(object):
- fmt = '%Y-%m-%d'
-
- def to_python(self, data):
- return time.strptime(data, self.fmt)
-
- def get_prep_value(self, data):
- return time.strftime(self.fmt, data)
-
-
-class TranslatableType(object):
- """
- a type that casts to LEAPTranslatable objects.
- Used for labels we get from providers and stuff.
- """
-
- def to_python(self, data):
- # TODO: add translatable
- return data # LEAPTranslatable(data)
-
- # needed? we already have an extended dict...
- #def get_prep_value(self, data):
- #return dict(data)
-
-
-class URIType(object):
-
- def to_python(self, data):
- parsed = urlparse.urlparse(data)
- if not parsed.scheme:
- raise TypeCastException("uri %s has no schema" % data)
- return parsed.geturl()
-
- def get_prep_value(self, data):
- return data
-
-
-class HTTPSURIType(object):
-
- def to_python(self, data):
- parsed = urlparse.urlparse(data)
- if not parsed.scheme:
- raise TypeCastException("uri %s has no schema" % data)
- if parsed.scheme != "https":
- raise TypeCastException(
- "uri %s does not has "
- "https schema" % data)
- return parsed.geturl()
-
- def get_prep_value(self, data):
- return data
-
-
-types = {
- 'date': DateType(),
- 'uri': URIType(),
- 'https-uri': HTTPSURIType(),
- 'translatable': TranslatableType(),
-}
-
-
-class PluggableConfig(object):
-
- options = {}
-
- def __init__(self,
- adaptors=adaptors,
- types=types,
- format=None):
-
- self.config = {}
- self.adaptors = adaptors
- self.types = types
- self._format = format
- self.mtime = None
- self.dirty = False
-
- @property
- def option_dict(self):
- if hasattr(self, 'options') and isinstance(self.options, dict):
- return self.options.get('properties', None)
-
- def items(self):
- """
- act like an iterator
- """
- if isinstance(self.option_dict, dict):
- return self.option_dict.items()
- return self.options
-
- def validate(self, config, format=None):
- """
- validate config
- """
- schema = self.options
- if format is None:
- format = self._format
-
- if format:
- adaptor = self.get_adaptor(self._format)
- adaptor.validate(config, schema)
- else:
- # we really should make format mandatory...
- logger.error('no format passed to validate')
-
- # first round of validation is ok.
- # now we proceed to cast types if any specified.
- self.to_python(config)
-
- def to_python(self, config):
- """
- cast types following first type and then format indications.
- """
- unseen_options = [i for i in config if i not in self.option_dict]
- if unseen_options:
- raise UnknownOptionException(
- "Unknown options: %s" % ', '.join(unseen_options))
-
- for key, value in config.items():
- _type = self.option_dict[key].get('type')
- if _type is None and 'default' in self.option_dict[key]:
- _type = type(self.option_dict[key]['default'])
- if _type is not None:
- tocast = True
- if not callable(_type) and isinstance(value, _type):
- tocast = False
- if tocast:
- try:
- config[key] = _type(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not coerce %s, %s, "
- "to type %s: %s" % (key, value, _type.__name__, e))
- _format = self.option_dict[key].get('format', None)
- _ftype = self.types.get(_format, None)
- if _ftype:
- try:
- config[key] = _ftype.to_python(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not coerce %s, %s, "
- "to format %s: %s" % (key, value,
- _ftype.__class__.__name__,
- e))
-
- return config
-
- def prep_value(self, config):
- """
- the inverse of to_python method,
- called just before serialization
- """
- for key, value in config.items():
- _format = self.option_dict[key].get('format', None)
- _ftype = self.types.get(_format, None)
- if _ftype and hasattr(_ftype, 'get_prep_value'):
- try:
- config[key] = _ftype.get_prep_value(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not serialize %s, %s, "
- "by format %s: %s" % (key, value,
- _ftype.__class__.__name__,
- e))
- else:
- config[key] = value
- return config
-
- # methods for adding configuration
-
- def get_default_values(self):
- """
- return a config options from configuration defaults
- """
- defaults = {}
- for key, value in self.items():
- if 'default' in value:
- defaults[key] = value['default']
- return copy.deepcopy(defaults)
-
- def get_adaptor(self, format):
- """
- get specified format adaptor or
- guess for a given filename
- """
- adaptor = self.adaptors.get(format, None)
- if adaptor:
- return adaptor
-
- # not registered in adaptors dict, let's try all
- for adaptor in self.adaptors.values():
- if format in adaptor.extensions:
- return adaptor
-
- def filename2format(self, filename):
- extension = os.path.splitext(filename)[-1]
- return extension.lstrip('.') or None
-
- def serialize(self, filename, format=None, full=False):
- if not format:
- format = self._format
- if not format:
- format = self.filename2format(filename)
- if not format:
- raise Exception('Please specify a format')
- # TODO: more specific exception type
-
- adaptor = self.get_adaptor(format)
- if not adaptor:
- raise Exception("Adaptor not found for format: %s" % format)
-
- config = copy.deepcopy(self.config)
- serializable = self.prep_value(config)
- adaptor.write(serializable, filename)
-
- if self.mtime:
- self.touch_mtime(filename)
-
- def touch_mtime(self, filename):
- mtime = self.mtime
- os.utime(filename, (mtime, mtime))
-
- def deserialize(self, string=None, fromfile=None, format=None):
- """
- load configuration from a file or string
- """
-
- def _try_deserialize():
- if fromfile:
- with open(fromfile, 'r') as f:
- content = adaptor.read(f)
- elif string:
- content = adaptor.read(string)
- return content
-
- # XXX cleanup this!
-
- if fromfile:
- leap_assert(os.path.exists(fromfile))
- if not format:
- format = self.filename2format(fromfile)
-
- if not format:
- format = self._format
- if format:
- adaptor = self.get_adaptor(format)
- else:
- adaptor = None
-
- if adaptor:
- content = _try_deserialize()
- return content
-
- # no adaptor, let's try rest of adaptors
-
- adaptors = self.adaptors[:]
-
- if format:
- adaptors.sort(
- key=lambda x: int(
- format in x.extensions),
- reverse=True)
-
- for adaptor in adaptors:
- content = _try_deserialize()
- return content
-
- def set_dirty(self):
- self.dirty = True
-
- def is_dirty(self):
- return self.dirty
-
- def load(self, *args, **kwargs):
- """
- load from string or file
- if no string of fromfile option is given,
- it will attempt to load from defaults
- defined in the schema.
- """
- string = args[0] if args else None
- fromfile = kwargs.get("fromfile", None)
- mtime = kwargs.pop("mtime", None)
- self.mtime = mtime
- content = None
-
- # start with defaults, so we can
- # have partial values applied.
- content = self.get_default_values()
- if string and isinstance(string, str):
- content = self.deserialize(string)
-
- if not string and fromfile is not None:
- #import ipdb;ipdb.set_trace()
- content = self.deserialize(fromfile=fromfile)
-
- if not content:
- logger.error('no content could be loaded')
- # XXX raise!
- return
-
- # lazy evaluation until first level of nesting
- # to allow lambdas with context-dependant info
- # like os.path.expanduser
- for k, v in content.iteritems():
- if callable(v):
- content[k] = v()
-
- self.validate(content)
- self.config = content
- return True
-
-
-def testmain(): # pragma: no cover
-
- from tests import test_validation as t
- import pprint
-
- config = PluggableConfig(_format="json")
- properties = copy.deepcopy(t.sample_spec)
-
- config.options = properties
- config.load(fromfile='data.json')
-
- print 'config'
- pprint.pprint(config.config)
-
- config.serialize('/tmp/testserial.json')
-
-if __name__ == "__main__":
- testmain()
diff --git a/src/leap/config/prefixers.py b/src/leap/config/prefixers.py
deleted file mode 100644
index 72211790..00000000
--- a/src/leap/config/prefixers.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# -*- coding: utf-8 -*-
-# prefixers.py
-# Copyright (C) 2013 LEAP
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Platform dependant configuration path prefixers
-"""
-import os
-import platform
-
-from abc import ABCMeta, abstractmethod
-
-from xdg import BaseDirectory
-
-from leap.common.check import leap_assert
-
-
-class Prefixer:
- """
- Abstract prefixer class
- """
-
- __metaclass__ = ABCMeta
-
- @abstractmethod
- def get_path_prefix(self, standalone=False):
- """
- Returns the platform dependant path prefixer
-
- @param standalone: if True it will return the prefix for a
- standalone application. Otherwise, it will return the system
- default for configuration storage.
- @type standalone: bool
- """
- return ""
-
-
-def get_platform_prefixer():
- prefixer = globals()[platform.system() + "Prefixer"]
- leap_assert(prefixer, "Unimplemented platform prefixer: %s" %
- (platform.system(),))
- return prefixer()
-
-
-class LinuxPrefixer(Prefixer):
- """
- Config prefixer for the Linux platform
- """
-
- def get_path_prefix(self, standalone=False):
- """
- Returns the platform dependant path prefixer.
- This method expects an env variable named LEAP_CLIENT_PATH if
- standalone is used.
-
- @param standalone: if True it will return the prefix for a
- standalone application. Otherwise, it will return the system
- default for configuration storage.
- @type standalone: bool
- """
- config_dir = BaseDirectory.xdg_config_home
- if not standalone:
- return config_dir
- return os.path.join(os.getcwd(), "config")
-
-
-class DarwinPrefixer(Prefixer):
- """
- Config prefixer for the Darwin platform
- """
-
- def get_path_prefix(self, standalone=False):
- """
- Returns the platform dependant path prefixer.
- This method expects an env variable named LEAP_CLIENT_PATH if
- standalone is used.
-
- @param standalone: if True it will return the prefix for a
- standalone application. Otherwise, it will return the system
- default for configuration storage.
- @type standalone: bool
- """
- config_dir = BaseDirectory.xdg_config_home
- if not standalone:
- return config_dir
- return os.getenv(os.getcwd(), "config")
-
-
-class WindowsPrefixer(Prefixer):
- """
- Config prefixer for the Windows platform
- """
-
- def get_path_prefix(self, standalone=False):
- """
- Returns the platform dependant path prefixer.
- This method expects an env variable named LEAP_CLIENT_PATH if
- standalone is used.
-
- @param standalone: if True it will return the prefix for a
- standalone application. Otherwise, it will return the system
- default for configuration storage.
- @type standalone: bool
- """
- config_dir = BaseDirectory.xdg_config_home
-
- if not standalone:
- return config_dir
- return os.path.join(os.getcwd(), "config")
-
-if __name__ == "__main__":
- try:
- abs_prefixer = Prefixer()
- except Exception as e:
- assert isinstance(e, TypeError), "Something went wrong"
- print "Abstract Prefixer class is working as expected"
-
- linux_prefixer = LinuxPrefixer()
- print linux_prefixer.get_path_prefix(standalone=True)
- print linux_prefixer.get_path_prefix()
diff --git a/src/leap/config/providerconfig.py b/src/leap/config/providerconfig.py
index 7651863b..5aa0cc6e 100644
--- a/src/leap/config/providerconfig.py
+++ b/src/leap/config/providerconfig.py
@@ -22,7 +22,7 @@ import logging
import os
from leap.common.check import leap_assert
-from leap.config.baseconfig import BaseConfig, LocalizedKey
+from leap.common.config.baseconfig import BaseConfig, LocalizedKey
from leap.config.provider_spec import leap_provider_spec
logger = logging.getLogger(__name__)
diff --git a/src/leap/services/eip/eipconfig.py b/src/leap/services/eip/eipconfig.py
index 3f873878..4e74687a 100644
--- a/src/leap/services/eip/eipconfig.py
+++ b/src/leap/services/eip/eipconfig.py
@@ -22,7 +22,7 @@ import os
import logging
from leap.common.check import leap_assert, leap_assert_type
-from leap.config.baseconfig import BaseConfig
+from leap.common.config.baseconfig import BaseConfig
from leap.config.providerconfig import ProviderConfig
from leap.services.eip.eipspec import eipservice_config_spec