Skip to content

Commit f1e6ae3

Browse files
committed
Merge branch 'backport-bk-pyarango' of https://github.com/dothebart/pyArango into dothebart-backport-bk-pyarango
2 parents b849941 + 7095efe commit f1e6ae3

File tree

7 files changed

+133
-76
lines changed

7 files changed

+133
-76
lines changed

pyArango/collection.py

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from . import consts as CONST
55

66
from .document import Document, Edge
7-
from .theExceptions import ValidationError, SchemaViolation, CreationError, UpdateError, DeletionError, InvalidDocument, ExportError
7+
8+
from .theExceptions import ValidationError, SchemaViolation, CreationError, UpdateError, DeletionError, InvalidDocument, ExportError, DocumentNotFoundError
9+
810
from .query import SimpleQuery
911
from .index import Index
1012

@@ -109,13 +111,15 @@ def __repr__(self) :
109111

110112
class Field(object) :
111113
"""The class for defining pyArango fields."""
112-
def __init__(self, validators = [], default="") :
113-
"validators must be a list of validators"
114+
def __init__(self, validators = None, default = "") :
115+
"""validators must be a list of validators"""
116+
if not validators:
117+
validators = []
114118
self.validators = validators
115119
self.default = default
116120

117121
def validate(self, value) :
118-
"checks the validity of 'value' given the lits of validators"
122+
"""checks the validity of 'value' given the lits of validators"""
119123
for v in self.validators :
120124
v.validate(value)
121125
return True
@@ -208,7 +212,7 @@ def isEdgeCollection(name) :
208212
return Collection_metaclass.isEdgeCollection(name)
209213

210214
def getCollectionClasses() :
211-
"returns a dictionary of all defined collection classes"
215+
"""returns a dictionary of all defined collection classes"""
212216
return Collection_metaclass.collectionClasses
213217

214218
class Collection(with_metaclass(Collection_metaclass, object)) :
@@ -269,22 +273,22 @@ def getIndexes(self) :
269273
return self.indexes
270274

271275
def activateCache(self, cacheSize) :
272-
"Activate the caching system. Cached documents are only available through the __getitem__ interface"
276+
"""Activate the caching system. Cached documents are only available through the __getitem__ interface"""
273277
self.documentCache = DocumentCache(cacheSize)
274278

275279
def deactivateCache(self) :
276280
"deactivate the caching system"
277281
self.documentCache = None
278282

279283
def delete(self) :
280-
"deletes the collection from the database"
284+
"""deletes the collection from the database"""
281285
r = self.connection.session.delete(self.URL)
282286
data = r.json()
283287
if not r.status_code == 200 or data["error"] :
284288
raise DeletionError(data["errorMessage"], data)
285289

286290
def createDocument(self, initDict = None) :
287-
"create and returns a document populated with the defaults or with the values in initDict"
291+
"""create and returns a document populated with the defaults or with the values in initDict"""
288292
if initDict is not None :
289293
return self.createDocument_(initDict)
290294
else :
@@ -369,7 +373,7 @@ def ensureFulltextIndex(self, fields, minLength = None) :
369373
"fields" : fields,
370374
}
371375
if minLength is not None :
372-
data["minLength"] = minLength
376+
data["minLength"] = minLength
373377

374378
ind = Index(self, creationData = data)
375379
self.indexes["fulltext"][ind.infos["id"]] = ind
@@ -436,7 +440,7 @@ def validatePrivate(self, field, value) :
436440

437441
@classmethod
438442
def hasField(cls, fieldName) :
439-
"returns True/False wether the collection has field K in it's schema. Use the dot notation for the nested fields: address.street"
443+
"""returns True/False wether the collection has field K in it's schema. Use the dot notation for the nested fields: address.street"""
440444
path = fieldName.split(".")
441445
v = cls._fields
442446
for k in path :
@@ -454,15 +458,18 @@ def fetchDocument(self, key, rawResults = False, rev = None) :
454458
r = self.connection.session.get(url, params = {'rev' : rev})
455459
else :
456460
r = self.connection.session.get(url)
457-
if (r.status_code - 400) < 0 :
461+
462+
if r.status_code < 400 :
458463
if rawResults :
459464
return r.json()
460465
return self.documentClass(self, r.json())
466+
elif r.status_code == 404 :
467+
raise DocumentNotFoundError("Unable to find document with _key: %s" % key, r.json())
461468
else :
462-
raise KeyError("Unable to find document with _key: %s" % key, r.json())
469+
raise Exception("Unable to find document with _key: %s, response: %s" % key, r.json())
463470

464471
def fetchByExample(self, exampleDict, batchSize, rawResults = False, **queryArgs) :
465-
"exampleDict should be something like {'age' : 28}"
472+
"""exampleDict should be something like {'age' : 28}"""
466473
return self.simpleQuery('by-example', rawResults, example = exampleDict, batchSize = batchSize, **queryArgs)
467474

468475
def fetchFirstExample(self, exampleDict, rawResults = False) :
@@ -483,7 +490,7 @@ def simpleQuery(self, queryType, rawResults = False, **queryArgs) :
483490
return SimpleQuery(self, queryType, rawResults, **queryArgs)
484491

485492
def action(self, method, action, **params) :
486-
"a generic fct for interacting everything that doesn't have an assigned fct"
493+
"""a generic fct for interacting everything that doesn't have an assigned fct"""
487494
fct = getattr(self.connection.session, method.lower())
488495
r = fct(self.URL + "/" + action, params = params)
489496
return r.json()
@@ -552,19 +559,19 @@ def bulkImport_values(self, filename, onDuplicate="error", **params) :
552559
raise UpdateError(data['errorMessage'], data)
553560

554561
def truncate(self) :
555-
"deletes every document in the collection"
562+
"""deletes every document in the collection"""
556563
return self.action('PUT', 'truncate')
557564

558565
def empty(self) :
559-
"alias for truncate"
566+
"""alias for truncate"""
560567
return self.truncate()
561568

562569
def load(self) :
563-
"loads collection in memory"
570+
"""loads collection in memory"""
564571
return self.action('PUT', 'load')
565572

566573
def unload(self) :
567-
"unloads collection from memory"
574+
"""unloads collection from memory"""
568575
return self.action('PUT', 'unload')
569576

570577
def revision(self) :
@@ -588,7 +595,7 @@ def figures(self) :
588595
return self.action('GET', 'figures')
589596

590597
def getType(self) :
591-
"returns a word describing the type of the collection (edges or ducments) instead of a number, if you prefer the number it's in self.type"
598+
"""returns a word describing the type of the collection (edges or ducments) instead of a number, if you prefer the number it's in self.type"""
592599
if self.type == CONST.COLLECTION_DOCUMENT_TYPE :
593600
return "document"
594601
elif self.type == CONST.COLLECTION_EDGE_TYPE :
@@ -597,7 +604,7 @@ def getType(self) :
597604
raise ValueError("The collection is of Unknown type %s" % self.type)
598605

599606
def getStatus(self) :
600-
"returns a word describing the status of the collection (loaded, loading, deleted, unloaded, newborn) instead of a number, if you prefer the number it's in self.status"
607+
"""returns a word describing the status of the collection (loaded, loading, deleted, unloaded, newborn) instead of a number, if you prefer the number it's in self.status"""
601608
if self.status == CONST.COLLECTION_LOADING_STATUS :
602609
return "loading"
603610
elif self.status == CONST.COLLECTION_LOADED_STATUS :
@@ -619,7 +626,7 @@ def __repr__(self) :
619626
return "ArangoDB collection name: %s, id: %s, type: %s, status: %s" % (self.name, self.id, self.getType(), self.getStatus())
620627

621628
def __getitem__(self, key) :
622-
"returns a document from the cache. If it's not there, fetches it from the db and caches it first. If the cache is not activated this is equivalent to fetchDocument( rawResults = False)"
629+
"""returns a document from the cache. If it's not there, fetches it from the db and caches it first. If the cache is not activated this is equivalent to fetchDocument( rawResults=False)"""
623630
if self.documentCache is None :
624631
return self.fetchDocument(key, rawResults = False)
625632
try :
@@ -638,17 +645,17 @@ def __contains__(self) :
638645
return False
639646

640647
class SystemCollection(Collection) :
641-
"for all collections with isSystem = True"
648+
"""for all collections with isSystem = True"""
642649
def __init__(self, database, jsonData) :
643650
Collection.__init__(self, database, jsonData)
644651

645652
class Edges(Collection) :
646-
"The default edge collection. All edge Collections must inherit from it"
653+
"""The default edge collection. All edge Collections must inherit from it"""
647654

648655
arangoPrivates = ["_id", "_key", "_rev", "_to", "_from"]
649656

650657
def __init__(self, database, jsonData) :
651-
"This one is meant to be called by the database"
658+
"""This one is meant to be called by the database"""
652659
Collection.__init__(self, database, jsonData)
653660
self.documentClass = Edge
654661
self.edgesURL = "%s/edges/%s" % (self.database.URL, self.name)
@@ -668,19 +675,21 @@ def validateField(cls, fieldName, value) :
668675
return valValue
669676

670677
def createEdge(self) :
671-
"Create an edge populated with defaults"
678+
"""Create an edge populated with defaults"""
672679
return self.createDocument()
673680

674-
def createEdge_(self, initValues = {}) :
675-
"Create an edge populated with initValues"
681+
def createEdge_(self, initValues = None) :
682+
"""Create an edge populated with initValues"""
683+
if not initValues:
684+
initValues = {}
676685
return self.createDocument_(initValues)
677686

678687
def getInEdges(self, vertex, rawResults = False) :
679-
"An alias for getEdges() that returns only the in Edges"
688+
"""An alias for getEdges() that returns only the in Edges"""
680689
return self.getEdges(vertex, inEdges = True, outEdges = False, rawResults = rawResults)
681690

682691
def getOutEdges(self, vertex, rawResults = False) :
683-
"An alias for getEdges() that returns only the out Edges"
692+
"""An alias for getEdges() that returns only the out Edges"""
684693
return self.getEdges(vertex, inEdges = False, outEdges = True, rawResults = rawResults)
685694

686695
def getEdges(self, vertex, inEdges = True, outEdges = True, rawResults = False) :
@@ -715,6 +724,3 @@ def getEdges(self, vertex, inEdges = True, outEdges = True, rawResults = False)
715724
return data["edges"]
716725
else :
717726
raise CreationError("Unable to return edges for vertex: %s" % vId, data)
718-
719-
720-

pyArango/database.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def createCollection(self, className = 'Collection', **colProperties) :
9595
colProperties = dict(colClass._properties)
9696
except AttributeError :
9797
colProperties = {}
98-
98+
9999
if className != 'Collection' and className != 'Edges' :
100100
colProperties['name'] = className
101101
else :
@@ -126,7 +126,7 @@ def fetchDocument(self, _id) :
126126
sid = _id.split("/")
127127
return self[sid[0]][sid[1]]
128128

129-
def createGraph(self, name) :
129+
def createGraph(self, name, createCollections = True, isSmart = False, numberOfShards = None, smartGraphAttribute = None) :
130130
"""Creates a graph and returns it. 'name' must be the name of a class inheriting from Graph.
131131
Checks will be performed to make sure that every collection mentionned in the edges definition exist. Raises a ValueError in case of
132132
a non-existing collection."""
@@ -149,13 +149,26 @@ def _checkCollectionList(lst) :
149149

150150
_checkCollectionList(graphClass._orphanedCollections)
151151

152+
options = {}
153+
if numberOfShards:
154+
options['numberOfShards'] = numberOfShards
155+
if smartGraphAttribute:
156+
options['smartGraphAttribute'] = smartGraphAttribute
157+
152158
payload = {
153159
"name": name,
154160
"edgeDefinitions": ed,
155161
"orphanCollections": graphClass._orphanedCollections
156162
}
157163

158-
payload = json.dumps(payload, default=str)
164+
if isSmart :
165+
payload['isSmart'] = isSmart
166+
167+
if options:
168+
payload['options'] = options
169+
170+
payload = json.dumps(payload)
171+
159172
r = self.connection.session.post(self.graphsURL, data = payload)
160173
data = r.json()
161174

@@ -196,8 +209,12 @@ def explainAQLQuery(self, query, bindVars={}, allPlans = False) :
196209
request = self.connection.session.post(self.explainURL, data = json.dumps(payload, default=str))
197210
return request.json()
198211

199-
def validateAQLQuery(self, query, bindVars = {}, options = {}) :
212+
def validateAQLQuery(self, query, bindVars = None, options = None) :
200213
"returns the server answer is the query is valid. Raises an AQLQueryError if not"
214+
if bindVars is None :
215+
bindVars = {}
216+
if options is None :
217+
options = {}
201218
payload = {'query' : query, 'bindVars' : bindVars, 'options' : options}
202219
r = self.connection.session.post(self.cursorsURL, data = json.dumps(payload, default=str))
203220
data = r.json()
@@ -225,7 +242,7 @@ def transaction(self, collections, action, waitForSync = False, lockTimeout = No
225242

226243
data = r.json()
227244

228-
if (r.status_code == 200 or r.status_code == 201 or r.status_code == 202) and not data["error"] :
245+
if (r.status_code == 200 or r.status_code == 201 or r.status_code == 202) and not data.get("error") :
229246
return data
230247
else :
231248
raise TransactionError(data["errorMessage"], action, data)

pyArango/document.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,16 @@ def __repr__(self) :
181181
class Document(object) :
182182
"""The class that represents a document. Documents are meant to be instanciated by collections"""
183183

184-
def __init__(self, collection, jsonFieldInit = {}) :
185-
self.typeName = "ArangoDoc"
184+
def __init__(self, collection, jsonFieldInit = None) :
185+
if jsonFieldInit is None :
186+
jsonFieldInit = {}
186187
self.privates = ["_id", "_key", "_rev"]
187188
self.reset(collection, jsonFieldInit)
189+
self.typeName = "ArangoDoc"
188190

189-
def reset(self, collection, jsonFieldInit = {}) :
191+
def reset(self, collection, jsonFieldInit = None) :
192+
if not jsonFieldInit:
193+
jsonFieldInit = {}
190194
"""replaces the current values in the document by those in jsonFieldInit"""
191195
self.collection = collection
192196
self.connection = self.collection.connection
@@ -383,23 +387,19 @@ def __repr__(self) :
383387

384388
class Edge(Document) :
385389
"""An Edge document"""
386-
def __init__(self, edgeCollection, jsonFieldInit = {}) :
390+
def __init__(self, edgeCollection, jsonFieldInit = None) :
391+
if not jsonFieldInit:
392+
jsonFieldInit = {}
393+
387394
self.typeName = "ArangoEdge"
388395
self.privates = ["_id", "_key", "_rev", "_from", "_to"]
389396
self.reset(edgeCollection, jsonFieldInit)
390397

391-
def reset(self, edgeCollection, jsonFieldInit = {}) :
398+
def reset(self, edgeCollection, jsonFieldInit = None) :
399+
if jsonFieldInit is None:
400+
jsonFieldInit = {}
392401
Document.reset(self, edgeCollection, jsonFieldInit)
393402

394-
# def setPrivates(self, fieldDict) :
395-
# """set _id, _key, _rev, _from, _to"""
396-
# super(Edge, self).setPrivates(fieldDict)
397-
# if "_from" in fieldDict :
398-
# self._from = fieldDict["_from"]
399-
400-
# if "_to" in fieldDict :
401-
# self._to = fieldDict["_to"]
402-
403403
def links(self, fromVertice, toVertice, **edgeArgs) :
404404
"""
405405
An alias to save that updates the _from and _to attributes.

pyArango/query.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ def __init__(self, request, database, rawResults) :
3434

3535
self.rawResults = rawResults
3636
self.response = request.json()
37-
if self.response["error"] and self.response["errorMessage"] != "no match" :
37+
if self.response.get("error") and self.response["errorMessage"] != "no match" :
3838
raise QueryError(self.response["errorMessage"], self.response)
3939

4040
self.request = request
4141
self.database = database
4242
self.connection = self.database.connection
4343
self.currI = 0
44-
if request.status_code == 201 or request.status_code == 200:
44+
if request.status_code == 201 or request.status_code == 200 or request.status_code == 202 :
4545
self.batchNumber = 1
4646
try : #if there's only one element
4747
self.response = {"result" : [self.response["document"]], 'hasMore' : False}
@@ -50,7 +50,8 @@ def __init__(self, request, database, rawResults) :
5050
pass
5151

5252
if "hasMore" in self.response and self.response["hasMore"] :
53-
self.cursor = RawCursor(self.database, self.id)
53+
cursor_id = self.response.get("id","")
54+
self.cursor = RawCursor(self.database, cursor_id)
5455
else :
5556
self.cursor = None
5657
elif request.status_code == 404 :

pyArango/tests/tests.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ def test_bulk_import_exception(self):
8989
usersCollection.importBulk(users, onDuplicate="error", complete=True)
9090
self.assertEqual(usersCollection.count(), 0)
9191

92-
# @unittest.skip("stand by")
93-
92+
# @unittest.skip("stand by")
9493
def test_bulk_import_error_return_value(self):
9594
usersCollection = self.db.createCollection(name="users")
9695
nbUsers = 2

0 commit comments

Comments
 (0)