Completed
Pull Request — develop (#143)
by
unknown
17:12 queued 07:14
created

_unpack_parsed_fields()   A

Complexity

Conditions 3

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 3
crap 3
1
"""Functions to interact with mapped classes and instances."""
2
3 1
import inspect
4 1
import logging
5
import string
6 1
import glob
7
import types
8 1
import parse
9
10
from . import common, exceptions
11 1
12
log = logging.getLogger(__name__)
0 ignored issues
show
Coding Style Naming introduced by
The name log 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...
13
14
15
def create(class_or_instance, *args, overwrite=False, **kwargs):
16
    """Create a new mapped object.
17 1
18 1
    NOTE: Calling this function is unnecessary with 'auto_create' enabled.
19
20 1
    """
21 1
    instance = _instantiate(class_or_instance, *args, **kwargs)
22 1
    mapper = common.get_mapper(instance, expected=True)
23
24 1
    if mapper.exists and not overwrite:
25
        msg = "{!r} already exists".format(mapper.path)
26
        raise exceptions.DuplicateMappingError(msg)
27 1
28
    return load(save(instance))
29 1
30 1
31
def find(class_or_instance, *args, create=False, **kwargs):  # pylint: disable=redefined-outer-name
0 ignored issues
show
introduced by
Locally disabling redefined-outer-name (W0621)
Loading history...
32 1
    """Find a matching mapped object or return None."""
33 1
    instance = _instantiate(class_or_instance, *args, **kwargs)
34 1
    mapper = common.get_mapper(instance, expected=True)
35 1
36
    if mapper.exists:
37 1
        return instance
38
    elif create:
39
        return save(instance)
40 1
    else:
41
        return None
42 1
43 1
44
class GlobFormatter(string.Formatter):
45
    """
46 1
    Uses '*' for all unknown fields
47
    """
48
49
    WILDCARD = object()
50
51
    def get_value(self, key, args, kwargs):
52
        try:
53 1
            return super().get_value(key, args, kwargs)
54
        except (KeyError, IndexError):
55 1
            return self.WILDCARD
56
57 1
    def convert_field(self, value, conversion):
58
        if value is self.WILDCARD:
59
            return self.WILDCARD
60 1
        else:
61
            return super().convert_field(value, conversion)
62
63
    def format_field(self, value, format_spec):
64
        if value is self.WILDCARD:
65
            return '*'
66 1
        else:
67
            return super().format_field(value, format_spec)
68 1
69 1
70 1
def _unpack_parsed_fields(pathfields):
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...
71
    return {
72 1
        (k[len('self.'):] if k.startswith('self.') else k): v
73 1
        for k, v in pathfields.items()
74
    }
75 1
76
77 1
def match(cls_or_path, factory=None, **kwargs):
78
    """match(class, [callable], ...) -> instance, ...
79
    match(str, callable, ...) -> instance, ...
80 1
81
    Yield all matching mapped objects. Can be used two ways:
82 1
    * With a YORM-decorated class, optionally with a factory callable
83
    * With a Python 3-style string template and a factory callable
84 1
85
    The factory callable must accept keyuword arguments, extracted from the file
86 1
    name merged with those passed to match(). If no factory is given, the class
87
    itself is used as the factory (same signature).
88
    """
89 1
    if isinstance(cls_or_path, type):
90 1
        path_format = common.path_formats[cls_or_path]
91 1
        # Let KeyError fail through
92
        if factory is None:
93 1
            factory = cls_or_path
94 1
    else:
95
        path_format = cls_or_path
96 1
        if factory is None:
97
            raise TypeError("Factory must be given if a string template is used")
98
99
    log.debug((path_format, factory, kwargs))
100
    gf = GlobFormatter()
0 ignored issues
show
Coding Style Naming introduced by
The name gf does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

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...
101
    mock = types.SimpleNamespace(**kwargs)
102
103
    kwargs['self'] = mock
104
    posix_pattern = gf.vformat(path_format, (), kwargs.copy())
105
    del kwargs['self']
106
    py_pattern = parse.compile(path_format)
0 ignored issues
show
Bug introduced by
The Module parse does not seem to have a member named compile.

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...
107
108
    for filename in glob.iglob(posix_pattern):
109
        pathfields = py_pattern.parse(filename).named
110
        fields = _unpack_parsed_fields(pathfields)
111
        fields.update(kwargs)
112
        yield factory(**fields)
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...
113
114
115
def load(instance):
116
    """Force the loading of a mapped object's file.
117
118
    NOTE: Calling this function is unnecessary. It exists for the
119
        aesthetic purpose of having symmetry between save and load.
120
121
    """
122
    mapper = common.get_mapper(instance, expected=True)
123
124
    mapper.load()
125
126
    return instance
127
128
129
def save(instance):
130
    """Save a mapped object to file.
131
132
    NOTE: Calling this function is unnecessary with 'auto_save' enabled.
133
134
    """
135
    mapper = common.get_mapper(instance, expected=True)
136
137
    if mapper.deleted:
138
        msg = "{!r} was deleted".format(mapper.path)
139
        raise exceptions.DeletedFileError(msg)
140
141
    if not mapper.exists:
142
        mapper.create()
143
144
    mapper.save()
145
146
    return instance
147
148
149
def delete(instance):
150
    """Delete a mapped object's file."""
151
    mapper = common.get_mapper(instance, expected=True)
152
153
    mapper.delete()
154
155
    return None
156
157
158
def _instantiate(class_or_instance, *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...
159
    if inspect.isclass(class_or_instance):
160
        instance = class_or_instance(*args, **kwargs)
161
    else:
162
        assert not args
163
        instance = class_or_instance
164
165
    return instance
166