From a38e61691a79b20199cdedf23f60a5760bba7a06 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 21 Sep 2012 06:44:19 +0900 Subject: add property to baseconfig config instead of get_config() --- src/leap/base/config.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src/leap/base/config.py') diff --git a/src/leap/base/config.py b/src/leap/base/config.py index 76fbee3c..79185976 100644 --- a/src/leap/base/config.py +++ b/src/leap/base/config.py @@ -38,13 +38,9 @@ class BaseLeapConfig(object): def get_config(self, *kwargs): raise NotImplementedError("abstract base class") - #XXX todo: enable this property after - #fixing name clash with "config" in use at - #vpnconnection - - #@property - #def config(self): - #return self.get_config() + @property + def config(self): + return self.get_config() def get_value(self, *kwargs): raise NotImplementedError("abstract base class") -- cgit v1.2.3 From 30570bd89c04a56b35b91a0bc1d5fc00bb6ad266 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 24 Sep 2012 22:21:50 +0900 Subject: add schema to JSONLeapConfig classes and a jsonvalidate function too, that calls to jsonchemea.validate(self, data) with self.schema We're using the specs to both purposes now: * providing a type casting system for our config options (work in progress for the type casting) * json schema validation --- src/leap/base/config.py | 58 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) (limited to 'src/leap/base/config.py') diff --git a/src/leap/base/config.py b/src/leap/base/config.py index 79185976..3854c2c2 100644 --- a/src/leap/base/config.py +++ b/src/leap/base/config.py @@ -12,6 +12,7 @@ logger = logging.getLogger(name=__name__) logger.setLevel('DEBUG') import configuration +import jsonschema import requests from leap.base import exceptions @@ -46,30 +47,58 @@ class BaseLeapConfig(object): raise NotImplementedError("abstract base class") +class SchemaEncoder(json.JSONEncoder): + def default(self, obj): + if obj is str: + return 'string' + if obj is unicode: + return 'string' + if obj is int: + return 'int' + if obj is list: + return 'array' + if obj is dict: + return object + + class MetaConfigWithSpec(type): """ metaclass for JSONLeapConfig classes. It creates a configuration spec out of - the `spec` dictionary. + the `spec` dictionary. The `properties` attribute + of the spec dict is turn into the `schema` attribute + of the new class (which will be used to validate against). """ # XXX in the near future, this is the # place where we want to enforce - # singletons, read-only and stuff. + # singletons, read-only and similar stuff. # TODO: # - add a error handler for missing options that # we can act easily upon (sys.exit is ugly, for $deity's sake) def __new__(meta, classname, bases, classDict): - spec_options = classDict.get('spec', None) + schema_obj = classDict.get('spec', None) + if schema_obj: + spec_options = schema_obj.get('properties', None) + schema_json = SchemaEncoder().encode(schema_obj) + schema = json.loads(schema_json) + else: + spec_options = None + schema = None # not quite happy with this workaround. # I want to raise if missing spec dict, but only # for grand-children of this metaclass. # maybe should use abc module for this. abcderived = ("JSONLeapConfig",) if spec_options is None and classname not in abcderived: - raise exceptions.ImproperlyConfigured( - "missing spec dict on your derived class") + if not schema_obj: + raise exceptions.ImproperlyConfigured( + "missing spec dict on your derived class (%s)" % classname) + if schema_obj and not spec_options: + raise exceptions.ImproperlyConfigured( + "missing properties attr in spec dict " + "on your derived class (%s)" % classname) # we create a configuration spec attribute from the spec dict config_class = type( @@ -77,6 +106,8 @@ class MetaConfigWithSpec(type): (configuration.Configuration, object), {'options': spec_options}) classDict['spec'] = config_class + # A shipped json-schema for validation + classDict['schema'] = schema return type.__new__(meta, classname, bases, classDict) @@ -96,8 +127,8 @@ class MetaConfigWithSpec(type): # - get_config (returns a optparse.OptionParser object) # TODO: +# [done] raise validation errors # - have a good type cast repertory (uris, version, hashes...) -# - raise validation errors # - multilingual objects ########################################################## @@ -151,9 +182,14 @@ class JSONLeapConfig(BaseLeapConfig): return if fromfile is None: fromfile = self.filename - newconfig = self._config.deserialize(fromfile) - # XXX check for no errors, etc - self._config.config = newconfig + if os.path.isfile(fromfile): + newconfig = self._config.deserialize(fromfile) + # XXX check for no errors, etc + # XXX could validate here! + self._config.config = newconfig + else: + logger.error('tried to load config from non-existent path') + logger.error('Not Found: %s', fromfile) def fetch(self, uri, fetcher=None, verify=True): if not fetcher: @@ -187,6 +223,10 @@ class JSONLeapConfig(BaseLeapConfig): def filename(self): return self.get_filename() + def jsonvalidate(self, data): + jsonschema.validate(data, self.schema) + return True + # private def _loadtemp(self, filename): -- cgit v1.2.3 From 5d8e518d03e9fd045a75a63fec79b52392266c26 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 25 Sep 2012 07:19:07 +0900 Subject: make test for provider pass --- src/leap/base/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/base/config.py') diff --git a/src/leap/base/config.py b/src/leap/base/config.py index 3854c2c2..7f69a41c 100644 --- a/src/leap/base/config.py +++ b/src/leap/base/config.py @@ -58,7 +58,7 @@ class SchemaEncoder(json.JSONEncoder): if obj is list: return 'array' if obj is dict: - return object + return 'object' class MetaConfigWithSpec(type): -- cgit v1.2.3 From abf481cab381a86d8a9c5607a131b56636081382 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 25 Sep 2012 05:48:06 +0900 Subject: refactored jsonconfig, included jsonschema validation and type casting. --- src/leap/base/config.py | 108 ++++++++++++++---------------------------------- 1 file changed, 32 insertions(+), 76 deletions(-) (limited to 'src/leap/base/config.py') diff --git a/src/leap/base/config.py b/src/leap/base/config.py index 7f69a41c..dc047f80 100644 --- a/src/leap/base/config.py +++ b/src/leap/base/config.py @@ -9,14 +9,12 @@ import tempfile import os logger = logging.getLogger(name=__name__) -logger.setLevel('DEBUG') -import configuration -import jsonschema import requests from leap.base import exceptions from leap.base import constants +from leap.base.pluggableconfig import PluggableConfig from leap.util.fileutil import (mkdir_p) # move to base! @@ -47,20 +45,6 @@ class BaseLeapConfig(object): raise NotImplementedError("abstract base class") -class SchemaEncoder(json.JSONEncoder): - def default(self, obj): - if obj is str: - return 'string' - if obj is unicode: - return 'string' - if obj is int: - return 'int' - if obj is list: - return 'array' - if obj is dict: - return 'object' - - class MetaConfigWithSpec(type): """ metaclass for JSONLeapConfig classes. @@ -73,63 +57,43 @@ class MetaConfigWithSpec(type): # place where we want to enforce # singletons, read-only and similar stuff. - # TODO: - # - add a error handler for missing options that - # we can act easily upon (sys.exit is ugly, for $deity's sake) - def __new__(meta, classname, bases, classDict): schema_obj = classDict.get('spec', None) - if schema_obj: - spec_options = schema_obj.get('properties', None) - schema_json = SchemaEncoder().encode(schema_obj) - schema = json.loads(schema_json) - else: - spec_options = None - schema = None + # not quite happy with this workaround. # I want to raise if missing spec dict, but only # for grand-children of this metaclass. # maybe should use abc module for this. abcderived = ("JSONLeapConfig",) - if spec_options is None and classname not in abcderived: - if not schema_obj: - raise exceptions.ImproperlyConfigured( - "missing spec dict on your derived class (%s)" % classname) - if schema_obj and not spec_options: - raise exceptions.ImproperlyConfigured( - "missing properties attr in spec dict " - "on your derived class (%s)" % classname) - - # we create a configuration spec attribute from the spec dict + if schema_obj is None and classname not in abcderived: + raise exceptions.ImproperlyConfigured( + "missing spec dict on your derived class (%s)" % classname) + + # we create a configuration spec attribute + # from the spec dict config_class = type( classname + "Spec", - (configuration.Configuration, object), - {'options': spec_options}) + (PluggableConfig, object), + {'options': schema_obj}) classDict['spec'] = config_class - # A shipped json-schema for validation - classDict['schema'] = schema return type.__new__(meta, classname, bases, classDict) ########################################################## -# hacking in progress: +# some hacking still in progress: # Configs have: + # - a slug (from where a filename/folder is derived) # - a spec (for validation and defaults). -# this spec is basically a dict that will be used +# this spec is conformant to the json-schema. +# basically a dict that will be used # for type casting and validation, and defaults settings. # all config objects, since they are derived from BaseConfig, implement basic # useful methods: # - save # - load -# - get_config (returns a optparse.OptionParser object) - -# TODO: -# [done] raise validation errors -# - have a good type cast repertory (uris, version, hashes...) -# - multilingual objects ########################################################## @@ -152,10 +116,10 @@ class JSONLeapConfig(BaseLeapConfig): raise exceptions.ImproperlyConfigured( "missing spec on JSONLeapConfig" " derived class") - assert issubclass(self.spec, configuration.Configuration) + assert issubclass(self.spec, PluggableConfig) - self._config = self.spec() - self._config.parse_args(list(args)) + self._config = self.spec(format="json") + self._config.load() self.fetcher = kwargs.pop('fetcher', requests) # mandatory baseconfig interface @@ -166,13 +130,6 @@ class JSONLeapConfig(BaseLeapConfig): folder, filename = os.path.split(to) if folder and not os.path.isdir(folder): mkdir_p(folder) - # lazy evaluation until first level of nesting - # to allow lambdas with context-dependant info - # like os.path.expanduser - config = self.get_config() - for k, v in config.iteritems(): - if callable(v): - config[k] = v() self._config.serialize(to) def load(self, fromfile=None, from_uri=None, fetcher=None, verify=False): @@ -183,10 +140,7 @@ class JSONLeapConfig(BaseLeapConfig): if fromfile is None: fromfile = self.filename if os.path.isfile(fromfile): - newconfig = self._config.deserialize(fromfile) - # XXX check for no errors, etc - # XXX could validate here! - self._config.config = newconfig + self._config.load(fromfile=fromfile) else: logger.error('tried to load config from non-existent path') logger.error('Not Found: %s', fromfile) @@ -196,19 +150,25 @@ class JSONLeapConfig(BaseLeapConfig): fetcher = self.fetcher logger.debug('verify: %s', verify) request = fetcher.get(uri, verify=verify) + # XXX should send a if-modified-since header # XXX get 404, ... # and raise a UnableToFetch... request.raise_for_status() fd, fname = tempfile.mkstemp(suffix=".json") - if not request.json: + + if request.json: + self._config.load(json.dumps(request.json)) + + else: + # not request.json + # might be server did not announce content properly, + # let's try deserializing all the same. try: - json.loads(request.content) + self._config.load(request.content) except ValueError: raise eipexceptions.LeapBadConfigFetchedError - with open(fname, 'w') as tmp: - tmp.write(json.dumps(request.json)) - self._loadtemp(fname) + return True def get_config(self): @@ -223,20 +183,16 @@ class JSONLeapConfig(BaseLeapConfig): def filename(self): return self.get_filename() - def jsonvalidate(self, data): - jsonschema.validate(data, self.schema) + def validate(self, data): + logger.debug('validating schema') + self._config.validate(data) return True # private - def _loadtemp(self, filename): - self.load(fromfile=filename) - os.remove(filename) - def _slug_to_filename(self): # is this going to work in winland if slug is "foo/bar" ? folder, filename = os.path.split(self.slug) - # XXX fix import config_file = get_config_file(filename, folder) return config_file -- cgit v1.2.3