Completed
Branch master (360284)
by Rich
01:48
created

Cache.inject()   C

Complexity

Conditions 7

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 55
ccs 19
cts 19
cp 1
rs 6.822
cc 7
crap 7

1 Method

Rating   Name   Duplication   Size   Complexity  
B Cache.outer() 0 44 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
17
18 1
def requires_h_depleted(func):
19
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 1
def requires_h_filled(func):
44
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 1
class Cache(object):
71
72
    """ Function cache."""
73
74 1
    def __init__(self):
75 1
        self.cached = {}
76
77 1
    @staticmethod
78
    def extract_kwargs(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...
79
80
        # py2 compat
81 1
        if pd.compat.PY2:
82
            spec = inspect.getargspec(func)
83
            if spec.defaults:
84
                kwds = spec.args[-len(spec.defaults):]
85
                kwds = OrderedDict(zip(kwds, spec.defaults))
86
            else:
87
                kwds = OrderedDict()
88
89
        else:
90
91 1
            kwds = OrderedDict((k, v.default) for k, v in
92
                               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...
93
                               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...
94
95 1
        return kwds
96
97 1
    def __call__(self, func):
98
99
        """ Create a decorator to identify a function as returning a cached
100
        value.
101
102
        This can be used for objects  that are nontrivial to generate, but
103
        are used by many functions.
104
        """
105
106 1
        name = func.__name__
107
108
        # get the key word arguments and the default values of the function
109
110 1
        kwds = self.extract_kwargs(func)
111
112 1
        @wraps(func)
113
        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...
114
115
            # py2 compat
116 1
            force = kwargs.pop('force', False)
117
118 1
            self.setup_cache(mol)
119
120
            # get the full set of keywords to use, including defaults
121 1
            kwds.update(kwargs)
122 1
            kw_to_save = tuple(sorted(kwds.items()))
123
124
            # call function if it hasn't already been called
125
            # with required arguments, or if told to.
126 1
            if force or name not in self.cached.keys() or \
127
                    kw_to_save not in mol.cache.get(name, {}).keys():
128
129 1
                res = func(mol, *args, **kwargs)
130
131
                # cache the value with the args used.
132 1
                mol.cache[name].update({kw_to_save: res})
133
134
            # return the cached value
135 1
            return mol.cache[name][kw_to_save]
136
137 1
        self.cached[name] = inner, tuple(kwds.keys())
138
139 1
        return inner
140
141 1
    def inject(self, *args_to_inject):
142
143
        """ Create a decorator that will inject cached values as arguments.
144
145
        Args:
146
            args (list<str>):
147
                A list of cachable requirements for this function.
148
        """
149
150 1
        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...
151
152
            # extract the defaults for the func
153 1
            kwds = self.extract_kwargs(func)
154
155 1
            @wraps(func)
156
            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...
157
158
                # augment with the keywords from the function
159 1
                kwds.update(kwargs)
160 1
                self.setup_cache(mol)
161
162
                # look up cached values, or produce them if not.
163
                # inject the cached values
164
165 1
                args_supp = ()
166
167 1
                for arg in args_to_inject:
168
                    # lookup function to inject
169 1
                    inj_func, params = self.cached[arg.__name__]
170
171
                    # get the kwargs required
172 1
                    inj_kwargs = {param: kwds[param] for param in params
173
                                  if param in kwds.keys()}
174
175
                    # get a hashable representation of the kwargs
176 1
                    immut = tuple(sorted(inj_kwargs.items()))
177
178
                    # retrieve the cached result (or None if not yet cached)
179 1
                    res = mol.cache.get(arg.__name__, {}).get(immut, None)
180
181
                    # calculate and cache result
182 1
                    if res is None:
183 1
                        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...
184
185
                    # add to injected args
186 1
                    args_supp += (res,)
187
188
                # put injected args at start of arg list
189 1
                args = args_supp + args
190
191 1
                return func(mol, *args, **kwargs)
192
193 1
            return inner
194
195 1
        return outer
196
197 1
    @staticmethod
198
    def setup_cache(mol):
199
200
        """ Set up a cache on e.g. a `Mol`. """
201
202 1
        if not hasattr(mol, 'cache'):
203 1
            mol.cache = defaultdict(dict)
204
205 1
    @staticmethod
206
    def teardown_cache(mol):
207
208
        """ Tear down a cache on e.g. a `Mol`. """
209
210
        if hasattr(mol, 'cache'):
211
            del mol.cache
212
213
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...
214