Completed
Push — master ( 098544...d0b1eb )
by Rich
28:55 queued 13:50
created

Cache.extract_kwargs()   A

Complexity

Conditions 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 15
ccs 3
cts 3
cp 1
rs 9.2
cc 4
crap 4
1
#! /usr/bin/env python
2
#
3
# Copyright (C) 2016 Rich Lewis <[email protected]>
4
# License: 3-clause BSD
5
6 1
"""
7
# skchem.features.descriptors.decorators
8
9
Decorators for descriptors in scikit-chem.
10
"""
11 1
import inspect
12 1
from functools import wraps
13 1
from collections import OrderedDict, defaultdict
14
15 1
import pandas as pd
0 ignored issues
show
Configuration introduced by
The import pandas could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
16 1
17
18
def requires_h_depleted(func):
19 1
20
    """ Decorate a function that requires an h-depleted graph.
21
22
    This will check if the molecule argument is h-depleted, and will
23
    memoize a depleted version if it is not, and pass it to the func.
24
25
26
    """
27
28
    @wraps(func)
29
    def inner(mol, *args, **kwargs):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
30
31
        # if is already h depleted
32
        if (mol.atoms.atomic_number == 1).sum() == 0:
33
            return func(mol, *args, **kwargs)
34
35
        if not hasattr(mol, '_h_depleted'):
36
            mol._h_depleted = mol.remove_hs()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _h_depleted was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
37
38
        return func(mol._h_depleted, *args, **kwargs)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _h_depleted was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
39
40
    return inner
41
42
43
def requires_h_filled(func):
44 1
45
    """ Decorate a function that requires a h-filled graph.
46
47
    This will check if the molecule argument is h-filled, and will
48
    memoize a filled version if it is not, and pass it to the func.
49
50
     Note:
51
        This decorator should be used first if in combination with dMat etc.
52
53
     """
54
55
    @wraps(func)
56
    def inner(mol, *args, **kwargs):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
57
        # if is already h enriched
58
        if mol.atoms.n_total_hs.sum() == 0:
59
            return func(mol, *args, **kwargs)
60
61
        # if not, memoize the enriched one and pass it
62
        if not hasattr(mol, '_h_enriched'):
63
            mol._h_enriched = mol.add_hs()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _h_enriched was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
64
65
        return func(mol._h_enriched, *args, **kwargs)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _h_enriched was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
66
67
    return inner
68
69
70
class Cache(object):
71 1
72
    """ Function cache."""
73
74
    def __init__(self):
75 1
        self.cached = {}
76 1
77
    def extract_kwargs(self, func):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
78 1
79
        # py2 compat
80
        if pd.compat.PY2:
81
            spec = inspect.getargspec(func)
82
            kwds = spec.args[len(spec.defaults) - len(spec.args):]
83
            kwds = OrderedDict(zip(kwds, spec.defaults))
84
85
        else:
86
87 1
            kwds = OrderedDict((k, v.default) for k, v in
88
                               inspect.signature(func).parameters.items()
0 ignored issues
show
Bug introduced by
The Module inspect does not seem to have a member named signature.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
89
                               if v.default != inspect._empty)
0 ignored issues
show
Bug introduced by
The Module inspect does not seem to have a member named _empty.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
Coding Style Best Practice introduced by
It seems like _empty was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
90 1
91
        return kwds
92
93
    def __call__(self, func):
94 1
95 1
        """ Create a decorator to identify a function as returning a cached
96
        value.
97 1
98
        This can be used for objects  that are nontrivial to generate, but
99
        are used by many functions.
100 1
        """
101 1
102
        name = func.__name__
103
104
        # get the key word arguments and the default values of the function
105 1
106
107
        @wraps(func)
108 1
        def inner(mol, *args, **kwargs):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
109
110
            # py2 compat
111 1
            force = kwargs.pop('force', False)
112
113
            self.setup_cache(mol)
114 1
115
            # get the full set of keywords to use, including defaults
116 1
            kwds.update(kwargs)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'kwds'
Loading history...
117
            kw_to_save = tuple(sorted(kwds.items()))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'kwds'
Loading history...
118 1
119
            # call function if it hasn't already been called
120 1
            # with required arguments, or if told to.
121
            if force or name not in self.cached.keys() or \
122
                    kw_to_save not in mol.cache.get(name, {}).keys():
123
124
                res = func(mol, *args, **kwargs)
125
126
                # cache the value with the args used.
127
                mol.cache[name].update({kw_to_save: res})
128
129 1
            # return the cached value
130
            return mol.cache[name][kw_to_save]
131
132 1
        self.cached[name] = inner, tuple(kwds.keys())
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'kwds'
Loading history...
133
134
        return inner
135
136 1
    def inject(self, *args_to_inject):
137
138
        """ Create a decorator that will inject cached values as arguments.
139
140 1
        Args:
141
            args (list<str>):
142 1
                A list of cachable requirements for this function.
143
        """
144
145
        def outer(func):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
146
147 1
            # extract the defaults for the func
148
            kwds = OrderedDict((k, v.default) for k, v in
149 1
                               inspect.signature(func).parameters.items()
0 ignored issues
show
Bug introduced by
The Module inspect does not seem to have a member named signature.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
150
                               if v.default != inspect._empty)
0 ignored issues
show
Bug introduced by
The Module inspect does not seem to have a member named _empty.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
Coding Style Best Practice introduced by
It seems like _empty was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
151 1
152
            @wraps(func)
153
            def inner(mol, *args, **kwargs):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
154 1
155
                # augment with the keywords from the function
156
                kwds.update(kwargs)
157
158 1
                self.setup_cache(mol)
159
160
                # look up cached values, or produce them if not.
161 1
                # inject the cached values
162
163
                args_supp = ()
164 1
165 1
                for arg in args_to_inject:
166
                    # lookup function to inject
167
                    inj_func, params = self.cached[arg.__name__]
168 1
169
                    # get the kwargs required
170
                    inj_kwargs = {param: kwds[param] for param in params
171 1
                                  if param in kwds.keys()}
172
173 1
                    # get a hashable representation of the kwargs
174
                    immut = tuple(sorted(inj_kwargs.items()))
175 1
176
                    # retrieve the cached result (or None if not yet cached)
177 1
                    res = mol.cache.get(arg.__name__, {}).get(immut, None)
178
179 1
                    # calculate and cache result
180
                    if res is None:
181
                        res = inj_func(mol, **inj_kwargs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
182
183
                    # add to injected args
184 1
                    args_supp += (res,)
185 1
186
                # put injected args at start of arg list
187 1
                args = args_supp + args
188
189
                return func(mol, *args, **kwargs)
190
191
            return inner
192
193
        return outer
194
195 1
    @staticmethod
196
    def setup_cache(mol):
197
198
        """ Set up a cache on e.g. a `Mol`. """
199
200
        if not hasattr(mol, 'cache'):
201
            mol.cache = defaultdict(dict)
202
203
    @staticmethod
204
    def teardown_cache(mol):
205
206
        """ Tear down a cache on e.g. a `Mol`. """
207
208
        if hasattr(mol, 'cache'):
209
            del mol.cache
210
211
cache = Cache()
0 ignored issues
show
Coding Style Naming introduced by
The name cache does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
212