Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/bulk import #205

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
716165f
added support for satellite graphs, general graphs do now support rep…
hkernbach Mar 12, 2020
c7c82fd
added satellite graphs example - readme
hkernbach Mar 12, 2020
26ab1e0
format
hkernbach Mar 12, 2020
7f65a87
Add JSON Schema validation example.
ObiWahn Mar 13, 2020
a06c083
remove writeConcern parameter in a satellite graph as it will be set …
hkernbach Mar 18, 2020
18056ef
Merge pull request #176 from ObiWahn/schema-validation-example
tariqdaouda Mar 21, 2020
6cb241d
Merge pull request #175 from hkernbach/master
tariqdaouda Mar 21, 2020
70ba9d7
Merge branch 'master' of github.com:tariqdaouda/pyArango
tariqdaouda Mar 24, 2020
2e18f66
Merge branch 'dev'
tariqdaouda Mar 24, 2020
3c33c92
Fix typo in example.
joerg84 Mar 30, 2020
bc49b31
Merge pull request #180 from ArangoDB-Community/example_typo
tariqdaouda Mar 30, 2020
71d901c
Fix social example
gitcarbs Mar 30, 2020
ce103bd
Added missing COL.
tariqdaouda Mar 31, 2020
7cb97df
change "validation" attribute name to "schema"
jsteemann Apr 22, 2020
49c1375
Merge pull request #185 from jsteemann/patch-1
tariqdaouda May 10, 2020
8605799
Merge pull request #182 from gitcarbs/master
tariqdaouda May 10, 2020
4010fd2
fix small typos in readme
jsteemann Sep 17, 2020
3dbe242
fix some typos in CHANGELOG.rst
jsteemann Sep 17, 2020
4a205db
add simple driver test wrapper script
dothebart Sep 22, 2020
d4c8076
Merge pull request #194 from ArangoDB-Community/add-arango-testing-in…
dothebart Oct 30, 2020
4bed6b8
Merge pull request #193 from jsteemann/bug-fix/changelog-typos
dothebart Oct 30, 2020
92942b3
Merge pull request #192 from jsteemann/bug-fix/readme-typos
dothebart Oct 30, 2020
dab72f3
Fix __contains__ method. Missing self prefix
Alexsaphir Nov 23, 2020
77c7a03
TravisCi Badge
Alexsaphir Nov 23, 2020
4652f21
Fix warning : Default argument value is mutable
Alexsaphir Nov 23, 2020
956eab0
Add __contains__ method to Database
Alexsaphir Nov 23, 2020
43fb38e
Add doc string and remove hint
Alexsaphir Nov 23, 2020
77cdc4e
Merge pull request #200 from Alexsaphir/feature-db-contains
tariqdaouda Apr 23, 2021
db758bf
Merge branch 'dev'
tariqdaouda May 5, 2021
ba6811b
Change error checking to status_code validation
cpurules Jun 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@

1.3.4
=====
* Bugfix: Query iterrator now returns all elements instead of a premature empty list
* Bugfix: Query iterator now returns all elements instead of a premature empty list
* Bugfix: Collection naming when using the arango's name argument
* New: Schema validation example
* New: Satelite graphs
* New: Satellite graphs

1.3.3
=====

* SSL certificate support
* More doc
* Fixed on_load schema validation
* Gevent, monkey patcjing breaks python's multi=processing. Removed grequests as the default, back to requests.
* Gevent, monkey patching breaks python's multi=processing. Removed grequests as the default, back to requests.
* Removed grequests and gevent as hard dependencies. Added explicit error messages, to prompt users can install them if needed.
* Jwauth is not in it's own file
* Jwauth is not in its own file
* Generic rest call to database support (action) for connection, database.
* Foxx support
* Tasks create, delete, fetch support
Expand All @@ -37,7 +37,7 @@
* Validation bug fixes
* New Numeric, Int, Bool, String, Enumeration, Range validators
* Fields can have default values
* When creationg a new document, Collection will serve one populated with defaults
* When creating a new document, Collection will serve one populated with defaults
* stastd support thx to: @dothebart
* properties definition in schema
* AQL errors now come with prints and line numbers for everyone's convenience
Expand All @@ -60,15 +60,15 @@
=====

* Added bulk import to connection
* Added binvars to explain
* Added bindvars to explain

1.2.8
=====

* BugFix: recursive field validation
* BugFix: fullCount option now works
* Length validator will raise a ValidationError if value has no length
* users can now specify custon json encoders
* users can now specify custom json encoders

1.2.7
=====
Expand Down Expand Up @@ -124,7 +124,7 @@
* Support for ArangoDB 3.X, pyArango no longer supports 2.X versions
* Support for authentication
* User support added
* Adedd AikidoSession to seemlessly manage request sessions
* Added AikidoSession to seemlessly manage request sessions
* AikidoSession stores basic stats about the requests
* AikidoSession detects 401 errors and notifies the user that authentication is required
* AikidoSession detects connection errors and notifies the user that arango is probably not running
Expand Down
66 changes: 55 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pyArango
:alt: downloads_week
:target: https://pepy.tech/project/pyarango/week

.. image:: https://travis-ci.org/tariqdaouda/pyArango.svg?branch=1.2.2
:target: https://travis-ci.org/tariqdaouda/pyArango
.. image:: https://travis-ci.com/Alexsaphir/pyArango.svg?branch=master
:target: https://travis-ci.com/github/Alexsaphir/pyArango
.. image:: https://img.shields.io/badge/python-2.7%2C%203.5-blue.svg
.. image:: https://img.shields.io/badge/arangodb-3.0-blue.svg

Expand All @@ -29,7 +29,7 @@ Key Features
pyArango is geared toward the developer. It's here to help to you develop really cool apps using ArangoDB, really fast.

- Light and simple interface
- Built-in validation of fields on seting or on saving
- Built-in validation of fields on setting or on saving
- Support for all index types
- Supports graphs, traversals and all types of queries
- Caching of documents with Insertions and Lookups in O(1)
Expand Down Expand Up @@ -79,7 +79,7 @@ Initialization and document saving
conn = Connection()

conn.createDatabase(name="test_db")
db = self.conn["test_db"] # all databases are loaded automatically into the connection and are accessible in this fashion
db = conn["test_db"] # all databases are loaded automatically into the connection and are accessible in this fashion
collection = db.createCollection(name="users") # all collections are also loaded automatically

# collection.delete() # self explanatory
Expand Down Expand Up @@ -159,9 +159,9 @@ from **Validator** and implement a **validate()** method:
}

_fields = {
'name': Field(validators=[VAL.NotNull(), String_val()]),
'anything': Field(),
'species': Field(validators=[VAL.NotNull(), VAL.Length(5, 15), String_val()])
'name': COL.Field(validators=[VAL.NotNull(), String_val()]),
'anything': COL.Field(),
'species': COL.Field(validators=[VAL.NotNull(), VAL.Length(5, 15), String_val()])
}

collection = db.createCollection('Humans')
Expand Down Expand Up @@ -189,17 +189,17 @@ In addition, you can also define collection properties_ (creation arguments for
}

_fields = {
'name': Field(validators=[VAL.NotNull(), String_val()]),
'anything': Field(),
'species': Field(validators=[VAL.NotNull(), VAL.Length(5, 15), String_val()])
'name': COL.Field(validators=[VAL.NotNull(), String_val()]),
'anything': COL.Field(),
'species': COL.Field(validators=[VAL.NotNull(), VAL.Length(5, 15), String_val()])
}

.. _properties: https://docs.arangodb.com/3.1/HTTP/Collection/Creating.html

A note on inheritence
----------------------

There is no inheritence of the "_validation" and "_fields" dictionaries.
There is no inheritance of the "_validation" and "_fields" dictionaries.
If a class does not fully define its own, the defaults will be automatically assigned to any missing value.

Creating Edges
Expand Down Expand Up @@ -310,6 +310,50 @@ to that document are also deleted:
# deleting one of them along with the edge
theGraph.deleteVertex(h2)

Creating a Satellite Graph
-----------------

If you want to benefit from the advantages of satellite graphs, you can also create them of course.
Please read the official ArangoDB Documentation for further technical information.

.. code:: python

from pyArango.connection import *
from pyArango.collection import Collection, Edges, Field
from pyArango.graph import Graph, EdgeDefinition

databaseName = "satellite_graph_db"

conn = Connection()

# Cleanup (if needed)
try:
conn.createDatabase(name=databaseName)
except Exception:
pass

# Select our "satellite_graph_db" database
db = conn[databaseName] # all databases are loaded automatically into the connection and are accessible in this fashion

# Define our vertex to use
class Humans(Collection):
_fields = {
"name": Field()
}

# Define our edge to use
class Friend(Edges):
_fields = {
"lifetime": Field()
}

# Here's how you define a Satellite Graph
class MySatelliteGraph(Graph) :
_edgeDefinitions = [EdgeDefinition("Friend", fromCollections=["Humans"], toCollections=["Humans"])]
_orphanedCollections = []

theSatelliteGraph = db.createSatelliteGraph("MySatelliteGraph")

Document Cache
--------------

Expand Down
10 changes: 5 additions & 5 deletions examples/createSocialGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ def __init__(self):
if self.db.hasGraph('social'):
raise Exception("The social graph was already provisioned! remove it first")

self.female = self.db.createCollection('Collection', "female")
self.male = self.db.createCollection('Collection', "male")
self.relation = self.db.createCollection('Edges', "relation")
self.female = self.db.createCollection(className='Collection', name='female')
self.male = self.db.createCollection(className='Collection', name='male')

self.relation = self.db.createCollection(className='Edges', name='relation')

g = self.db.createGraph("social")

a = g.createVertex('female', {"name": 'Alice', "_key": 'alice'});
Expand Down
50 changes: 50 additions & 0 deletions examples/json_schema_validation_in_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import sys
import time
from pyArango.connection import *
from pyArango.graph import *
from pyArango.collection import *

def main():
conn = Connection(username="", password="")
db = conn["_system"]
name = "pyArangoValidation"

schema = {
"rule" : {
"properties" : {
"value" : {
"type" : "number"
}
}
},
"level" : "strict",
"message" : "invalid document - schema validation failed!"
}

collection = None
if db.hasCollection(name):
db[name].delete() # drop
db.reloadCollections() # work around drop should reload...

collection = db.createCollection(
name = name,
schema = schema
)

try:
d = collection.createDocument()
d["value"] = "bar"
d.save()
except Exception as e:
print(e)

d = collection.createDocument()
d["value"] = 3
d.save()

print(collection.fetchAll())
return 0

if __name__ == "__main__":
sys.exit(main())
12 changes: 4 additions & 8 deletions pyArango/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,10 +789,8 @@ def bulkImport_json(self, filename, onDuplicate="error", formatType="auto", **pa
data = f.read()
r = self.connection.session.post(url, params = params, data = data)

try:
errorMessage = "At least: %d errors. The first one is: '%s'\n\n more in <this_exception>.data" % (len(data), data[0]["errorMessage"])
except KeyError:
raise UpdateError(data['errorMessage'], data)
if r.status_code != 201:
raise UpdateError('Unable to bulk import JSON', r)

def bulkImport_values(self, filename, onDuplicate="error", **params):
"""bulk import from a file repecting arango's json format"""
Expand All @@ -804,10 +802,8 @@ def bulkImport_values(self, filename, onDuplicate="error", **params):
data = f.read()
r = self.connection.session.post(url, params = params, data = data)

try:
errorMessage = "At least: %d errors. The first one is: '%s'\n\n more in <this_exception>.data" % (len(data), data[0]["errorMessage"])
except KeyError:
raise UpdateError(data['errorMessage'], data)
if r.status_code != 201:
raise UpdateError('Unable to bulk import values', r)

def truncate(self):
"""deletes every document in the collection"""
Expand Down
29 changes: 26 additions & 3 deletions pyArango/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def fetchDocument(self, _id):
sid = _id.split("/")
return self[sid[0]][sid[1]]

def createGraph(self, name, createCollections = True, isSmart = False, numberOfShards = None, smartGraphAttribute = None):
def createGraph(self, name, createCollections = True, isSmart = False, numberOfShards = None, smartGraphAttribute = None, replicationFactor = None, writeConcern = 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 Down Expand Up @@ -171,6 +171,10 @@ def _checkCollectionList(lst):
options['numberOfShards'] = numberOfShards
if smartGraphAttribute:
options['smartGraphAttribute'] = smartGraphAttribute
if replicationFactor:
options['replicationFactor'] = replicationFactor
if writeConcern:
options['writeConcern'] = writeConcern

payload = {
"name": name,
Expand All @@ -195,6 +199,9 @@ def _checkCollectionList(lst):
raise CreationError(data["errorMessage"], data)
return self.graphs[name]

def createSatelliteGraph(self, name, createCollections = True):
return self.createGraph(name, createCollections, False, None, None, "satellite", None);

def hasCollection(self, name):
"""returns true if the databse has a collection by the name of 'name'"""
return name in self.collections
Expand All @@ -217,10 +224,15 @@ def dropAllCollections(self):
self[collection_name].delete()
return

def AQLQuery(self, query, batchSize = 100, rawResults = False, bindVars = {}, options = {}, count = False, fullCount = False,
def AQLQuery(self, query, batchSize = 100, rawResults = False, bindVars = None, options = None, count = False, fullCount = False,
json_encoder = None, **moreArgs):
"""Set rawResults = True if you want the query to return dictionnaries instead of Document objects.
You can use **moreArgs to pass more arguments supported by the api, such as ttl=60 (time to live)"""
if bindVars is None:
bindVars = {}
if options is None:
options = {}

return AQLQuery(self, query, rawResults = rawResults, batchSize = batchSize, bindVars = bindVars, options = options, count = count, fullCount = fullCount,
json_encoder = json_encoder, **moreArgs)

Expand Down Expand Up @@ -452,8 +464,11 @@ def no_fetch_run(
return
raise AQLFetchError("No results should be returned for the query.")

def explainAQLQuery(self, query, bindVars={}, allPlans = False):
def explainAQLQuery(self, query, bindVars = None, allPlans = False):
"""Returns an explanation of the query. Setting allPlans to True will result in ArangoDB returning all possible plans. False returns only the optimal plan"""
if bindVars is None:
bindVars = {}

payload = {'query' : query, 'bindVars' : bindVars, 'allPlans' : allPlans}
request = self.connection.session.post(self.getExplainURL(), data = json.dumps(payload, default=str))
return request.json()
Expand Down Expand Up @@ -499,6 +514,14 @@ def transaction(self, collections, action, waitForSync = False, lockTimeout = No
def __repr__(self):
return "ArangoDB database: %s" % self.name

def __contains__(self, _id):
"""allows to check if _id:str is the id of an existing document"""
col, key = _id.split('/')
try:
return key in self[col]
except KeyError:
return False

def __getitem__(self, collectionName):
"""use database[collectionName] to get a collection from the database"""
try:
Expand Down
9 changes: 7 additions & 2 deletions pyArango/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ class DocumentStore(object):
"""Store all the data of a document in hierarchy of stores and handles validation.
Does not store private information, these are in the document."""

def __init__(self, collection, validators={}, initDct={}, patch=False, subStore=False, validateInit=False):
def __init__(self, collection, validators=None, initDct=None, patch=False, subStore=False, validateInit=False):
if validators is None:
validators = {}
if initDct is None:
initDct = {}

self.store = {}
self.patchStore = {}
self.collection = collection
Expand Down Expand Up @@ -131,7 +136,7 @@ def __dict__(self):
return dict(self.store) + dict(self.patchStore)

def __contains__(self, field):
return field in store
return field in self.store

def __getitem__(self, field):
"""Get an element from the store"""
Expand Down
4 changes: 3 additions & 1 deletion pyArango/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ def __init__(self, database, query, batchSize, bindVars, options, count, fullCou
except QueryError as e:
raise AQLQueryError( message = e.message, query = self.query, errors = e.errors)

def explain(self, bindVars={}, allPlans = False):
def explain(self, bindVars = None, allPlans = False):
"""Returns an explanation of the query. Setting allPlans to True will result in ArangoDB returning all possible plans. False returns only the optimal plan"""
if bindVars is None:
bindVars = {}
return self.database.explainAQLQuery(self.query, bindVars, allPlans)

def _raiseInitFailed(self, request):
Expand Down
10 changes: 10 additions & 0 deletions pyArango/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,16 @@ def test_document_fetch_by_key(self):
doc2 = collection.fetchDocument(doc._key)
self.assertEqual(doc._id, doc2._id)

def test_database_contains_id(self):
collection = self.db.createCollection(name="lala")
doc = collection.createDocument()
doc["name"] = 'iop'
doc.save()
result = doc["_id"] in self.db
self.assertTrue(result)
result = doc["_id"] + '1' in self.db
self.assertFalse(result)

# @unittest.skip("stand by")
def test_document_set_private_w_rest(self):
collection = self.db.createCollection(name = "lala")
Expand Down
Loading