Skip to content

Commit 1e1bb9f

Browse files
committed
Add datetime and timedelta conversion ,Add tests
1 parent f435b75 commit 1e1bb9f

File tree

6 files changed

+181
-16
lines changed

6 files changed

+181
-16
lines changed

pyravendb/data/document_convention.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from pyravendb.data.indexes import SortOptions
22
from datetime import datetime, timedelta
3+
from pyravendb.tools.utils import Utils
34
from enum import Enum
45
from inflector import Inflector
6+
import sys
57

68

79
class Failover(Enum):
@@ -68,9 +70,9 @@ def __init__(self):
6870
@staticmethod
6971
def json_default(o):
7072
if isinstance(o, datetime):
71-
return str(0)
73+
return Utils.datetime_to_string(o)
7274
elif isinstance(o, timedelta):
73-
return str(0)
75+
return Utils.timedelta_to_str(o)
7476
elif getattr(o, "__dict__"):
7577
return o.__dict__
7678
else:
@@ -104,9 +106,13 @@ def try_get_type_from_metadata(metadata):
104106
def uses_range_type(obj):
105107
if obj is None:
106108
return False
107-
108-
if isinstance(obj, int) or isinstance(obj, long) or isinstance(obj, float):
109-
return True
109+
if sys.version_info.major > 2:
110+
if isinstance(obj, int) or isinstance(obj, float):
111+
return True
112+
else:
113+
if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, long):
114+
return True
115+
return False
110116

111117
@staticmethod
112118
def get_default_sort_option(type_name):

pyravendb/store/session_query.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from pyravendb.custom_exceptions.exceptions import ErrorResponseException
33
from pyravendb.data.indexes import IndexQuery
44
from pyravendb.tools.utils import Utils
5+
from datetime import timedelta
6+
import sys
57
import time
68

79

@@ -42,9 +44,6 @@ def __call__(self, object_type=None, index_name=None, using_default_operator=Non
4244
self._with_statistics = with_statistics
4345
return self
4446

45-
def __iter__(self):
46-
return self._execute_query().__iter__()
47-
4847
def _lucene_builder(self, value, action=None):
4948
lucene_text = Utils.to_lucene(value, action=action)
5049

@@ -56,6 +55,9 @@ def _lucene_builder(self, value, action=None):
5655

5756
return lucene_text
5857

58+
def __iter__(self):
59+
return self._execute_query().__iter__()
60+
5961
def where_equals(self, field_name, value):
6062
"""
6163
To get all the document that equal to the value in the given field_name
@@ -70,6 +72,10 @@ def where_equals(self, field_name, value):
7072
if value is not None and not isinstance(value, str) and field_name is not None:
7173
sort_hint = self.session.conventions.get_default_sort_option(type(value).__name__)
7274
if sort_hint:
75+
field_name = "{0}_Range".format(field_name)
76+
if sys.version_info.major > 2:
77+
if value > sys.maxsize:
78+
sort_hint = self.session.conventions.get_default_sort_option("long")
7379
self._sort_hints.add("SortHint-{0}={1}".format(field_name, sort_hint))
7480

7581
lucene_text = self._lucene_builder(value, action="equal")
@@ -106,6 +112,10 @@ def where_ends_with(self, field_name, value):
106112
if value is not None and not isinstance(value, str) and field_name is not None:
107113
sort_hint = self.session.conventions.get_default_sort_option(type(value).__name__)
108114
if sort_hint:
115+
field_name = "{0}_Range".format(field_name)
116+
if sys.version_info.major > 2:
117+
if value > sys.maxsize:
118+
sort_hint = self.session.conventions.get_default_sort_option("long")
109119
self._sort_hints.add("SortHint-{0}={1}".format(field_name, sort_hint))
110120

111121
lucene_text = self._lucene_builder(value, action="end_with")
@@ -127,6 +137,10 @@ def where_starts_with(self, field_name, value):
127137
if value is not None and not isinstance(value, str) and field_name is not None:
128138
sort_hint = self.session.conventions.get_default_sort_option(type(value).__name__)
129139
if sort_hint:
140+
field_name = "{0}_Range".format(field_name)
141+
if sys.version_info.major > 2:
142+
if value > sys.maxsize:
143+
sort_hint = self.session.conventions.get_default_sort_option("long")
130144
self._sort_hints.add("SortHint-{0}={1}".format(field_name, sort_hint))
131145

132146
lucene_text = self._lucene_builder(value, action="end_with")
@@ -152,23 +166,40 @@ def where_in(self, field_name, values):
152166
return self
153167

154168
def where_between(self, field_name, start, end):
169+
if isinstance(start, timedelta):
170+
start = Utils.timedelta_tick(start)
171+
if isinstance(end, timedelta):
172+
end = Utils.timedelta_tick(end)
173+
155174
value = start or end
156175
if self.session.conventions.uses_range_type(value) and not field_name.endswith("_Range"):
157176
sort_hint = self.session.conventions.get_default_sort_option(type(value).__name__)
158177
if sort_hint:
178+
field_name = "{0}_Range".format(field_name)
179+
if sys.version_info.major > 2:
180+
if value > sys.maxsize:
181+
sort_hint = self.session.conventions.get_default_sort_option("long")
159182
self._sort_hints.add("SortHint-{0}={1}".format(field_name, sort_hint))
160-
field_name = "{0}_Range".format(field_name)
183+
161184
lucene_text = self._lucene_builder([start, end], action="between")
162185
self.query_builder += "{0}:{1}".format(field_name, lucene_text)
163186
return self
164187

165188
def where_between_or_equal(self, field_name, start, end):
189+
if isinstance(start, timedelta):
190+
start = Utils.timedelta_tick(start)
191+
if isinstance(end, timedelta):
192+
end = Utils.timedelta_tick(end)
193+
166194
value = start or end
167195
if self.session.conventions.uses_range_type(value) and not field_name.endswith("_Range"):
168196
sort_hint = self.session.conventions.get_default_sort_option(type(value).__name__)
169197
if sort_hint:
198+
field_name = "{0}_Range".format(field_name)
199+
if sys.version_info.major > 2:
200+
if value > sys.maxsize:
201+
sort_hint = self.session.conventions.get_default_sort_option("long")
170202
self._sort_hints.add("SortHint-{0}={1}".format(field_name, sort_hint))
171-
field_name = "{0}_Range".format(field_name)
172203
lucene_text = self._lucene_builder([start, end], action="equal_between")
173204
self.query_builder += "{0}:{1}".format(field_name, lucene_text)
174205
return self
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from pyravendb.tests.test_base import TestBase
2+
from pyravendb.store.document_store import documentstore
3+
from datetime import datetime, timedelta
4+
from pyravendb.tools.utils import Utils
5+
import unittest
6+
7+
8+
class Time(object):
9+
def __init__(self, td, dt):
10+
self.td = td
11+
self.dt = dt
12+
13+
14+
class TestConversion(TestBase):
15+
@classmethod
16+
def setUpClass(cls):
17+
super(TestConversion, cls).setUpClass()
18+
19+
cls.db.put("times/3",
20+
{"td": Utils.timedelta_to_str(timedelta(days=20, minutes=23, seconds=59, milliseconds=254)),
21+
"dt": Utils.datetime_to_string(datetime.now())}, {"Raven-Entity-Name": "Times"})
22+
23+
cls.db.put("times/4",
24+
{"td": Utils.timedelta_to_str(timedelta(minutes=23, seconds=59, milliseconds=254)),
25+
"dt": Utils.datetime_to_string(datetime.now())}, {"Raven-Entity-Name": "Times"})
26+
27+
cls.document_store = documentstore(cls.default_url, cls.default_database)
28+
cls.document_store.initialize()
29+
30+
def test_load_timedelta_and_datetime(self):
31+
with self.document_store.open_session() as session:
32+
times = session.load("times/3", object_type=Time, nested_object_types={"td": timedelta, "dt": datetime})
33+
self.assertTrue(isinstance(times.td, timedelta) and isinstance(times.dt, datetime))
34+
35+
def test_store_conversion(self):
36+
with self.document_store.open_session() as session:
37+
times = Time(timedelta(days=1, hours=1), datetime.now())
38+
session.store(times)
39+
key = session.advanced.get_document_id(times)
40+
session.save_changes()
41+
42+
with self.document_store.open_session() as session:
43+
times = session.load(key, object_type=Time, nested_object_types={"td": timedelta, "dt": datetime})
44+
self.assertTrue(isinstance(times.td, timedelta) and isinstance(times.dt, datetime))
45+
46+
def test_query_conversion(self):
47+
with self.document_store.open_session() as session:
48+
query = list(session.query(object_type=Time, nested_object_types={"td": timedelta,
49+
"dt": datetime}).where_greater_than_or_equal(
50+
"td", timedelta(days=9)))
51+
52+
not_working = False
53+
if len(query) < 1:
54+
not_working = True
55+
for item in query:
56+
if not isinstance(item.td, timedelta):
57+
not_working = True
58+
59+
self.assertFalse(not_working)
60+
61+
62+
if __name__ == "__main__":
63+
unittest.main()

pyravendb/tests/session_tests/load_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ def __init__(self, name, key):
2121
self.key = key
2222

2323

24+
class Time(object):
25+
def __init__(self, td):
26+
self.td = td
27+
28+
2429
class TestLoad(TestBase):
2530
@classmethod
2631
def setUpClass(cls):

pyravendb/tools/utils.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pyravendb.data.operations import BulkOperationOption
22
from pyravendb.data.indexes import IndexQuery
33
from pyravendb.custom_exceptions import exceptions
4+
from datetime import datetime, timedelta
45
import urllib
56
import inspect
67
import sys
@@ -113,8 +114,13 @@ def convert_to_entity(document, object_type, conventions, nested_object_types=No
113114
attr = getattr(entity, key)
114115
if attr:
115116
try:
116-
setattr(entity, key, nested_object_types[key](
117-
**Utils.make_initialize_dict(attr, nested_object_types[key].__init__)))
117+
if nested_object_types[key] is datetime:
118+
setattr(entity, key, Utils.string_to_datetime(attr))
119+
elif nested_object_types[key] is timedelta:
120+
setattr(entity, key, Utils.string_to_timedelta(attr))
121+
else:
122+
setattr(entity, key, nested_object_types[key](
123+
**Utils.make_initialize_dict(attr, nested_object_types[key].__init__)))
118124
except TypeError:
119125
pass
120126

@@ -188,11 +194,15 @@ def numeric_to_lucene_syntax(value):
188194
elif not value:
189195
value = "[[EMPTY_STRING]]"
190196

191-
if isinstance(value, float) or isinstance(value, int):
197+
python_version = sys.version_info.major
198+
199+
if (python_version > 2 and value > sys.maxsize and isinstance(value,int)) \
200+
or python_version <= 2 and isinstance(value, long):
201+
value = "Lx{0}".format(int(value))
202+
203+
elif isinstance(value, float) or isinstance(value, int):
192204
value = "Dx{0}".format(value)
193205

194-
elif isinstance(value, long):
195-
value = "Lx{0}".format(value)
196206
return value
197207

198208
@staticmethod
@@ -203,3 +213,53 @@ def dict_to_string(dictionary):
203213
dictionary[item] = dictionary[item].decode('utf-8')
204214
builder.append('{0}={1}'.format(item, dictionary[item]))
205215
return ','.join(item for item in builder)
216+
217+
@staticmethod
218+
def datetime_to_string(datetime_obj):
219+
return datetime_obj.strftime("%Y-%m-%dT%H:%M:%S.%f0")
220+
221+
@staticmethod
222+
def string_to_datetime(datetime_str):
223+
try:
224+
datetime_s = datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S.%f")
225+
except ValueError:
226+
datetime_s = datetime.strptime(datetime_str[:-1], "%Y-%m-%dT%H:%M:%S.%f")
227+
228+
return datetime_s
229+
230+
@staticmethod
231+
def timedelta_tick(td):
232+
return int(td.total_seconds() * 10000000)
233+
234+
@staticmethod
235+
def string_to_timedelta(timedelta_str):
236+
pattern = r'(?:(-?\d+)[.])?(\d{2}):(\d{2}):(\d{2})(?:.(\d+))?'
237+
timedelta_initialize = None
238+
m = re.match(pattern, timedelta_str, re.IGNORECASE)
239+
if m:
240+
timedelta_initialize = {"days": 0 if m.group(1) is None else int(m.group(1)),
241+
"hours": 0 if m.group(2) is None else int(m.group(2)),
242+
"minutes": 0 if m.group(3) is None else int(m.group(3)),
243+
"seconds": 0 if m.group(4) is None else int(m.group(4)),
244+
"microseconds": 0 if m.group(5) is None else int(m.group(5))
245+
}
246+
if timedelta_initialize:
247+
return timedelta(**timedelta_initialize)
248+
return None
249+
250+
@staticmethod
251+
def timedelta_to_str(timedelta_obj):
252+
timedelta_str = ""
253+
if isinstance(timedelta_obj, timedelta):
254+
total_seconds = timedelta_obj.seconds
255+
days = timedelta_obj.days
256+
hours = total_seconds // 3600
257+
minutes = (total_seconds // 60) % 60
258+
seconds = (total_seconds % 3600) % 60
259+
microseconds = timedelta_obj.microseconds
260+
if days > 0:
261+
timedelta_str += "{0}.".format(days)
262+
timedelta_str += "{:02}:{:02}:{:02}".format(hours, minutes, seconds)
263+
if microseconds > 0:
264+
timedelta_str += ".{0}".format(microseconds)
265+
return timedelta_str

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
setup(
44
name='pyravendb',
55
packages=find_packages(),
6-
version='1.1.0',
6+
version='1.1.1',
77
description='This is the official python client for RavenDB document database',
88
author='Idan Haim Shalom',
99
author_email='[email protected]',

0 commit comments

Comments
 (0)