Skip to content

Commit

Permalink
Merge branch 'backport-bk-pyarango' of https://github.com/dothebart/p…
Browse files Browse the repository at this point in the history
…yArango into dothebart-backport-bk-pyarango
  • Loading branch information
tariqdaouda committed Sep 27, 2018
2 parents b849941 + 7095efe commit f1e6ae3
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 76 deletions.
70 changes: 38 additions & 32 deletions pyArango/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from . import consts as CONST

from .document import Document, Edge
from .theExceptions import ValidationError, SchemaViolation, CreationError, UpdateError, DeletionError, InvalidDocument, ExportError

from .theExceptions import ValidationError, SchemaViolation, CreationError, UpdateError, DeletionError, InvalidDocument, ExportError, DocumentNotFoundError

from .query import SimpleQuery
from .index import Index

Expand Down Expand Up @@ -109,13 +111,15 @@ def __repr__(self) :

class Field(object) :
"""The class for defining pyArango fields."""
def __init__(self, validators = [], default="") :
"validators must be a list of validators"
def __init__(self, validators = None, default = "") :
"""validators must be a list of validators"""
if not validators:
validators = []
self.validators = validators
self.default = default

def validate(self, value) :
"checks the validity of 'value' given the lits of validators"
"""checks the validity of 'value' given the lits of validators"""
for v in self.validators :
v.validate(value)
return True
Expand Down Expand Up @@ -208,7 +212,7 @@ def isEdgeCollection(name) :
return Collection_metaclass.isEdgeCollection(name)

def getCollectionClasses() :
"returns a dictionary of all defined collection classes"
"""returns a dictionary of all defined collection classes"""
return Collection_metaclass.collectionClasses

class Collection(with_metaclass(Collection_metaclass, object)) :
Expand Down Expand Up @@ -269,22 +273,22 @@ def getIndexes(self) :
return self.indexes

def activateCache(self, cacheSize) :
"Activate the caching system. Cached documents are only available through the __getitem__ interface"
"""Activate the caching system. Cached documents are only available through the __getitem__ interface"""
self.documentCache = DocumentCache(cacheSize)

def deactivateCache(self) :
"deactivate the caching system"
self.documentCache = None

def delete(self) :
"deletes the collection from the database"
"""deletes the collection from the database"""
r = self.connection.session.delete(self.URL)
data = r.json()
if not r.status_code == 200 or data["error"] :
raise DeletionError(data["errorMessage"], data)

def createDocument(self, initDict = None) :
"create and returns a document populated with the defaults or with the values in initDict"
"""create and returns a document populated with the defaults or with the values in initDict"""
if initDict is not None :
return self.createDocument_(initDict)
else :
Expand Down Expand Up @@ -369,7 +373,7 @@ def ensureFulltextIndex(self, fields, minLength = None) :
"fields" : fields,
}
if minLength is not None :
data["minLength"] = minLength
data["minLength"] = minLength

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

@classmethod
def hasField(cls, fieldName) :
"returns True/False wether the collection has field K in it's schema. Use the dot notation for the nested fields: address.street"
"""returns True/False wether the collection has field K in it's schema. Use the dot notation for the nested fields: address.street"""
path = fieldName.split(".")
v = cls._fields
for k in path :
Expand All @@ -454,15 +458,18 @@ def fetchDocument(self, key, rawResults = False, rev = None) :
r = self.connection.session.get(url, params = {'rev' : rev})
else :
r = self.connection.session.get(url)
if (r.status_code - 400) < 0 :

if r.status_code < 400 :
if rawResults :
return r.json()
return self.documentClass(self, r.json())
elif r.status_code == 404 :
raise DocumentNotFoundError("Unable to find document with _key: %s" % key, r.json())
else :
raise KeyError("Unable to find document with _key: %s" % key, r.json())
raise Exception("Unable to find document with _key: %s, response: %s" % key, r.json())

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

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

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

def truncate(self) :
"deletes every document in the collection"
"""deletes every document in the collection"""
return self.action('PUT', 'truncate')

def empty(self) :
"alias for truncate"
"""alias for truncate"""
return self.truncate()

def load(self) :
"loads collection in memory"
"""loads collection in memory"""
return self.action('PUT', 'load')

def unload(self) :
"unloads collection from memory"
"""unloads collection from memory"""
return self.action('PUT', 'unload')

def revision(self) :
Expand All @@ -588,7 +595,7 @@ def figures(self) :
return self.action('GET', 'figures')

def getType(self) :
"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"
"""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"""
if self.type == CONST.COLLECTION_DOCUMENT_TYPE :
return "document"
elif self.type == CONST.COLLECTION_EDGE_TYPE :
Expand All @@ -597,7 +604,7 @@ def getType(self) :
raise ValueError("The collection is of Unknown type %s" % self.type)

def getStatus(self) :
"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"
"""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"""
if self.status == CONST.COLLECTION_LOADING_STATUS :
return "loading"
elif self.status == CONST.COLLECTION_LOADED_STATUS :
Expand All @@ -619,7 +626,7 @@ def __repr__(self) :
return "ArangoDB collection name: %s, id: %s, type: %s, status: %s" % (self.name, self.id, self.getType(), self.getStatus())

def __getitem__(self, key) :
"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)"
"""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)"""
if self.documentCache is None :
return self.fetchDocument(key, rawResults = False)
try :
Expand All @@ -638,17 +645,17 @@ def __contains__(self) :
return False

class SystemCollection(Collection) :
"for all collections with isSystem = True"
"""for all collections with isSystem = True"""
def __init__(self, database, jsonData) :
Collection.__init__(self, database, jsonData)

class Edges(Collection) :
"The default edge collection. All edge Collections must inherit from it"
"""The default edge collection. All edge Collections must inherit from it"""

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

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

def createEdge(self) :
"Create an edge populated with defaults"
"""Create an edge populated with defaults"""
return self.createDocument()

def createEdge_(self, initValues = {}) :
"Create an edge populated with initValues"
def createEdge_(self, initValues = None) :
"""Create an edge populated with initValues"""
if not initValues:
initValues = {}
return self.createDocument_(initValues)

def getInEdges(self, vertex, rawResults = False) :
"An alias for getEdges() that returns only the in Edges"
"""An alias for getEdges() that returns only the in Edges"""
return self.getEdges(vertex, inEdges = True, outEdges = False, rawResults = rawResults)

def getOutEdges(self, vertex, rawResults = False) :
"An alias for getEdges() that returns only the out Edges"
"""An alias for getEdges() that returns only the out Edges"""
return self.getEdges(vertex, inEdges = False, outEdges = True, rawResults = rawResults)

def getEdges(self, vertex, inEdges = True, outEdges = True, rawResults = False) :
Expand Down Expand Up @@ -715,6 +724,3 @@ def getEdges(self, vertex, inEdges = True, outEdges = True, rawResults = False)
return data["edges"]
else :
raise CreationError("Unable to return edges for vertex: %s" % vId, data)



27 changes: 22 additions & 5 deletions pyArango/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def createCollection(self, className = 'Collection', **colProperties) :
colProperties = dict(colClass._properties)
except AttributeError :
colProperties = {}

if className != 'Collection' and className != 'Edges' :
colProperties['name'] = className
else :
Expand Down Expand Up @@ -126,7 +126,7 @@ def fetchDocument(self, _id) :
sid = _id.split("/")
return self[sid[0]][sid[1]]

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

_checkCollectionList(graphClass._orphanedCollections)

options = {}
if numberOfShards:
options['numberOfShards'] = numberOfShards
if smartGraphAttribute:
options['smartGraphAttribute'] = smartGraphAttribute

payload = {
"name": name,
"edgeDefinitions": ed,
"orphanCollections": graphClass._orphanedCollections
}

payload = json.dumps(payload, default=str)
if isSmart :
payload['isSmart'] = isSmart

if options:
payload['options'] = options

payload = json.dumps(payload)

r = self.connection.session.post(self.graphsURL, data = payload)
data = r.json()

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

def validateAQLQuery(self, query, bindVars = {}, options = {}) :
def validateAQLQuery(self, query, bindVars = None, options = None) :
"returns the server answer is the query is valid. Raises an AQLQueryError if not"
if bindVars is None :
bindVars = {}
if options is None :
options = {}
payload = {'query' : query, 'bindVars' : bindVars, 'options' : options}
r = self.connection.session.post(self.cursorsURL, data = json.dumps(payload, default=str))
data = r.json()
Expand Down Expand Up @@ -225,7 +242,7 @@ def transaction(self, collections, action, waitForSync = False, lockTimeout = No

data = r.json()

if (r.status_code == 200 or r.status_code == 201 or r.status_code == 202) and not data["error"] :
if (r.status_code == 200 or r.status_code == 201 or r.status_code == 202) and not data.get("error") :
return data
else :
raise TransactionError(data["errorMessage"], action, data)
Expand Down
28 changes: 14 additions & 14 deletions pyArango/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,16 @@ def __repr__(self) :
class Document(object) :
"""The class that represents a document. Documents are meant to be instanciated by collections"""

def __init__(self, collection, jsonFieldInit = {}) :
self.typeName = "ArangoDoc"
def __init__(self, collection, jsonFieldInit = None) :
if jsonFieldInit is None :
jsonFieldInit = {}
self.privates = ["_id", "_key", "_rev"]
self.reset(collection, jsonFieldInit)
self.typeName = "ArangoDoc"

def reset(self, collection, jsonFieldInit = {}) :
def reset(self, collection, jsonFieldInit = None) :
if not jsonFieldInit:
jsonFieldInit = {}
"""replaces the current values in the document by those in jsonFieldInit"""
self.collection = collection
self.connection = self.collection.connection
Expand Down Expand Up @@ -383,23 +387,19 @@ def __repr__(self) :

class Edge(Document) :
"""An Edge document"""
def __init__(self, edgeCollection, jsonFieldInit = {}) :
def __init__(self, edgeCollection, jsonFieldInit = None) :
if not jsonFieldInit:
jsonFieldInit = {}

self.typeName = "ArangoEdge"
self.privates = ["_id", "_key", "_rev", "_from", "_to"]
self.reset(edgeCollection, jsonFieldInit)

def reset(self, edgeCollection, jsonFieldInit = {}) :
def reset(self, edgeCollection, jsonFieldInit = None) :
if jsonFieldInit is None:
jsonFieldInit = {}
Document.reset(self, edgeCollection, jsonFieldInit)

# def setPrivates(self, fieldDict) :
# """set _id, _key, _rev, _from, _to"""
# super(Edge, self).setPrivates(fieldDict)
# if "_from" in fieldDict :
# self._from = fieldDict["_from"]

# if "_to" in fieldDict :
# self._to = fieldDict["_to"]

def links(self, fromVertice, toVertice, **edgeArgs) :
"""
An alias to save that updates the _from and _to attributes.
Expand Down
7 changes: 4 additions & 3 deletions pyArango/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ def __init__(self, request, database, rawResults) :

self.rawResults = rawResults
self.response = request.json()
if self.response["error"] and self.response["errorMessage"] != "no match" :
if self.response.get("error") and self.response["errorMessage"] != "no match" :
raise QueryError(self.response["errorMessage"], self.response)

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

if "hasMore" in self.response and self.response["hasMore"] :
self.cursor = RawCursor(self.database, self.id)
cursor_id = self.response.get("id","")
self.cursor = RawCursor(self.database, cursor_id)
else :
self.cursor = None
elif request.status_code == 404 :
Expand Down
3 changes: 1 addition & 2 deletions pyArango/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ def test_bulk_import_exception(self):
usersCollection.importBulk(users, onDuplicate="error", complete=True)
self.assertEqual(usersCollection.count(), 0)

# @unittest.skip("stand by")

# @unittest.skip("stand by")
def test_bulk_import_error_return_value(self):
usersCollection = self.db.createCollection(name="users")
nbUsers = 2
Expand Down
Loading

0 comments on commit f1e6ae3

Please sign in to comment.