-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
roll
committed
Jan 27, 2015
1 parent
b3dc070
commit c815eac
Showing
28 changed files
with
795 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,6 @@ mario | |
sphinx | ||
sphinx-settings | ||
sphinx-rtd-theme | ||
sugarbowl | ||
clyde | ||
nose | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
from .cache import cachedproperty | ||
from .function import Function | ||
from .impobj import import_object | ||
from .load import load | ||
from .merge import merge_dicts | ||
from .pack import pack | ||
from .parse import parse | ||
from .settings import Settings | ||
from .stylize import stylize |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
class cachedproperty: | ||
""" | ||
Decorator to implement cached property. | ||
To cache property value just use cachedproperty decorator | ||
in class definition instead of regular property decorator:: | ||
class Container: | ||
@cachedproperty | ||
def attribute(self): | ||
return 'value' | ||
@attribute.setter | ||
def attribute(self, cache, name, value): | ||
cache[name] = value | ||
@attribute.deleter | ||
def attribute(self, cache, name): | ||
del cache[name] | ||
It works for private attributes (__*) and not tested for threads. | ||
""" | ||
|
||
# Public | ||
|
||
def __init__(self, getter=None, setter=None, deleter=None): | ||
self.getter(getter) | ||
self.setter(setter) | ||
self.deleter(deleter) | ||
|
||
def __get__(self, obj, cls): | ||
if obj is None: | ||
# To get property from class object | ||
return self | ||
if self.__getter is None: | ||
raise AttributeError('Can\'t get attribute') | ||
name = self.__get_name(obj) | ||
cache = self.__get_cache(obj) | ||
if name not in cache: | ||
cache[self.__name] = self.__getter(obj) | ||
return cache[self.__name] | ||
|
||
def __set__(self, obj, value): | ||
if self.__setter is None: | ||
raise AttributeError('Can\'t set attribute') | ||
name = self.__get_name(obj) | ||
cache = self.__get_cache(obj) | ||
self.__setter(obj, cache, name, value) | ||
|
||
def __delete__(self, obj): | ||
if self.__deleter is None: | ||
raise AttributeError('Can\'t delete attribute') | ||
name = self.__get_name(obj) | ||
cache = self.__get_cache(obj) | ||
self.__deleter(obj, cache, name) | ||
|
||
def getter(self, getter): | ||
self.__getter = getter | ||
self.__doc__ = None | ||
if getter is not None: | ||
self.__doc__ = getter.__doc__ | ||
return self | ||
|
||
def setter(self, setter): | ||
self.__setter = setter | ||
return self | ||
|
||
def deleter(self, deleter): | ||
self.__deleter = deleter | ||
return self | ||
|
||
# Private | ||
|
||
__name = None | ||
__cache = '_cachedproperty' | ||
|
||
def __get_name(self, obj): | ||
if self.__name is None: | ||
for cls in type(obj).mro(): | ||
for name, value in vars(cls).items(): | ||
if self is value: | ||
self.__name = name | ||
return self.__name | ||
|
||
def __get_cache(self, obj): | ||
return obj.__dict__.setdefault(self.__cache, {}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from abc import ABCMeta, abstractmethod | ||
|
||
|
||
class Metaclass(ABCMeta): | ||
|
||
# Public | ||
|
||
def __call__(self, *args, **kwargs): | ||
function = object.__new__(self) | ||
protocol = function.protocol | ||
if callable(function.protocol): | ||
protocol = function.protocol(*args, **kwargs) | ||
if protocol == self.CLASS: | ||
function.__init__(*args, **kwargs) | ||
return function.__call__() | ||
elif protocol == self.FUNCTION: | ||
function.__init__() | ||
return function.__call__(*args, **kwargs) | ||
elif protocol == self.DECORATOR: | ||
function.__init__(*args, **kwargs) | ||
return function | ||
else: | ||
raise ValueError( | ||
'Unsupported protocol "{protocol}"'. | ||
format(protocol=protocol)) | ||
|
||
def __instancecheck__(self, instance): | ||
result = issubclass(instance, self) | ||
if not result: | ||
result = super().__instancecheck__(instance) | ||
return result | ||
|
||
|
||
class Function(metaclass=Metaclass): | ||
"""Abstract class-based function representation. | ||
Designed to implement functions/decorators as classes | ||
to work with state and inheritance. In this model function will | ||
have state only for invocation. Main use case is function like | ||
parse where there is not global state to create Parser class. | ||
Also supports decorator protocol to create decorators with parameters. | ||
User have to implement this abstract class. Examples below will | ||
make it much more clearer. | ||
Raises | ||
------ | ||
ValueError | ||
If protocol is unsupported. | ||
Examples | ||
-------- | ||
Invocation protocols: | ||
- Function.CLASS (default) | ||
>>> class echo(Function): | ||
... protocol = Function.CLASS | ||
... def __init__(self, value): | ||
... self.value = value | ||
... def __call__(self): | ||
... return self.value | ||
>>> echo('Hello World') | ||
'Hello World!' | ||
- Function.FUNCTION | ||
>>> class echo(Function): | ||
... protocol = Function.FUNCTION | ||
... def __call__(self, value): | ||
... return value | ||
>>> echo('Hello World!') | ||
'Hello World!' | ||
- Function.DECORATOR | ||
>>> class echo(Function): | ||
... protocol = Function.DECORATOR | ||
... def __init__(self, value): | ||
... self.value = value | ||
... def __call__(self, processor): | ||
... return processor(self.value) | ||
>>> hello('Hello World!')(str.upper) | ||
'HELLO WORLD!' | ||
""" | ||
|
||
# Public | ||
|
||
CLASS = 'class' | ||
FUNCTION = 'function' | ||
DECORATOR = 'decorator' | ||
|
||
protocol = CLASS | ||
"""Invocation protocol. | ||
""" | ||
|
||
@abstractmethod | ||
def __call__(self, *args, **kwargs): | ||
"""Abstract method to implement. | ||
""" | ||
pass # pragma: no cover |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import importlib | ||
|
||
|
||
def import_object(name, *, package=None): | ||
"""Import an object. | ||
Parameters | ||
---------- | ||
name: str/object | ||
Object name in "[module.]module.]attr" form. | ||
package: str | ||
Argument is required when performing a relative import. | ||
It specifies the package to use as the anchor point from which | ||
to resolve the relative import to an absolute import. | ||
Returns | ||
------- | ||
object | ||
Imported object. | ||
Raises | ||
------ | ||
ValueError | ||
If name is not in a proper form. | ||
ImportError | ||
If there is an error in module importing. | ||
AttributeError | ||
If module doesn't have the given attribute. | ||
Examples | ||
-------- | ||
If name not is a string function returns name without changes. | ||
It usefull when client may give you pointer to some objects in | ||
two forms like string or already imported object:: | ||
>>> obj = import_object('importlib.import_module') | ||
>>> obj is import_object(obj) | ||
True | ||
>>> obj | ||
<function importlib.import_module> | ||
""" | ||
if isinstance(name, str): | ||
try: | ||
module, name = name.rsplit('.', 1) | ||
except ValueError: | ||
raise ValueError('Name is in a bad form.') from None | ||
if not module: | ||
module = '.' | ||
imported_module = importlib.import_module(module, package=package) | ||
imported_object = getattr(imported_module, name) | ||
else: | ||
imported_object = name | ||
return imported_object |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from copy import copy | ||
from .function import Function | ||
|
||
|
||
class merge_dicts(Function): | ||
"""Merge dicts with optional conflict resolving and return a new one. | ||
Parameters | ||
---------- | ||
dict1: dict | ||
Left dict to merge. | ||
dict2: dict | ||
Right dict to merge. | ||
resolvers: dict | ||
Conflict resolvers. Key is a type, value is a merge_dicts subclass | ||
or callable with [(value1, value2) -> value] signature. | ||
To make function recursive pass {dict: merge_dicts}. | ||
Resolvers parameter will be passed resursively to | ||
any merge_dicts subclass resolver. | ||
Returns | ||
------- | ||
dict | ||
Merged dict. | ||
Examples | ||
-------- | ||
Here is the code:: | ||
>>> from operators import add | ||
>>> dict1 = {'a': 1, 'b': 2} | ||
>>> dict2 = {'a': 1, 'c': 3} | ||
>>> merge_dicts(dict1, dict2, resolvers={int: add}) | ||
{'a': 2, 'b': 2, 'c', 3} | ||
""" | ||
|
||
protocol = 'function' | ||
|
||
def __call__(self, dict1, dict2, *, resolvers={}): | ||
result = copy(dict1) | ||
for key in dict2: | ||
value = dict2[key] | ||
if key in dict1: | ||
resolver1 = resolvers.get(type(dict1[key]), None) | ||
resolver2 = resolvers.get(type(dict2[key]), None) | ||
if resolver1 is resolver2 is not None: | ||
if (not isinstance(resolver1, type) or | ||
not issubclass(resolver1, merge_dicts)): | ||
value = resolver1(dict1[key], dict2[key]) | ||
else: | ||
value = resolver1( | ||
dict1[key], dict2[key], resolvers=resolvers) | ||
result[key] = value | ||
return result |
Oops, something went wrong.