Completed
Pull Request — develop (#143)
by
unknown
19:16 queued 09:25
created

GlobFormatter.convert_field()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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