Completed
Pull Request — develop (#143)
by
unknown
09:43
created

GlobFormatter   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 24
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 6
c 0
b 0
f 0
dl 0
loc 24
ccs 6
cts 6
cp 1
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A get_value() 0 5 2
A format_field() 0 5 2
A convert_field() 0 5 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
    posix_pattern = gf.format(path_format, self=mock, **kwargs)
0 ignored issues
show
Unused Code introduced by
Argument 'self' passed by position and keyword in method call
Loading history...
104
    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...
105
106
    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...
107
        pathfields = py_pattern.parse(filename)
108
        fields = _unpack_parsed_fields(pathfields)
109
        fields.update(kwargs)
110
        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...
111
112
113
def load(instance):
114
    """Force the loading of a mapped object's file.
115
116
    NOTE: Calling this function is unnecessary. It exists for the
117
        aesthetic purpose of having symmetry between save and load.
118
119
    """
120
    mapper = common.get_mapper(instance, expected=True)
121
122
    mapper.load()
123
124
    return instance
125
126
127
def save(instance):
128
    """Save a mapped object to file.
129
130
    NOTE: Calling this function is unnecessary with 'auto_save' enabled.
131
132
    """
133
    mapper = common.get_mapper(instance, expected=True)
134
135
    if mapper.deleted:
136
        msg = "{!r} was deleted".format(mapper.path)
137
        raise exceptions.DeletedFileError(msg)
138
139
    if not mapper.exists:
140
        mapper.create()
141
142
    mapper.save()
143
144
    return instance
145
146
147
def delete(instance):
148
    """Delete a mapped object's file."""
149
    mapper = common.get_mapper(instance, expected=True)
150
151
    mapper.delete()
152
153
    return None
154
155
156
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...
157
    if inspect.isclass(class_or_instance):
158
        instance = class_or_instance(*args, **kwargs)
159
    else:
160
        assert not args
161
        instance = class_or_instance
162
163
    return instance
164