Skip to content

Commit 4247c95

Browse files
committed
business-rules extension updates
1 parent 11a61f2 commit 4247c95

File tree

2 files changed

+119
-42
lines changed

2 files changed

+119
-42
lines changed

business_rules/fields.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
FIELD_TEXT = 'text'
2-
FIELD_NUMERIC = 'numeric'
3-
FIELD_NO_INPUT = 'none'
4-
FIELD_SELECT = 'select'
5-
FIELD_SELECT_MULTIPLE = 'select_multiple'
1+
FIELD_TEXT = "text"
2+
FIELD_NUMERIC = "numeric"
3+
FIELD_NO_INPUT = "none"
4+
FIELD_SELECT = "select"
5+
FIELD_SELECT_MULTIPLE = "select_multiple"
6+
FIELD_DATETIME = "datetime"

business_rules/operators.py

+113-37
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,79 @@
11
import inspect
22
import re
3+
from datetime import date, datetime
34
from functools import wraps
45
from six import string_types, integer_types
56

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+
)
815
from .utils import fn_name_to_pretty_label, float_to_decimal
9-
from decimal import Decimal, Inexact, Context
16+
from decimal import Decimal
17+
1018

1119
class BaseType(object):
1220
def __init__(self, value):
1321
self.value = self._assert_valid_value_and_cast(value)
1422

1523
def _assert_valid_value_and_cast(self, value):
16-
raise NotImplemented()
24+
raise NotImplementedError()
1725

1826
@classmethod
1927
def get_all_operators(cls):
2028
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+
]
2534

2635

2736
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."""
2938
cls.export_in_rule_data = True
3039
return cls
3140

3241

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.
3644
3745
- assert_type_for_arguments - if True this patches the operator function
3846
so that arguments passed to it will have _assert_valid_value_and_cast
3947
called on them to make type errors explicit.
4048
"""
49+
4150
def wrapper(func):
4251
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__)
4553
func.input_type = input_type
4654

4755
@wraps(func)
4856
def inner(self, *args, **kwargs):
4957
if assert_type_for_arguments:
5058
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+
)
5362
return func(self, *args, **kwargs)
63+
5464
return inner
65+
5566
return wrapper
5667

5768

5869
@export_type
5970
class StringType(BaseType):
60-
6171
name = "string"
6272

6373
def _assert_valid_value_and_cast(self, value):
6474
value = value or ""
6575
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))
6877
return value
6978

7079
@type_operator(FIELD_TEXT)
@@ -98,7 +107,7 @@ def non_empty(self):
98107

99108
@export_type
100109
class NumericType(BaseType):
101-
EPSILON = Decimal('0.000001')
110+
EPSILON = Decimal("0.000001")
102111

103112
name = "numeric"
104113

@@ -112,8 +121,7 @@ def _assert_valid_value_and_cast(value):
112121
if isinstance(value, Decimal):
113122
return value
114123
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))
117125

118126
@type_operator(FIELD_NUMERIC)
119127
def equal_to(self, other_numeric):
@@ -138,13 +146,11 @@ def less_than_or_equal_to(self, other_numeric):
138146

139147
@export_type
140148
class BooleanType(BaseType):
141-
142149
name = "boolean"
143150

144151
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))
148154
return value
149155

150156
@type_operator(FIELD_NO_INPUT)
@@ -155,22 +161,22 @@ def is_true(self):
155161
def is_false(self):
156162
return not self.value
157163

164+
158165
@export_type
159166
class SelectType(BaseType):
160-
161167
name = "select"
162168

163169
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))
167172
return value
168173

169174
@staticmethod
170175
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()
174180
else:
175181
return value_from_list == other_value
176182

@@ -191,13 +197,13 @@ def does_not_contain(self, other_value):
191197

192198
@export_type
193199
class SelectMultipleType(BaseType):
194-
195200
name = "select_multiple"
196201

197202
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+
)
201207
return value
202208

203209
@type_operator(FIELD_SELECT_MULTIPLE)
@@ -235,3 +241,73 @@ def shares_exactly_one_element_with(self, other_value):
235241
@type_operator(FIELD_SELECT_MULTIPLE)
236242
def shares_no_elements_with(self, other_value):
237243
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

Comments
 (0)