Skip to content

Commit

Permalink
:add: besteffort in the evaluation function (try to resolve unknown v…
Browse files Browse the repository at this point in the history
…ariables) and add the xml driver
  • Loading branch information
b3j0f committed Feb 24, 2016
1 parent 838abbf commit 6e51155
Show file tree
Hide file tree
Showing 24 changed files with 600 additions and 164 deletions.
3 changes: 2 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include README.rst LICENSE
include README.rst LICENSE requirements.txt changelog.rst
graft etc
prune docs
26 changes: 22 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Description
-----------

Python class configuration tools in reflective and distributed concerns.
Python class/function configuration tools in reflective and distributed concerns.

.. image:: https://img.shields.io/pypi/l/b3j0f.conf.svg
:target: https://pypi.python.org/pypi/b3j0f.conf/
Expand Down Expand Up @@ -62,12 +62,12 @@ pip install b3j0f.conf
Features
--------

This library provides a set of class configuration tools in order to ease development of systems based on configuration resources such as files, DB documents, etc. in a reflexive context.
This library provides a set of class configuration tools in order to ease development of systems based on configuration resources such as files, DB documents, etc. in a reflexive, functional and distributed context.

Configuration process is in 2 steps:

- inject configuration resources (file, DB documents, etc.) in a Configurable class (with specific drivers which allows the Configurable class to be agnostic from configuration languages such as ini, json, xml, etc.),
- let the configurable class read properties from such configuration resources and apply values on a dedicated class which may be a associated to a business code. This last process can be done automatically or manually thanks to the the applyconfiguration method.
- let the configurable class read properties from such configuration resources and apply values on a dedicated class or function which may be a associated to a business code. This last process can be done automatically or manually thanks to the the applyconfiguration method.

Configuration
#############
Expand Down Expand Up @@ -165,7 +165,7 @@ The following code permits to load upper configuration to a python object.

.. code-block:: python
from b3j0f.conf import Configurable, Category
from b3j0f.conf import Configurable
# instantiate a business class
@Configurable(paths='myobject.conf')
Expand All @@ -179,6 +179,24 @@ The following code permits to load upper configuration to a python object.
assert myobject.six == 6
assert myobject.twelve == 12
The following code permits to load upper configuration to a python function.

.. code-block:: python
from b3j0f.conf import Configurable
# instantiate a business class
@Configurable(paths='myobject.conf')
def myfunc(myattr, six, twelve):
return myattr, six, twelve
myattr, six, twelve = myfunc(twelve=46)
# assert attributes
assert myobject.myattr == 'myvalue'
assert myobject.six == 6
assert myobject.twelve == 46
Configure several objects with one configurable
###############################################

Expand Down
125 changes: 80 additions & 45 deletions b3j0f/conf/configurable/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@

"""Specification of the class Configurable."""

__all__ = ['MetaConfigurable', 'Configurable']
__all__ = ['Configurable']

from six import string_types, add_metaclass
from six import string_types
from six.moves import reload_module

from inspect import getargspec, isclass

from b3j0f.utils.path import lookup
from b3j0f.utils.version import getcallargs

from b3j0f.annotation import PrivateInterceptor

Expand All @@ -38,43 +44,9 @@
from ..driver.file.json import JSONConfDriver
from ..driver.file.ini import INIConfDriver


class MetaConfigurable(type):
"""Meta class for Configurable."""

INIT_CAT = 'init_cat' #: initialization category.

def __call__(cls, *args, **kwargs):
"""Get a new instance of input cls class, and if instance.auto_conf or
instance.reconf_once, then call instance.applyconfiguration().
"""

result = type.__call__(cls, *args, **kwargs)

if result.autoconf:
# get configuration
conf = result.conf

# add a last category which contains args and kwargs as parameters
if kwargs:
init_category = Category(MetaConfigurable.INIT_CAT)

for name in kwargs:
param = Parameter(name=name, value=kwargs[name])
init_category += param

conf += init_category

# apply configuration
result.applyconfiguration(conf=conf)

return result

#: toconfigure configurable attribute name.
__CONFIGURABLES__ = '__configurables__'


@add_metaclass(MetaConfigurable)
class Configurable(PrivateInterceptor):
"""Manage class conf synchronisation with conf resources.
Expand Down Expand Up @@ -110,7 +82,7 @@ def __init__(
conf=None, inheritedconf=DEFAULT_INHERITEDCONF, confpath=None,
store=DEFAULT_STORE, paths=None, drivers=DEFAULT_DRIVERS,
foreigns=DEFAULT_FOREIGNS, autoconf=DEFAULT_AUTOCONF,
toconfigure=(), safe=DEFAULT_SAFE,
toconfigure=(), safe=DEFAULT_SAFE, modules=None, callparams=True,
*args, **kwargs
):
"""
Expand All @@ -137,7 +109,9 @@ def __init__(
:param bool safe: if True (default), expression parser are used in a
safe context to resolve python object. For example, if safe,
builtins function such as `open` are not resolvable.
"""
:param list modules: required modules.
:param bool callparams: if True (default), use parameters in the
configured callable function."""

super(Configurable, self).__init__(*args, **kwargs)

Expand All @@ -146,6 +120,7 @@ def __init__(
self._conf = None
self._toconfigure = []
self._confpath = confpath
self._modules = []

# init public attributes
self.store = store
Expand All @@ -161,24 +136,84 @@ def __init__(
self.paths = paths
self.safe = safe
self.autoconf = autoconf # end of dirty hack
self.modules = modules
self.callparams = callparams

def _interception(self, joinpoint):

toconfigure = result = joinpoint.proceed()
if self.callparams:

if toconfigure is None:
if 'self' in joinpoint.kwargs:
toconfigure = joinpoint.kwargs['self']
conf = self.conf

params = conf.params

args, kwargs = joinpoint.args, joinpoint.kwargs

target = joinpoint.target

try:
argspec = getargspec(target)

except TypeError:
argspec = None
callargs = None

else:
toconfigure = joinpoint.args[0]
callargs = getcallargs(
target, args, kwargs
)

for param in params:

if argspec is None:
args.append(param.value)

elif param.name not in callargs and (
param.name in argspec.args or self.foreigns
):

kwargs[param.name] = param.value

toconfigure = result = joinpoint.proceed()

if self.autoconf:

self.toconfigure += [toconfigure]
if isclass(target):

self.applyconfiguration(toconfigure=toconfigure)
if toconfigure is None:
if 'self' in kwargs:
toconfigure = kwargs['self']

else:
toconfigure = args[0]

if isinstance(toconfigure, target):

self.toconfigure += [toconfigure]

self.applyconfiguration(toconfigure=toconfigure)

return result

@property
def modules(self):
"""Get this required modules."""

return self._modules

@modules.setter
def modules(self, value):
"""Change required modules.
Reload modules given in the value.
:param list value: new modules to use."""

self._modules = value
if value:
for module in value:
reload_module(lookup(module))

@property
def confpath(self):
"""Get configuration path.
Expand Down
33 changes: 5 additions & 28 deletions b3j0f/conf/driver/file/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

from __future__ import absolute_import

__all__ = ['JSONConfDriver']
__all__ = ['JSONFileConfDriver']

try:
from json import load, dump
Expand All @@ -37,14 +37,11 @@
from simplejson import load, dump

from .base import FileConfDriver
from ..json import JSONConfDriver


class JSONConfDriver(FileConfDriver):
"""Manage json resource configuration."""

def resource(self):

return {}
class JSONFileConfDriver(FileConfDriver, JSONConfDriver):
"""Manage json resource configuration from json file."""

def _pathresource(self, rscpath):

Expand All @@ -56,29 +53,9 @@ def _pathresource(self, rscpath):

return result

def _cnames(self, resource):

return resource.keys()

def _params(self, resource, cname):

params = resource[cname]

result = [
(key, params[key]) for key in params
]

return result

def _setconf(self, conf, resource, rscpath):

for category in conf:

cat = resource.setdefault(category.name, {})

for parameter in category:

cat[parameter.name] = parameter.svalue
super(JSONFileConfDriver, self)._setconf(conf, resource, rscpath)

with open(rscpath, 'w') as fpw:

Expand Down
54 changes: 54 additions & 0 deletions b3j0f/conf/driver/file/xml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2014 Jonathan Labéjof <jonathan.labejof@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# --------------------------------------------------------------------

"""XML configuration file driver."""

from __future__ import absolute_import

__all__ = ['XMLFileConfDriver']

from xml.etree.ElementTree import parse

from .base import FileConfDriver
from ..xml import XMLConfDriver


class XMLFileConfDriver(FileConfDriver, XMLConfDriver):
"""Manage xml resource configuration from file."""

def _pathresource(self, rscpath):

result = parse(rscpath).getroot()

return result

def _setconf(self, conf, resource, rscpath):

super(XMLFileConfDriver, self)._setconf(
conf=conf, resource=resource, rscpath=rscpath
)

resource.write(rscpath)

0 comments on commit 6e51155

Please sign in to comment.