Skip to content

Commit 5a786eb

Browse files
authored
Merge pull request #245 from ArangoDB-Community/feat-fill-defaults
Feature fill defaults
2 parents 2640184 + 0210599 commit 5a786eb

File tree

7 files changed

+246
-115
lines changed

7 files changed

+246
-115
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2.1.0
2+
=====
3+
* Added getitem for documents at the database level
4+
* Added fill_default() on documents to replace None values by schema defaults
5+
* fill_default() is automatically called on save
6+
17
2.0.2
28
=====
39
* Fixed contains functions

pyArango/collection.py

Lines changed: 116 additions & 95 deletions
Large diffs are not rendered by default.

pyArango/connection.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ class AikidoSession:
3434
"""
3535

3636
class Holder(object):
37-
def __init__(self, fct, auth, max_conflict_retries=5, verify=True):
37+
def __init__(self, fct, auth, max_conflict_retries=5, verify=True, timeout=30):
3838
self.fct = fct
3939
self.auth = auth
4040
self.max_conflict_retries = max_conflict_retries
4141
if not isinstance(verify, bool) and not isinstance(verify, CA_Certificate) and not not isinstance(verify, str) :
4242
raise ValueError("'verify' argument can only be of type: bool, CA_Certificate or str ")
4343
self.verify = verify
44+
self.timeout = timeout
4445

4546
def __call__(self, *args, **kwargs):
4647
if self.auth:
@@ -50,10 +51,12 @@ def __call__(self, *args, **kwargs):
5051
else :
5152
kwargs["verify"] = self.verify
5253

54+
kwargs["timeout"] = self.timeout
55+
5356
try:
5457
do_retry = True
5558
retry = 0
56-
while do_retry and retry < self.max_conflict_retries :
59+
while do_retry and retry < self.max_conflict_retries:
5760
ret = self.fct(*args, **kwargs)
5861
do_retry = ret.status_code == 1200
5962
try :
@@ -84,7 +87,8 @@ def __init__(
8487
max_retries=5,
8588
single_session=True,
8689
log_requests=False,
87-
pool_maxsize=10
90+
pool_maxsize=10,
91+
timeout=30,
8892
):
8993
if username:
9094
self.auth = (username, password)
@@ -95,6 +99,7 @@ def __init__(
9599
self.max_retries = max_retries
96100
self.log_requests = log_requests
97101
self.max_conflict_retries = max_conflict_retries
102+
self.timeout = timeout
98103

99104
self.session = None
100105
if single_session:
@@ -133,12 +138,13 @@ def __getattr__(self, request_function_name):
133138

134139
auth = object.__getattribute__(self, "auth")
135140
verify = object.__getattribute__(self, "verify")
141+
timeout = object.__getattribute__(self, "timeout")
136142
if self.log_requests:
137143
log = object.__getattribute__(self, "log")
138144
log["nb_request"] += 1
139145
log["requests"][request_function.__name__] += 1
140146

141-
return AikidoSession.Holder(request_function, auth, max_conflict_retries=self.max_conflict_retries, verify=verify)
147+
return AikidoSession.Holder(request_function, auth, max_conflict_retries=self.max_conflict_retries, verify=verify, timeout=timeout)
142148

143149
def disconnect(self):
144150
pass
@@ -180,6 +186,8 @@ class Connection(object):
180186
max number of requests for a conflict error (1200 arangodb error). Does not work with gevents (grequests),
181187
pool_maxsize: int
182188
max number of open connections. (Not intended for grequest)
189+
timeout: int
190+
number of seconds to wait on a hanging connection before giving up
183191
"""
184192

185193
LOAD_BLANCING_METHODS = {'round-robin', 'random'}
@@ -199,7 +207,8 @@ def __init__(
199207
use_lock_for_reseting_jwt=True,
200208
max_retries=5,
201209
max_conflict_retries=5,
202-
pool_maxsize=10
210+
pool_maxsize=10,
211+
timeout=30
203212
):
204213

205214
if loadBalancing not in Connection.LOAD_BLANCING_METHODS:
@@ -215,6 +224,7 @@ def __init__(
215224
self.max_retries = max_retries
216225
self.max_conflict_retries = max_conflict_retries
217226
self.action = ConnectionAction(self)
227+
self.timeout = timeout
218228

219229
self.databases = {}
220230
self.verbose = verbose
@@ -295,7 +305,8 @@ def create_aikido_session(
295305
max_conflict_retries=self.max_conflict_retries,
296306
max_retries=self.max_retries,
297307
log_requests=False,
298-
pool_maxsize=self.pool_maxsize
308+
pool_maxsize=self.pool_maxsize,
309+
timeout=self.timeout
299310
)
300311

301312
def create_grequest_session(

pyArango/database.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -526,16 +526,20 @@ def __contains__(self, name_or_id):
526526
else:
527527
return self.hasCollection(name_or_id) or self.hasGraph(name_or_id)
528528

529-
def __getitem__(self, collectionName):
530-
"""use database[collectionName] to get a collection from the database"""
529+
def __getitem__(self, col_or_doc_id):
530+
"""use database[col_or_doc_id] to get a collection from the database"""
531531
try:
532-
return self.collections[collectionName]
533-
except KeyError:
534-
self.reload()
532+
col_name, doc_key = col_or_doc_id.split('/')
533+
return self.collections[col_name][doc_key]
534+
except ValueError:
535535
try:
536-
return self.collections[collectionName]
536+
return self.collections[col_or_doc_id]
537537
except KeyError:
538-
raise KeyError("Can't find any collection named : %s" % collectionName)
538+
self.reload()
539+
try:
540+
return self.collections[col_or_doc_id]
541+
except KeyError:
542+
raise KeyError("Can't find any collection named : %s" % col_or_doc_id)
539543

540544
class DBHandle(Database):
541545
"As the loading of a Database also triggers the loading of collections and graphs within. Only handles are loaded first. The full database are loaded on demand in a fully transparent manner."

pyArango/document.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,7 @@ def validate(self):
108108
return True
109109

110110
def set(self, dct):
111-
"""Set the store using a dictionary"""
112-
# if not self.mustValidate:
113-
# self.store = dct
114-
# self.patchStore = dct
115-
# return
116-
111+
"""Set the values to a dict. Any missing value will be filled by it's default"""
117112
for field, value in dct.items():
118113
if field not in self.collection.arangoPrivates:
119114
if isinstance(value, dict):
@@ -126,6 +121,14 @@ def set(self, dct):
126121
else:
127122
self[field] = value
128123

124+
def fill_default(self):
125+
"""replace all None values with defaults"""
126+
for field, value in self.validators.items():
127+
if isinstance(value, dict):
128+
self[field].fill_default()
129+
elif self[field] is None:
130+
self[field] = value.default
131+
129132
def __dir__(self):
130133
return dir(self.getStore())
131134

@@ -234,6 +237,10 @@ def to_default(self):
234237
"""reset the document to the default values"""
235238
self.reset(self.collection, self.collection.getDefaultDocument())
236239

240+
def fill_default(self):
241+
"""reset the document to the default values"""
242+
self._store.fill_default()
243+
237244
def validate(self):
238245
"""validate the document"""
239246
self._store.validate()
@@ -264,7 +271,9 @@ def save(self, waitForSync = False, **docArgs):
264271
If you want to only update the modified fields use the .patch() function.
265272
Use docArgs to put things such as 'waitForSync = True' (for a full list cf ArangoDB's doc).
266273
It will only trigger a saving of the document if it has been modified since the last save. If you want to force the saving you can use forceSave()"""
274+
self._store.fill_default()
267275
payload = self._store.getStore()
276+
# print(payload)
268277
self._save(payload, waitForSync = False, **docArgs)
269278

270279
def _save(self, payload, waitForSync = False, **docArgs):

pyArango/tests/tests.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest, copy
22
import os
3+
from unittest.mock import MagicMock, patch
34

45
from pyArango.connection import *
56
from pyArango.database import *
@@ -122,7 +123,78 @@ class theCol(Collection):
122123
doc.to_default()
123124
self.assertEqual(doc["address"]["street"], "Paper street")
124125
self.assertEqual(doc["name"], "Tyler Durden")
126+
127+
# @unittest.skip("stand by")
128+
def test_fill_default(self):
129+
class theCol(Collection):
130+
_fields = {
131+
"name": Field( default="Paper"),
132+
"dct1":{
133+
"num": Field(default=13),
134+
"dct2":{
135+
"str": Field(default='string'),
136+
}
137+
}
138+
}
139+
140+
_validation = {
141+
"on_save" : True,
142+
"on_set" : True,
143+
"allow_foreign_fields" : False
144+
}
145+
146+
col = self.db.createCollection("theCol")
147+
doc = col.createDocument()
148+
doc['name'] = 'Orson'
149+
doc['dct1']['num'] = None
150+
doc['dct1']['dct2']['str'] = None
151+
152+
doc.fill_default()
153+
self.assertEqual(doc['name'], 'Orson')
154+
self.assertEqual(doc['dct1']['num'], 13)
155+
self.assertEqual(doc['dct1']['dct2']['str'], 'string')
156+
157+
# @unittest.skip("stand by")
158+
def test_fill_default_on_save(self):
159+
class theCol(Collection):
160+
_fields = {
161+
"name": Field( default="Paper"),
162+
"dct1":{
163+
"num": Field(default=13),
164+
"dct2":{
165+
"str": Field(default='string'),
166+
}
167+
}
168+
}
169+
170+
_validation = {
171+
"on_save" : True,
172+
"on_set" : True,
173+
"allow_foreign_fields" : False
174+
}
175+
176+
col = self.db.createCollection("theCol")
177+
doc = col.createDocument()
178+
doc['name'] = 'Orson'
179+
doc['dct1']['num'] = None
180+
doc['dct1']['dct2']['str'] = None
125181

182+
store = doc.getStore()
183+
doc.save()
184+
185+
self.assertEqual(store['name'], 'Orson')
186+
self.assertEqual(store['dct1']['num'], None)
187+
self.assertEqual(store['dct1']['dct2']['str'], None)
188+
189+
self.assertEqual(doc['name'], 'Orson')
190+
self.assertEqual(doc['dct1']['num'], 13)
191+
self.assertEqual(doc['dct1']['dct2']['str'], 'string')
192+
193+
doc2 = col[doc['_key']]
194+
self.assertEqual(doc2['name'], 'Orson')
195+
self.assertEqual(doc2['dct1']['num'], 13)
196+
self.assertEqual(doc2['dct1']['dct2']['str'], 'string')
197+
126198
# @unittest.skip("stand by")
127199
def test_bulk_operations(self):
128200
(collection, docs) = self.createManyUsersBulk(55, 17)
@@ -1072,7 +1144,15 @@ def test_tasks(self):
10721144
db_tasks.delete(task_id)
10731145
self.assertListEqual(db_tasks(), [])
10741146

1147+
# @unittest.skip("stand by")
1148+
def test_timeout_parameter(self):
1149+
# Create a Connection object with the desired timeout
1150+
timeout = 120
1151+
connection = Connection(arangoURL=ARANGODB_URL, username=ARANGODB_ROOT_USERNAME, password=ARANGODB_ROOT_PASSWORD, timeout=timeout)
10751152

1153+
# Verify that the Connection session was created with the correct timeout
1154+
assert connection.session.timeout == timeout
1155+
10761156
if __name__ == "__main__":
10771157
# Change default username/password in bash like this:
10781158
# export ARANGODB_ROOT_USERNAME=myUserName

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
setup(
1212
name='pyArango',
1313

14-
version='2.0.2',
14+
version='2.1.0',
1515

1616
description='An easy to use python driver for ArangoDB with built-in validation',
1717
long_description=long_description,

0 commit comments

Comments
 (0)