1
1
import inspect
2
2
import re
3
+ from datetime import date , datetime
3
4
from functools import wraps
4
5
from six import string_types , integer_types
5
6
6
- from .fields import (FIELD_TEXT , FIELD_NUMERIC , FIELD_NO_INPUT ,
7
- FIELD_SELECT , FIELD_SELECT_MULTIPLE )
7
+ from .fields import (
8
+ FIELD_TEXT ,
9
+ FIELD_NUMERIC ,
10
+ FIELD_NO_INPUT ,
11
+ FIELD_SELECT ,
12
+ FIELD_SELECT_MULTIPLE ,
13
+ FIELD_DATETIME ,
14
+ )
8
15
from .utils import fn_name_to_pretty_label , float_to_decimal
9
- from decimal import Decimal , Inexact , Context
16
+ from decimal import Decimal
17
+
10
18
11
19
class BaseType (object ):
12
20
def __init__ (self , value ):
13
21
self .value = self ._assert_valid_value_and_cast (value )
14
22
15
23
def _assert_valid_value_and_cast (self , value ):
16
- raise NotImplemented ()
24
+ raise NotImplementedError ()
17
25
18
26
@classmethod
19
27
def get_all_operators (cls ):
20
28
methods = inspect .getmembers (cls )
21
- return [{'name' : m [0 ],
22
- 'label' : m [1 ].label ,
23
- 'input_type' : m [1 ].input_type }
24
- for m in methods if getattr (m [1 ], 'is_operator' , False )]
29
+ return [
30
+ {"name" : m [0 ], "label" : m [1 ].label , "input_type" : m [1 ].input_type }
31
+ for m in methods
32
+ if getattr (m [1 ], "is_operator" , False )
33
+ ]
25
34
26
35
27
36
def export_type (cls ):
28
- """ Decorator to expose the given class to business_rules.export_rule_data. """
37
+ """Decorator to expose the given class to business_rules.export_rule_data."""
29
38
cls .export_in_rule_data = True
30
39
return cls
31
40
32
41
33
- def type_operator (input_type , label = None ,
34
- assert_type_for_arguments = True ):
35
- """ Decorator to make a function into a type operator.
42
+ def type_operator (input_type , label = None , assert_type_for_arguments = True ):
43
+ """Decorator to make a function into a type operator.
36
44
37
45
- assert_type_for_arguments - if True this patches the operator function
38
46
so that arguments passed to it will have _assert_valid_value_and_cast
39
47
called on them to make type errors explicit.
40
48
"""
49
+
41
50
def wrapper (func ):
42
51
func .is_operator = True
43
- func .label = label \
44
- or fn_name_to_pretty_label (func .__name__ )
52
+ func .label = label or fn_name_to_pretty_label (func .__name__ )
45
53
func .input_type = input_type
46
54
47
55
@wraps (func )
48
56
def inner (self , * args , ** kwargs ):
49
57
if assert_type_for_arguments :
50
58
args = [self ._assert_valid_value_and_cast (arg ) for arg in args ]
51
- kwargs = dict ((k , self ._assert_valid_value_and_cast (v ))
52
- for k , v in kwargs .items ())
59
+ kwargs = dict (
60
+ (k , self ._assert_valid_value_and_cast (v )) for k , v in kwargs .items ()
61
+ )
53
62
return func (self , * args , ** kwargs )
63
+
54
64
return inner
65
+
55
66
return wrapper
56
67
57
68
58
69
@export_type
59
70
class StringType (BaseType ):
60
-
61
71
name = "string"
62
72
63
73
def _assert_valid_value_and_cast (self , value ):
64
74
value = value or ""
65
75
if not isinstance (value , string_types ):
66
- raise AssertionError ("{0} is not a valid string type." .
67
- format (value ))
76
+ raise AssertionError ("{0} is not a valid string type." .format (value ))
68
77
return value
69
78
70
79
@type_operator (FIELD_TEXT )
@@ -98,7 +107,7 @@ def non_empty(self):
98
107
99
108
@export_type
100
109
class NumericType (BaseType ):
101
- EPSILON = Decimal (' 0.000001' )
110
+ EPSILON = Decimal (" 0.000001" )
102
111
103
112
name = "numeric"
104
113
@@ -112,8 +121,7 @@ def _assert_valid_value_and_cast(value):
112
121
if isinstance (value , Decimal ):
113
122
return value
114
123
else :
115
- raise AssertionError ("{0} is not a valid numeric type." .
116
- format (value ))
124
+ raise AssertionError ("{0} is not a valid numeric type." .format (value ))
117
125
118
126
@type_operator (FIELD_NUMERIC )
119
127
def equal_to (self , other_numeric ):
@@ -138,13 +146,11 @@ def less_than_or_equal_to(self, other_numeric):
138
146
139
147
@export_type
140
148
class BooleanType (BaseType ):
141
-
142
149
name = "boolean"
143
150
144
151
def _assert_valid_value_and_cast (self , value ):
145
- if type (value ) != bool :
146
- raise AssertionError ("{0} is not a valid boolean type" .
147
- format (value ))
152
+ if type (value ) is not bool :
153
+ raise AssertionError ("{0} is not a valid boolean type" .format (value ))
148
154
return value
149
155
150
156
@type_operator (FIELD_NO_INPUT )
@@ -155,22 +161,22 @@ def is_true(self):
155
161
def is_false (self ):
156
162
return not self .value
157
163
164
+
158
165
@export_type
159
166
class SelectType (BaseType ):
160
-
161
167
name = "select"
162
168
163
169
def _assert_valid_value_and_cast (self , value ):
164
- if not hasattr (value , '__iter__' ):
165
- raise AssertionError ("{0} is not a valid select type" .
166
- format (value ))
170
+ if not hasattr (value , "__iter__" ):
171
+ raise AssertionError ("{0} is not a valid select type" .format (value ))
167
172
return value
168
173
169
174
@staticmethod
170
175
def _case_insensitive_equal_to (value_from_list , other_value ):
171
- if isinstance (value_from_list , string_types ) and \
172
- isinstance (other_value , string_types ):
173
- return value_from_list .lower () == other_value .lower ()
176
+ if isinstance (value_from_list , string_types ) and isinstance (
177
+ other_value , string_types
178
+ ):
179
+ return value_from_list .lower () == other_value .lower ()
174
180
else :
175
181
return value_from_list == other_value
176
182
@@ -191,13 +197,13 @@ def does_not_contain(self, other_value):
191
197
192
198
@export_type
193
199
class SelectMultipleType (BaseType ):
194
-
195
200
name = "select_multiple"
196
201
197
202
def _assert_valid_value_and_cast (self , value ):
198
- if not hasattr (value , '__iter__' ):
199
- raise AssertionError ("{0} is not a valid select multiple type" .
200
- format (value ))
203
+ if not hasattr (value , "__iter__" ):
204
+ raise AssertionError (
205
+ "{0} is not a valid select multiple type" .format (value )
206
+ )
201
207
return value
202
208
203
209
@type_operator (FIELD_SELECT_MULTIPLE )
@@ -235,3 +241,73 @@ def shares_exactly_one_element_with(self, other_value):
235
241
@type_operator (FIELD_SELECT_MULTIPLE )
236
242
def shares_no_elements_with (self , other_value ):
237
243
return not self .shares_at_least_one_element_with (other_value )
244
+
245
+
246
+ @export_type
247
+ class DateTimeType (BaseType ):
248
+ name = "datetime"
249
+ DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
250
+ DATE_FORMAT = "%Y-%m-%d"
251
+
252
+ def _assert_valid_value_and_cast (self , value ):
253
+ """
254
+ Parse string with formats '%Y-%m-%dT%H:%M:%S' or '%Y-%m-%d' into
255
+ datetime.datetime instance.
256
+
257
+ :param value:
258
+ :return:
259
+ """
260
+ if isinstance (value , datetime ):
261
+ return value
262
+
263
+ if isinstance (value , date ):
264
+ return datetime (value .year , value .month , value .day )
265
+
266
+ try :
267
+ return datetime .strptime (value , self .DATETIME_FORMAT )
268
+ except (ValueError , TypeError ):
269
+ pass
270
+
271
+ try :
272
+ return datetime .strptime (value , self .DATE_FORMAT )
273
+ except (ValueError , TypeError ):
274
+ raise AssertionError ("{0} is not a valid datetime type." .format (value ))
275
+
276
+ def _set_timezone_if_different (self , variable_datetime , condition_value_datetime ):
277
+ # type: (datetime, datetime) -> datetime
278
+ if variable_datetime .tzinfo is None :
279
+ if condition_value_datetime .tzinfo is None :
280
+ return condition_value_datetime
281
+ else :
282
+ return condition_value_datetime .replace (tzinfo = None )
283
+
284
+ return condition_value_datetime .replace (tzinfo = variable_datetime .tzinfo )
285
+
286
+ @type_operator (FIELD_DATETIME )
287
+ def equal_to (self , other_datetime ):
288
+ # type: (datetime) -> bool
289
+ other_datetime = self ._set_timezone_if_different (self .value , other_datetime )
290
+
291
+ return self .value == other_datetime
292
+
293
+ @type_operator (FIELD_DATETIME )
294
+ def after_than (self , other_datetime ):
295
+ # type: (datetime) -> bool
296
+ other_datetime = self ._set_timezone_if_different (self .value , other_datetime )
297
+
298
+ return self .value > other_datetime
299
+
300
+ @type_operator (FIELD_DATETIME )
301
+ def after_than_or_equal_to (self , other_datetime ):
302
+ return self .after_than (other_datetime ) or self .equal_to (other_datetime )
303
+
304
+ @type_operator (FIELD_DATETIME )
305
+ def before_than (self , other_datetime ):
306
+ # type: (datetime) -> bool
307
+ other_datetime = self ._set_timezone_if_different (self .value , other_datetime )
308
+
309
+ return self .value < other_datetime
310
+
311
+ @type_operator (FIELD_DATETIME )
312
+ def before_than_or_equal_to (self , other_datetime ):
313
+ return self .before_than (other_datetime ) or self .equal_to (other_datetime )
0 commit comments