Skip to content

Commit

Permalink
:add: resolver.core module
Browse files Browse the repository at this point in the history
  • Loading branch information
b3j0f committed Feb 26, 2016
1 parent 1020bda commit 6085237
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 38 deletions.
8 changes: 7 additions & 1 deletion b3j0f/conf/parser/resolver/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@

from .registry import register

from .core import (
DEFAULT_BESTEFFORT, DEFAULT_SAFE, DEFAULT_TOSTR, DEFAULT_SCOPE
)


class _MetaExprResolver(type):
"""Expression Resolver meta class.
Expand All @@ -73,7 +77,9 @@ class ExprResolver(object):
All sub classes are automatically registered."""

def __call__(
self, expr, safe=True, tostr=False, scope=None, besteffort=True
self, expr,
safe=DEFAULT_SAFE, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE,
besteffort=DEFAULT_BESTEFFORT
):
"""Resolve input expression.
Expand Down
60 changes: 60 additions & 0 deletions b3j0f/conf/parser/resolver/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2015 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.
# --------------------------------------------------------------------

"""core resolver module.
Contains signature of the resolver function and and default parameter values."""

__all__ = [
'DEFAULT_BESTEFFORT', 'DEFAULT_SAFE', 'DEFAULT_TOSTR', 'DEFAULT_SCOPE',
'resolver'
]

DEFAULT_BESTEFFORT = True #: default best effort execution context flag.
DEFAULT_SAFE = True #: default safe execuction context flag.
DEFAULT_TOSTR = False #: default conversion to str flag.
DEFAULT_SCOPE = None #: default scope execution context.

def resolver(
expr, safe=DEFAULT_SAFE, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE,
besteffort=DEFAULT_BESTEFFORT
):
"""Resolve input expression.
This function is given such as template resolution function. For wrapping
test for example.
:param str expr: configuration expression to resolve.
:param bool safe: if True (default), run safely execution context.
:param bool tostr: format the result.
:param dict scope: execution scope (contains references to expression
objects).
:param bool besteffort: if True (default), try to resolve unknown variable
name with execution runtime.
:return: resolved expression."""


raise NotImplementedError()
10 changes: 9 additions & 1 deletion b3j0f/conf/parser/resolver/lang/js.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@

from ..registry import register

from ..core import (
DEFAULT_BESTEFFORT, DEFAULT_SAFE, DEFAULT_TOSTR, DEFAULT_SCOPE
)

try:
from PyV8 import JSContext

Expand All @@ -50,7 +54,11 @@ def resolvejs(**_):
CTXT = JSContext()

@register('js')
def resolvejs(expr, tostr=False, scope=None, **_):
@register('pyv8')
def resolvejs(
expr, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE, safe=DEFAULT_SAFE,
besteffort=DEFAULT_BESTEFFORT
):
"""Javascript resolver."""

_ctxt = CTXT if scope is None else JSContext(scope)
Expand Down
9 changes: 8 additions & 1 deletion b3j0f/conf/parser/resolver/lang/py.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
from copy import deepcopy

from ..registry import register
from ..core import (
DEFAULT_BESTEFFORT, DEFAULT_SAFE, DEFAULT_TOSTR, DEFAULT_SCOPE
)


MISSING_NAME = r'\'(?P<name>\w+)\''
Expand Down Expand Up @@ -63,7 +66,11 @@ def repl(match):


@register('py')
def resolvepy(expr, safe=True, tostr=False, scope=None, besteffort=True):
def resolvepy(
expr,
safe=DEFAULT_SAFE, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE,
besteffort=DEFAULT_BESTEFFORT
):
"""Resolve input expression.
:param str expr: configuration expression to resolve in this language.
Expand Down
79 changes: 53 additions & 26 deletions b3j0f/conf/parser/resolver/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@

__all__ = [
'ResolverRegistry',
'names', 'resolve', 'register', 'loadresolvers', 'defaultname', 'getname'
'names', 'resolve', 'register', 'unregister', 'loadresolvers',
'defaultname', 'getname'
]

from b3j0f.utils.path import lookup
Expand All @@ -61,6 +62,10 @@

from os import getenv

from .core import (
DEFAULT_BESTEFFORT, DEFAULT_SAFE, DEFAULT_TOSTR, DEFAULT_SCOPE
)

#: env variable name of exprres modules.
B3J0F_EXPRRES_PATH = 'B3J0F_EXPRRES_PATH'

Expand All @@ -73,54 +78,55 @@
__RESOLVER__ = '__resolver__' #: resolver name for class IoC


class ResolverRegistry(object):
class ResolverRegistry(OrderedDict):
"""Resolver registry."""

__slots__ = ('resolvers', '_default')
__slots__ = ('_default')

def __init__(self, default=None, **resolvers):

super(ResolverRegistry, self).__init__(resolvers)

self._default = None

self.resolvers = OrderedDict(resolvers)
self.default = default

@property
def default(self):
"""Get default resolver name."""

if self._default is None or self._default not in self:
self._default = list(self.keys())[0] if self else None

return self._default

@default.setter
def default(self, value):
"""Change of resolver name.
:param str value: new default value to use.
:raises: NameError if value is not registered."""
:param value: new default value to use.
:type value: str or callable
:raises: KeyError if value is a string not already registered."""

if value is None:
if self.resolvers:
value = list(self.resolvers.keys())[0]
if self:
value = list(self.keys())[0]

elif not isinstance(value, string_types):
value = register(exprresolver=value, reg=self)

elif value not in self.resolvers:
raise NameError(
'{0} not registered in {1}'.format(value, self.resolvers)
elif value not in self:
raise KeyError(
'{0} not registered in {1}'.format(value, self)
)

self._default = value

@property
def names(self):
"""Get all resolver names.
:rtype: list"""

return list(self.resolvers.keys())

def resolve(self, expr, name=None, safe=True, tostr=False, scope=None):
def resolve(
self, expr, name,
safe=DEFAULT_SAFE, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE,
besteffort=DEFAULT_BESTEFFORT
):
"""Resolve an expression with possibly a dedicated expression resolvers.
:param str name: expression resolver registered name. Default is the
Expand All @@ -141,9 +147,12 @@ def resolve(self, expr, name=None, safe=True, tostr=False, scope=None):
if name is None:
name = self.default

resolver = self.resolvers[name]
resolver = self[name]

result = resolver(expr=expr, safe=safe, tostr=tostr, scope=scope)
result = resolver(
expr=expr,
safe=safe, tostr=tostr, scope=scope, besteffort=besteffort
)

return result

Expand Down Expand Up @@ -225,7 +234,7 @@ def _register(exprresolver, _name=name, _params=params):
if _name is None:
_name = getname(exprresolver)

reg.resolvers[_name] = _exprresolver
reg[_name] = _exprresolver

if reg.default is None:
reg.default = _name
Expand All @@ -248,6 +257,16 @@ def _register(exprresolver, _name=name, _params=params):
return result


def unregister(name):
"""Unregister resolver.
:param str name: resolver name to unregister.
:return: named resolver.
:rtype: callable"""

return _RESOLVER_REGISTRY.pop(name)


def defaultname(name=None):
"""Get default resolver name.
Expand All @@ -268,9 +287,13 @@ def names():
:rtype: list"""

return _RESOLVER_REGISTRY.names
return list(_RESOLVER_REGISTRY.keys())

def resolve(*args, **kwargs):
def resolve(
expr, name=None,
safe=DEFAULT_SAFE, tostr=DEFAULT_TOSTR, scope=DEFAULT_SCOPE,
besteffort=DEFAULT_BESTEFFORT
):
"""Resolve an expression with possibly a dedicated expression resolvers.
:param str name: expression resolver registered name. Default is the
Expand All @@ -280,12 +303,16 @@ def resolve(*args, **kwargs):
:param bool tostr: if True (False by default), transform the result into
a string format.
:param dict scope: scope execution resolution.
:param bool besteffort: if True (default) auto import unknown var.
:raises: KeyError if no expression resolver has been registered or if
name does not exist in expression resolvers.
"""

return _RESOLVER_REGISTRY.resolve(*args, **kwargs)
return _RESOLVER_REGISTRY.resolve(
expr=expr, name=name, safe=safe, tostr=tostr, scope=scope,
besteffort=besteffort
)

def getname(exprresolver):
"""Get expression resolver name.
Expand Down
33 changes: 24 additions & 9 deletions b3j0f/conf/parser/resolver/test/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from ..registry import (
ResolverRegistry, names, resolve, register, loadresolvers, defaultname,
getname
getname, unregister
)


Expand Down Expand Up @@ -98,7 +98,7 @@ def test_defaultregistry(self):

self.assertIsNone(reg.default)

self.assertFalse(reg.resolvers)
self.assertFalse(reg)

def test_defaultwithregistry(self):

Expand All @@ -108,7 +108,7 @@ def test_defaultwithregistry(self):

self.assertIs(reg.default, name)

self.assertTrue(reg.resolvers)
self.assertTrue(reg)

def test_nodefaultwithoutregistry(self):

Expand All @@ -118,38 +118,38 @@ def test_nodefaultwithoutregistry(self):

self.assertIs(reg.default, func.__name__)

self.assertTrue(reg.resolvers)
self.assertTrue(reg)

def test_defaultandregistry(self):

reg = ResolverRegistry('test', test=lambda **_: None)

self.assertEqual(reg.default, 'test')
self.assertIn('test', reg.resolvers)
self.assertIn('test', reg)

def test_defaultnotinregistry(self):

self.assertRaises(
NameError, ResolverRegistry, 'test2', test=lambda **_: None
KeyError, ResolverRegistry, 'test2', test=lambda **_: None
)

def test_nonames(self):

reg = ResolverRegistry()

self.assertFalse(reg.names)
self.assertFalse(reg)

def test_names(self):

reg = ResolverRegistry(test=lambda **_: None, example=lambda **_: None)

self.assertEqual(set(reg.names), set(['test', 'example']))
self.assertEqual(set(reg.keys()), set(['test', 'example']))

def test_setnotdefault(self):

reg = ResolverRegistry()

self.assertRaises(NameError, setattr, reg, 'default', 'test')
self.assertRaises(KeyError, setattr, reg, 'default', 'test')

def test_setdefault(self):

Expand All @@ -176,5 +176,20 @@ def test_name(self):

self.assertEqual(names()[0], _defaultname)

def test_defaultpamplemouse(self):

pamplemouse = 'pamplemouse'

self.assertRaises(KeyError, defaultname, name=pamplemouse)

register(name=pamplemouse, exprresolver=lambda x: pamplemouse)

_defaultname = defaultname(pamplemouse)

self.assertEqual(_defaultname, pamplemouse)

unregister(name=pamplemouse)


if __name__ == '__main__':
main()

0 comments on commit 6085237

Please sign in to comment.