Completed
Push — master ( 6fec67...b302c1 )
by P.R.
01:52
created

Type2Helper._date2int()   B

Complexity

Conditions 6

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 9.3798

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
dl 0
loc 23
ccs 6
cts 11
cp 0.5455
crap 9.3798
rs 7.6949
1
"""
2
ETLT
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8 1
import copy
9 1
import datetime
10
11
12 1
class Type2Helper:
13
    """
14
    A helper class for reference data with date intervals.
15
    """
16
17
    # ------------------------------------------------------------------------------------------------------------------
18 1
    def __init__(self, key_start_date, key_end_date, natural_key):
19
        """
20
        Object constructor.
21
22
        :param str key_start_date: The key of the start date in the rows.
23
        :param str key_end_date: The key of the end date in the rows.
24
        :param list[str] natural_key: The keys of the columns that form the natural key.
25
        """
26 1
        self._natural_key = list(natural_key)
27
        """
28
        The keys of the columns that form the natural key.
29
30
        :type: list[str]
31
        """
32
33 1
        self._key_end_date = key_end_date
34
        """
35
        The key of the end date in the rows.
36
37
        :type: str
38
        """
39 1
        self._key_start_date = key_start_date
40
        """
41
        The key of the start date in the rows.
42
43
        :type: str
44
        """
45
46 1
        self.rows = dict()
47
        """
48
        The data set.
49
50
        :type: dict
51
        """
52
53 1
        self._copy = True
54
        """
55
        If set to true a copy will be made from the original rows such that the original rows is not modified.
56
57
         :type: bool
58
        """
59
60 1
        self._date_type = ''
61 1
        """
62
        The type of the date fields.
63
        - date for datetime.date objects
64
        - str  for strings in ISO 8601 (YYYY-MM-DD) format
65
        - int for integers
66
67
        :type: str
68
        """
69
70
    # ------------------------------------------------------------------------------------------------------------------
71 1
    def _get_natural_key(self, row):
72
        """
73
        Returns the natural key in a row.
74
75
        :param dict row: The row.
76
77
        :rtype: tuple
78
        """
79 1
        ret = list()
80 1
        for key in self._natural_key:
81 1
            ret.append(row[key])
82
83 1
        return tuple(ret)
84
85
    # ------------------------------------------------------------------------------------------------------------------
86 1
    @staticmethod
87
    def _date2int(date):
88
        """
89
        Returns an integer representation of a date.
90
91
        :param str|datetime.date date: The date.
92
93
        :rtype: int
94
        """
95 1
        if isinstance(date, str):
96 1
            if date.endswith(' 00:00:00') or date.endswith('T00:00:00'):
97
                # Ignore time suffix.
98 1
                date = date[0:-9]
99 1
            tmp = datetime.datetime.strptime(date, '%Y-%m-%d')
100 1
            return tmp.toordinal()
101
102
        if isinstance(date, datetime.date):
103
            return date.toordinal()
104
105
        if isinstance(date, int):
106
            return date
107
108
        raise ValueError('Unexpected type {0!s}'.format(date.__class__))
109
110
    # ------------------------------------------------------------------------------------------------------------------
111 1
    def _rows_date2int(self, rows):
112
        """
113
        Replaces start and end dates in a row set with their integer representation
114
115
        :param list[dict[str,T]] rows: The list of rows.
116
117
        :rtype: list[dict[str,T]]
118
        """
119 1
        ret = list()
120 1
        for row in rows:
121
            # Determine the type of dates based on the first start date.
122 1
            if not self._date_type:
123 1
                self._date_type = self._get_date_type(row[self._key_start_date])
124
125
            # Convert dates to integers.
126 1
            row[self._key_start_date] = self._date2int(row[self._key_start_date])
127 1
            row[self._key_end_date] = self._date2int(row[self._key_end_date])
128 1
            ret.append(row)
129
130 1
        return ret
131
132
    # ------------------------------------------------------------------------------------------------------------------
133 1
    def _rows_int2date(self, rows):
134
        """
135
        Replaces start and end dates in the row set with their integer representation
136
137
        :param list[dict[str,T]] rows: The list of rows.
138
        """
139 1
        for row in rows:
140 1
            if self._date_type == 'str':
141 1
                row[self._key_start_date] = datetime.date.fromordinal(row[self._key_start_date]).isoformat()
142 1
                row[self._key_end_date] = datetime.date.fromordinal(row[self._key_end_date]).isoformat()
143
            elif self._date_type == 'date':
144
                row[self._key_start_date] = datetime.date.fromordinal(row[self._key_start_date])
145
                row[self._key_end_date] = datetime.date.fromordinal(row[self._key_end_date])
146
            elif self._date_type == 'int':
147
                # Nothing to do.
148
                pass
149
            else:
150
                raise ValueError('Unexpected date type {0!s}'.format(self._date_type))
151
152
    # ------------------------------------------------------------------------------------------------------------------
153 1
    def _rows_sort(self, rows):
154
        """
155
        Returns a list of rows sorted by start and end date.
156
157
        :param list[dict[str,T]] rows: The list of rows.
158
159
        :rtype: list[dict[str,T]]
160
        """
161 1
        return sorted(rows, key=lambda row: (row[self._key_start_date], row[self._key_end_date]))
162
163
    # ------------------------------------------------------------------------------------------------------------------
164 1
    @staticmethod
165
    def _get_date_type(date):
166
        """
167
        Returns the type of a date.
168
169
        :param str|datetime.date date: The date.
170
171
        :rtype: str
172
        """
173 1
        if isinstance(date, str):
174 1
            return 'str'
175
176
        if isinstance(date, datetime.date):
177
            return 'date'
178
179
        if isinstance(date, int):
180
            return 'int'
181
182
        raise ValueError('Unexpected type {0!s}'.format(date.__class__))
183
184
    # ------------------------------------------------------------------------------------------------------------------
185 1
    def enumerate(self, name, start=1):
186
        """
187
        Enumerates all rows such that the natural key and the ordinal number are a unique key.
188
189
        :param str name: The key holding the ordinal number.
190
        :param int start: The start of the ordinal numbers. Foreach natural key the first row has this ordinal number.
191
        """
192 1
        for natural_key, rows in self.rows.items():
193 1
            rows = self._rows_sort(rows)
194 1
            ordinal = start
195 1
            for row in rows:
196 1
                row[name] = ordinal
197 1
                ordinal += 1
198 1
            self.rows[natural_key] = rows
199
200
    # ------------------------------------------------------------------------------------------------------------------
201 1
    def get_rows(self, sort=False):
202
        """
203
        Returns the rows of this Type2Helper.
204
205
        :param bool sort: If True the rows are sorted by the natural key.
206
        """
207 1
        ret = []
208 1
        for _, rows in sorted(self.rows.items()) if sort else self.rows.items():
209 1
            self._rows_int2date(rows)
210 1
            ret.extend(rows)
211
212 1
        return ret
213
214
    # ------------------------------------------------------------------------------------------------------------------
215 1
    def prepare_data(self, rows):
216
        """
217
        Sets and prepares the rows. The rows are stored in groups in a dictionary. A group is a list of rows with the
218
        same natural key. The key in the dictionary is a tuple with the values of the natural key.
219
220
        :param list[dict] rows: The rows
221
        """
222 1
        self.rows = dict()
223 1
        for row in copy.copy(rows) if self._copy else rows:
224 1
            natural_key = self._get_natural_key(row)
225 1
            if natural_key not in self.rows:
226 1
                self.rows[natural_key] = list()
227 1
            self.rows[natural_key].append(row)
228
229
        # Convert begin and end dates to integers.
230 1
        self._date_type = None
231 1
        for natural_key, rows in self.rows.items():
232 1
            self.rows[natural_key] = self._rows_date2int(rows)
233
234
# ----------------------------------------------------------------------------------------------------------------------
235