Skip to content

Commit 31f6845

Browse files
committed
Merge branch 'arangoV3'
2 parents 0cd9ea8 + 588b3af commit 31f6845

File tree

8 files changed

+743
-745
lines changed

8 files changed

+743
-745
lines changed

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
1.1.0
2+
======
3+
4+
* Support for ArangoDB 3.X, pyArango no longer supports 2.X versions
5+
* Support for anthentification
6+
* Adedd AikidoSession to seemlessly manage request sessions
7+
* AikidoSession stores basic stats about the requests
8+
* save() and patch() functions now empty _patchStore is succesfull
9+
* _from and _to are are no longer private object attribute, they are now accessed using ["_from"] and ["_to"].

pyArango/collection.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ def __init__(self, database, jsonData) :
232232
self.documentClass = Document
233233
self.indexes = {
234234
"primary" : {},
235-
"cap" : {},
236235
"hash" : {},
237236
"skiplist" : {},
238237
"geo" : {},
@@ -268,19 +267,6 @@ def createDocument(self, initValues = {}) :
268267
"create and returns a document"
269268
return self.documentClass(self, initValues)
270269

271-
def ensureCapConstraint(self, size, byteSize = None) :
272-
"""Ensures that there's a cap constraint in the collection, and returns it"""
273-
data = {
274-
"type" : "cap",
275-
"size" : size,
276-
}
277-
if byteSize is not None :
278-
data["byteSize"] = byteSize
279-
280-
ind = Index(self, creationData = data)
281-
self.indexes["cap"][ind.infos["id"]] = ind
282-
return ind
283-
284270
def ensureHashIndex(self, fields, unique = False, sparse = True) :
285271
"""Creates a hash index if it does not already exist, and returns it"""
286272
data = {
@@ -403,14 +389,6 @@ def fetchByExample(self, exampleDict, batchSize, rawResults = False, **queryArgs
403389
"exampleDict should be something like {'age' : 28}"
404390
return self.simpleQuery('by-example', rawResults, example = exampleDict, batchSize = batchSize, **queryArgs)
405391

406-
def fetchFirst(self, count, rawResults = False) :
407-
"""Returns the first document inserted in a collection"""
408-
return self.simpleQuery('first', rawResults = rawResults, count = count)
409-
410-
def fetchLast(self, count, rawResults = False) :
411-
"""Returns the last document inserted in a collection"""
412-
return self.simpleQuery('last', rawResults = rawResults, count = count)
413-
414392
def fetchFirstExample(self, exampleDict, rawResults = False) :
415393
"""exampleDict should be something like {'age' : 28}. returns only a single element but still in a SimpleQuery object.
416394
returns the first example found that matches the example"""
@@ -529,11 +507,9 @@ class Edges(Collection) :
529507
def __init__(self, database, jsonData) :
530508
"This one is meant to be called by the database"
531509
Collection.__init__(self, database, jsonData)
532-
self.arangoPrivates.extend(["_to", "_from"])
533510
self.documentClass = Edge
534-
self.documentsURL = "%s/edge" % (self.database.URL)
535-
self.edgesURL = "%ss/%s" % (self.documentsURL, self.name)
536-
511+
self.edgesURL = "%s/edges/%s" % (self.database.URL, self.name)
512+
537513
def createEdge(self, initValues = {}) :
538514
"alias for createDocument, both functions create an edge"
539515
return self.createDocument(initValues)

pyArango/connection.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,51 @@
44
from database import Database, DBHandle
55
from theExceptions import SchemaViolation, CreationError, ConnectionError
66

7+
class AikidoSession(object) :
8+
"""Magical Aikido being that you probably do not need to access directly that deflects every http request to requests in the most graceful way.
9+
It will also save basic stats on requests in it's attribute '.log'.
10+
"""
11+
12+
class Holder(object) :
13+
def __init__(self, session, fct) :
14+
self.session = session
15+
self.fct = fct
16+
17+
def __call__(self, *args, **kwargs) :
18+
if self.session.auth :
19+
kwargs["auth"] = self.session.auth
20+
return self.fct(*args, **kwargs)
21+
22+
def __init__(self, username, password) :
23+
if username and password :
24+
self.auth = (username, password)
25+
else :
26+
self.auth = None
27+
28+
self.session = requests.Session()
29+
self.log = {}
30+
self.log["nb_request"] = 0
31+
self.log["requests"] = {}
32+
33+
def __getattr__(self, k) :
34+
try :
35+
reqFct = getattr(object.__getattribute__(self, "session"), k)
36+
except :
37+
raise AttributeError("Attribute '%s' not found (no Aikido move available)" % k)
38+
39+
holdClass = object.__getattribute__(self, "Holder")
40+
log = object.__getattribute__(self, "log")
41+
log["nb_request"] += 1
42+
try :
43+
log["requests"][reqFct.__name__] += 1
44+
except :
45+
log["requests"][reqFct.__name__] = 1
46+
47+
return holdClass(self, reqFct)
48+
749
class Connection(object) :
850
"""This is the entry point in pyArango and direcltt handles databases."""
9-
def __init__(self, arangoURL = 'http://localhost:8529') :
51+
def __init__(self, arangoURL = 'http://localhost:8529', username=None, password=None) :
1052
self.databases = {}
1153
if arangoURL[-1] == "/" :
1254
self.arangoURL = url[:-1]
@@ -17,19 +59,21 @@ def __init__(self, arangoURL = 'http://localhost:8529') :
1759
self.databasesURL = '%s/database' % self.URL
1860

1961
self.session = None
20-
self.resetSession()
62+
self.resetSession(username, password)
2163
self.reload()
2264

23-
def resetSession(self) :
65+
def resetSession(self, username=None, password=None) :
2466
"""resets the session"""
25-
self.session = requests.Session()
67+
self.session = AikidoSession(username, password)
2668

2769
def reload(self) :
2870
"""Reloads the database list.
2971
Because loading a database triggers the loading of all collections and graphs within,
3072
only handles are loaded when this function is called. The full databases are loaded on demand when accessed
3173
"""
74+
3275
r = self.session.get(self.databasesURL)
76+
3377
data = r.json()
3478
if r.status_code == 200 and not data["error"] :
3579
self.databases = {}

pyArango/database.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,13 @@ def reloadCollections(self) :
3939
if r.status_code == 200 :
4040
self.collections = {}
4141

42-
for colData in data["collections"] :
42+
for colData in data["result"] :
4343
colName = colData['name']
4444
if colData['isSystem'] :
4545
colObj = COL.SystemCollection(self, colData)
4646
else :
4747
try :
4848
colClass = COL.getCollectionClass(colName)
49-
# if colName[0] != "_" :
5049
colObj = colClass(self, colData)
5150
except KeyError :
5251
if colData["type"] == COL.COLLECTION_EDGE_TYPE :
@@ -156,19 +155,13 @@ def _checkCollectionList(lst) :
156155
payload = json.dumps(payload)
157156
r = self.connection.session.post(self.graphsURL, data = payload)
158157
data = r.json()
159-
if r.status_code == 201 :
158+
159+
if r.status_code == 201 or r.status_code == 202 :
160160
self.graphs[name] = graphClass(self, data["graph"])
161161
else :
162162
raise CreationError(data["errorMessage"], data)
163163
return self.graphs[name]
164164

165-
# def _checkGraphCollections(self, edgeDefinitions, orphanCollections) :
166-
# for ed in edgeDefinitions :
167-
# checkList(ed["from"])
168-
# checkList(ed["to"])
169-
170-
# checkList(orphanCollections)
171-
172165
def hasCollection(self, name) :
173166
"""returns true if the databse has a collection by the name of 'name'"""
174167
return name in self.collections

pyArango/document.py

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ def save(self, waitForSync = False, **docArgs) :
8282
update = True
8383

8484
data = r.json()
85-
if (r.status_code == 201 or r.status_code == 202) and not data['error'] :
85+
86+
if (r.status_code == 201 or r.status_code == 202) and "error" not in data :
8687
if update :
8788
self._rev = data['_rev']
8889
else :
@@ -95,6 +96,8 @@ def save(self, waitForSync = False, **docArgs) :
9596

9697
self.modified = False
9798

99+
self._patchStore = {}
100+
98101
def forceSave(self, **docArgs) :
99102
"saves even if the document has not been modified since the last save"
100103
self.modified = True
@@ -125,20 +128,23 @@ def patch(self, keepNull = True, **docArgs) :
125128

126129
r = self.connection.session.patch(self.URL, params = params, data = payload)
127130
data = r.json()
128-
if (r.status_code == 201 or r.status_code == 202) and not data['error'] :
131+
if (r.status_code == 201 or r.status_code == 202) and "error" not in data :
129132
self._rev = data['_rev']
130133
else :
131134
raise UpdateError(data['errorMessage'], data)
132135

133136
self.modified = False
134137

138+
self._patchStore = {}
139+
135140
def delete(self) :
136141
"deletes the document from the database"
137142
if self.URL is None :
138143
raise DeletionError("Can't delete a document that was not saved")
139144
r = self.connection.session.delete(self.URL)
140145
data = r.json()
141-
if (r.status_code != 200 and r.status_code != 202) or data['error'] :
146+
147+
if (r.status_code != 200 and r.status_code != 202) or 'error' in data :
142148
raise DeletionError(data['errorMessage'], data)
143149
self.reset(self.collection)
144150

@@ -218,52 +224,37 @@ def reset(self, edgeCollection, jsonFieldInit = {}) :
218224
Document.reset(self, edgeCollection, jsonFieldInit)
219225
self.typeName = "ArangoEdge"
220226

221-
def setPrivates(self, jsonFieldInit) :
222-
try :
223-
self._from = jsonFieldInit["_from"]
224-
del(jsonFieldInit["_from"])
225-
self._to = jsonFieldInit["_to"]
226-
del(jsonFieldInit["_to"])
227-
except KeyError :
228-
self._from, self._to = None, None
229-
Document.setPrivates(self, jsonFieldInit)
230-
231227
def links(self, fromVertice, toVertice, **edgeArgs) :
232-
"An alias of save that works only for first saves. It will also trigger the saving of fromVertice and toVertice"
233-
if self.URL is not None :
234-
raise AttributeError("It appears that the edge has already been saved. You can now use save() and patch()")
235-
236-
fromVertice.save()
237-
toVertice.save()
238-
239-
self.save(fromVertice, toVertice, **edgeArgs)
240-
241-
def save(self, fromVertice = None, toVertice = None, **edgeArgs) :
242-
"""Works like Document's except that the first time you save an Edge you must specify the 'from' and 'to' vertices.
243-
There's also a links() function especially for first saves"""
244-
import types
245-
246-
if self.URL is None and (fromVertice is None or toVertice is None) :
247-
raise ValueError("The first time you save an Edge you must specify the 'from' and 'to' vertices")
228+
"""
229+
An alias to save that updates the _from and _to attributes.
230+
fromVertice and toVertice, can be either strings or documents. It they are unsaved documents, they will be automatically saved.
231+
"""
248232

249233
if fromVertice.__class__ is Document :
250-
edgeArgs["from"] = fromVertice._id
251-
self._from = edgeArgs["from"]
234+
if not fromVertice._id :
235+
fromVertice._id.save()
236+
237+
self["_from"] = fromVertice._id
252238
elif (type(fromVertice) is types.StringType) or (type(fromVertice) is types.UnicodeType) :
253-
edgeArgs["from"] = fromVertice
254-
self._from = edgeArgs["from"]
255-
else :
256-
if not self._from :
257-
raise ValueError("fromVertice must be either a Document or a String, got: %s" % fromVertice)
239+
self["_from"] = fromVertice
258240

259241
if toVertice.__class__ is Document :
260-
edgeArgs["to"] = toVertice._id
261-
self._to = edgeArgs["to"]
242+
if not toVertice._id :
243+
toVertice._id.save()
244+
245+
self["_to"] = toVertice._id
262246
elif (type(toVertice) is types.StringType) or (type(toVertice) is types.UnicodeType) :
263-
edgeArgs["to"] = toVertice
264-
self._to = edgeArgs["to"]
265-
else :
266-
if not self._to :
267-
raise ValueError("toVertice must be either a Document or a String, got: %s" % fromVertice)
247+
self["_to"] = toVertice
248+
249+
self.save(**edgeArgs)
250+
251+
def save(self, **edgeArgs) :
252+
"""Works like Document's except that you must specify '_from' and '_to' vertices before.
253+
There's also a links() function especially for first saves."""
254+
255+
import types
256+
257+
if "_from" not in self._store or "_to" not in self._store :
258+
raise AttributeError("You must specify '_from' and '_to' attributes before saving. You can also use the function 'links()'")
268259

269260
Document.save(self, **edgeArgs)

pyArango/graph.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,6 @@ def __init__(self, database, jsonInit) :
9292
raise KeyError("'%s' is not a valid edge collection" % de.name)
9393
self.definitions[de.name] = de
9494

95-
# for e in jsonInit["edgeDefinitions"] :
96-
# if e["collection"] not in self._edgeDefinitions :
97-
# raise CreationError("Collection '%s' is not mentioned in the definition of graph '%s'" % (e["collection"], self.__class__,__name__))
98-
# if e["from"] != self._edgeDefinitions[e["collection"]]["from"] :
99-
# vals = (e["collection"], self.__class__,__name__, self._edgeDefinitions[e["collection"]]["from"], e["from"])
100-
# raise CreationError("Edge definition '%s' of graph '%s' mismatch for 'from':\npython:%s\narangoDB:%s" % vals)
101-
# if e["to"] != self._edgeDefinitions[e["collection"]]["to"] :
102-
# vals = (e["collection"], self.__class__,__name__, self._edgeDefinitions[e["collection"]]["to"], e["to"])
103-
# raise CreationError("Edge definition '%s' of graph '%s' mismatch for 'to':\npython:%s\narangoDB:%s" % vals )
104-
# defs.append(e["collection"])
105-
106-
# if jsonInit["orphanCollections"] != self._orphanCollections :
107-
# raise CreationError("Orphan collection '%s' of graph '%s' mismatch:\npython:%s\narangoDB:%s" (e["collection"], self.__class__,__name__, self._orphanCollections, jsonInit["orphanCollections"]))
108-
10995
self.URL = "%s/%s" % (self.database.graphsURL, self._key)
11096

11197
def createVertex(self, collectionName, docAttributes, waitForSync = False) :
@@ -171,7 +157,7 @@ def delete(self) :
171157
"""deletes the graph"""
172158
r = self.connection.session.delete(self.URL)
173159
data = r.json()
174-
if not r.status_code == 200 or data["error"] :
160+
if r.status_code < 200 or r.status_code > 202 or data["error"] :
175161
raise DeletionError(data["errorMessage"], data)
176162

177163
def traverse(self, startVertex, **kwargs) :
@@ -192,7 +178,7 @@ def traverse(self, startVertex, **kwargs) :
192178

193179
r = self.connection.session.post(url, data = json.dumps(payload))
194180
data = r.json()
195-
if not r.status_code == 200 or data["error"] :
181+
if r.status_code < 200 or r.status_code > 202 or data["error"] :
196182
raise TraversalError(data["errorMessage"], data)
197183

198184
return data["result"]

0 commit comments

Comments
 (0)