functions.py 3.77 KB
Newer Older
1
import re
2
import logging
3
4
5
6
from sqlalchemy.orm.attributes import InstrumentedAttribute


class Functions:
Gerion Entrup's avatar
Gerion Entrup committed
7
8
9
10
11
12
13
14
15
    """Holds the translation functions between table attributes and filenames.

    Construct a Functions with Functions() if you want the functions
    autogenerated.

    Use Functions.construct_with_functions if you want take self defined
    functions.
    """

16
    def __init__(self, *args):
Gerion Entrup's avatar
Gerion Entrup committed
17
18
19
20
21
22
23
24
25
26
        """Builds a Functions object.

        This function excepts a variable amount of arguments of type
        str and type InstrumentedAttribute.

        The arguments are meant for defining the way a filename is
        generated, e.g. the arguments (Artist.name, " - ", Recording.name)
        leads to the filename "<Artist.name> - <Recording.name>", where
        <Artist.name> and <Recording.name> are replaced with the real values.
        """
27
        self.logger = logging.getLogger('sqlfuse.functions')
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
        self._fltt_re = re.compile('([^A-Z])([A-Z])')

        self._name = []
        self._name_regex = '^'
        self._regex_groups = []
        for arg in args:
            if type(arg) == str:
                self._name_regex += re.escape(arg)
                self._name.append((True, arg))
            elif type(arg) == InstrumentedAttribute:
                attr_name = self._flatten(arg)
                self._name_regex += '(?P<{}>.*)'.format(attr_name)
                self._regex_groups.append((arg, attr_name))
                self._name.append((False, attr_name))
            else:
                raise Exception("Type must be str or InstrumentedAttribute")
        self._name_regex = re.compile(self._name_regex + '$')
45
46
        self.logger.debug(
            "generated regex: {}".format(self._name_regex.pattern))
47
48
49

    @classmethod
    def construct_with_functions(cls, gen_name, parse_name):
Gerion Entrup's avatar
Gerion Entrup committed
50
51
52
53
54
55
56
57
58
59
        """Build a Functions object with predefined functions.

        Arguments:
        gen_name   -- this function will be called if a filename has to be
                      generated
        parse_name -- this function will be called if a filename has to be
                      parsed

        Look into the class documentation for the arguments of the functions.
        """
60
61
62
63
64
65
        n_cls = cls()
        n_cls.gen_name = gen_name
        n_cls.parse_name = parse_name
        return n_cls

    def _flatten(self, attr):
Gerion Entrup's avatar
Gerion Entrup committed
66
        """Try to emulate the apply_labels function of sqlalchemy."""
67
68
69
        return self._fltt_re.sub(r'\1_\2', str(attr).replace('.', '_')).lower()

    def gen_name(self, row):
Gerion Entrup's avatar
Gerion Entrup committed
70
71
72
73
74
75
76
        """Generates a filename out of an table row.

        Arguments:
        row -- One row of an sqlalchemy query.

        The function is expected to return of filename of type str.
        """
77
78
79
80
81
82
83
84
85
86
87
88
89
90
        name = []
        for (is_str, elem) in self._name:
            if is_str:
                name.append(elem)
            else:
                try:
                    name.append(getattr(row, elem))
                except AttributeError:
                    raise Exception('Cannot access ' + elem +
                                    '. Row: ' + str(row) +
                                    'Maybe your select miss a column.')
        return ''.join(name)

    def parse_name(self, name, query):
Gerion Entrup's avatar
Gerion Entrup committed
91
92
93
94
95
96
97
98
99
        """Parses a filename and apply the result to the query.

        Arguments:
        name  -- the filename, that has to be parsed
        query -- the query, that needs to be modified

        The function is expected to return a modified query if the filename
        matches a table row or return None otherwise.
        """
100
        self.logger.debug("parse name: ", name)
101
102
103
104
105
106
        res = self._name_regex.match(name)
        if res is None:
            return None
        for (attr, group_name) in self._regex_groups:
            query = query.where(attr == res.group(group_name))
        return query