Skip to content

Commit 8cb4644

Browse files
author
aisch
committedNov 23, 2015
add time-delta field
1 parent a62f43c commit 8cb4644

File tree

3 files changed

+89
-17
lines changed

3 files changed

+89
-17
lines changed
 

‎pilo/fields.py

+68-16
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ def field1(self, value):
5858
except ImportError:
5959
pass
6060

61+
try:
62+
import pytimeparse
63+
except ImportError as ex:
64+
pass
65+
6166
from . import (
6267
NONE, NOT_SET, ERROR, IGNORE, ctx, ContextMixin, Close, Source, SourceError, DefaultSource, Types,
6368
)
@@ -731,16 +736,12 @@ def _validate(self, value):
731736
return True
732737

733738

734-
class Number(Field):
739+
class RangeMixin(object):
735740

736-
def __init__(self, *args, **kwargs):
737-
range_value = kwargs.pop('range', None)
738-
if range_value is not None:
739-
self.min_value, self.max_value = range_value
740-
else:
741-
self.min_value = kwargs.pop('min_value', None)
742-
self.max_value = kwargs.pop('max_value', None)
743-
super(Number, self).__init__(*args, **kwargs)
741+
742+
min_value = None
743+
744+
max_value = None
744745

745746
def min(self, value):
746747
self.min_value = value
@@ -753,9 +754,7 @@ def max(self, value):
753754
def range(self, l, r):
754755
return self.min(l).max(r)
755756

756-
def _validate(self, value):
757-
if not super(Number, self)._validate(value):
758-
return False
757+
def validate(self, value):
759758
if value is not None:
760759
if self.min_value is not None and value < self.min_value:
761760
self.ctx.errors.invalid('"{0}" must be >= {1}'.format(
@@ -769,6 +768,22 @@ def _validate(self, value):
769768
return False
770769
return True
771770

771+
class Number(Field, RangeMixin):
772+
773+
def __init__(self, *args, **kwargs):
774+
range_value = kwargs.pop('range', None)
775+
if range_value is not None:
776+
self.min_value, self.max_value = range_value
777+
else:
778+
self.min_value = kwargs.pop('min_value', None)
779+
self.max_value = kwargs.pop('max_value', None)
780+
super(Number, self).__init__(*args, **kwargs)
781+
782+
def _validate(self, value):
783+
if not super(Number, self)._validate(value):
784+
return False
785+
return RangeMixin.validate(self, value)
786+
772787

773788
class Integer(Number):
774789

@@ -836,7 +851,7 @@ def _parse(self, path):
836851
Bool = Boolean
837852

838853

839-
class RangeMixin(object):
854+
class TimeRangeMixin(object):
840855

841856
def after(self, value):
842857
self.after_value = value
@@ -850,7 +865,7 @@ def between(self, l, r):
850865
return self.after(l).before(r)
851866

852867

853-
class Date(Field, RangeMixin):
868+
class Date(Field, TimeRangeMixin):
854869

855870
def __init__(self, *args, **kwargs):
856871
self.after_value = kwargs.pop('after', None)
@@ -910,7 +925,7 @@ def _validate(self, value):
910925
return True
911926

912927

913-
class Time(Field, RangeMixin):
928+
class Time(Field, TimeRangeMixin):
914929

915930
def __init__(self, *args, **kwargs):
916931
self.after_value = kwargs.pop('after', None)
@@ -949,7 +964,7 @@ def _validate(self, value):
949964
return True
950965

951966

952-
class Datetime(Field, RangeMixin):
967+
class Datetime(Field, TimeRangeMixin):
953968

954969
def __init__(self, *args, **kwargs):
955970
self.after_value = kwargs.pop('after', None)
@@ -993,6 +1008,43 @@ def _validate(self, value):
9931008
return True
9941009

9951010

1011+
class TimeDelta(Field, RangeMixin):
1012+
1013+
def __init__(self, *args, **kwargs):
1014+
range_value = kwargs.pop('range', None)
1015+
if range_value is not None:
1016+
self.min_value, self.max_value = range_value
1017+
else:
1018+
self.min_value = kwargs.pop('min_value', None)
1019+
self.max_value = kwargs.pop('max_value', None)
1020+
self._format = kwargs.pop('format', 'human')
1021+
super(TimeDelta, self).__init__(*args, **kwargs)
1022+
1023+
def format(self, value):
1024+
self._format = value
1025+
return self
1026+
1027+
def _parse(self, path):
1028+
if isinstance(path.value, datetime.datetime):
1029+
return path.value
1030+
value = path.primitive(basestring)
1031+
if self._format == 'human':
1032+
parsed = pytimeparse.parse(value)
1033+
if parsed is None:
1034+
self.ctx.errors.invalid('Not a time-delta expression')
1035+
return ERROR
1036+
parsed = datetime.timedelta(seconds=parsed)
1037+
else:
1038+
self.ctx.errors.invalid('No format for value "{0}"'.format(value))
1039+
return ERROR
1040+
return parsed
1041+
1042+
def _validate(self, value):
1043+
if not super(TimeDelta, self)._validate(value):
1044+
return False
1045+
return RangeMixin.validate(self, value)
1046+
1047+
9961048
class Tuple(Field):
9971049

9981050
def __init__(self, *args, **kwargs):

‎setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def run_tests(self):
1818
'tests': [
1919
'pytest >=2.5.2,<3',
2020
'pytest-cov >=1.7,<2',
21+
'pytimeparse >=1.1.5,<2',
2122
'mock >=1.0,<2.0',
2223
'unittest2 >=0.5.1,<0.6',
2324
'iso8601 >=0.1,<0.2',

‎tests/test_fields.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import abc
22
import datetime
3+
import inspect
34
import re
45

56
import pilo
@@ -295,6 +296,24 @@ class MyForm(pilo.Form):
295296

296297
MyForm(src)
297298

299+
def test_time_delta(self):
300+
301+
class Task(pilo.Form):
302+
303+
due_in = pilo.fields.TimeDelta()
304+
305+
for desc, expected in [
306+
(dict(due_in='10s'), datetime.timedelta(seconds=10)),
307+
(dict(due_in='1h10s'), datetime.timedelta(hours=1, seconds=10)),
308+
(dict(due_in='nevah'), pilo.Invalid),
309+
]:
310+
if inspect.isclass(expected) and issubclass(expected, Exception):
311+
with self.assertRaises(expected):
312+
Task(desc)
313+
else:
314+
obj = Task(desc)
315+
self.assertEqual(obj.due_in, expected)
316+
298317

299318
class TestFormPolymorphism(TestCase):
300319

@@ -339,7 +358,7 @@ class Dog(Animal):
339358
self.assertEqual(left, right)
340359

341360
def test_computed(self):
342-
361+
343362
class Animal(pilo.Form):
344363

345364
clothed = pilo.fields.Boolean()

0 commit comments

Comments
 (0)
Please sign in to comment.