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

GlobFormatter.format_field()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
crap 2
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_field(self, field_name, args, kwargs):
52
        try:
53 1
            return super().get_field(field_name, args, kwargs)
54
        except (KeyError, IndexError, AttributeError):
55 1
            return self.WILDCARD, None
56
57 1
    def get_value(self, key, args, kwargs):
58
        try:
59
            return super().get_value(key, args, kwargs)
60 1
        except (KeyError, IndexError, AttributeError):
61
            return self.WILDCARD
62
63
    def convert_field(self, value, conversion):
64
        if value is self.WILDCARD:
65
            return self.WILDCARD
66 1
        else:
67
            return super().convert_field(value, conversion)
68 1
69 1
    def format_field(self, value, format_spec):
70 1
        if value is self.WILDCARD:
71
            return '*'
72 1
        else:
73 1
            return super().format_field(value, format_spec)
74
75 1
76
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...
77 1
    return {
78
        (k[len('self.'):] if k.startswith('self.') else k): v
79
        for k, v in pathfields.items()
80 1
    }
81
82 1
83
def match(cls_or_path, factory=None, **kwargs):
84 1
    """match(class, [callable], ...) -> instance, ...
85
    match(str, callable, ...) -> instance, ...
86 1
87
    Yield all matching mapped objects. Can be used two ways:
88
    * With a YORM-decorated class, optionally with a factory callable
89 1
    * With a Python 3-style string template and a factory callable
90 1
91 1
    The factory callable must accept keyuword arguments, extracted from the file
92
    name merged with those passed to match(). If no factory is given, the class
93 1
    itself is used as the factory (same signature).
94 1
    """
95
    if isinstance(cls_or_path, type):
96 1
        path_format = common.path_formats[cls_or_path]
97
        # Let KeyError fail through
98
        if factory is None:
99
            factory = cls_or_path
100
    else:
101
        path_format = cls_or_path
102
        if factory is None:
103
            raise TypeError("Factory must be given if a string template is used")
104
105
    log.debug((path_format, factory, kwargs))
106
    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...
107
    mock = types.SimpleNamespace(**kwargs)
108
109
    kwargs['self'] = mock
110
    posix_pattern = gf.vformat(path_format, (), kwargs.copy())
111
    del kwargs['self']
112
    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...
113
114
    for filename in glob.iglob(posix_pattern):
115
        pathfields = py_pattern.parse(filename).named
116
        fields = _unpack_parsed_fields(pathfields)
117
        fields.update(kwargs)
118
        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...
119
120
121
def load(instance):
122
    """Force the loading of a mapped object's file.
123
124
    NOTE: Calling this function is unnecessary. It exists for the
125
        aesthetic purpose of having symmetry between save and load.
126
127
    """
128
    mapper = common.get_mapper(instance, expected=True)
129
130
    mapper.load()
131
132
    return instance
133
134
135
def save(instance):
136
    """Save a mapped object to file.
137
138
    NOTE: Calling this function is unnecessary with 'auto_save' enabled.
139
140
    """
141
    mapper = common.get_mapper(instance, expected=True)
142
143
    if mapper.deleted:
144
        msg = "{!r} was deleted".format(mapper.path)
145
        raise exceptions.DeletedFileError(msg)
146
147
    if not mapper.exists:
148
        mapper.create()
149
150
    mapper.save()
151
152
    return instance
153
154
155
def delete(instance):
156
    """Delete a mapped object's file."""
157
    mapper = common.get_mapper(instance, expected=True)
158
159
    mapper.delete()
160
161
    return None
162
163
164
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...
165
    if inspect.isclass(class_or_instance):
166
        instance = class_or_instance(*args, **kwargs)
167
    else:
168
        assert not args
169
        instance = class_or_instance
170
171
    return instance
172