Skip to content

Commit cfae343

Browse files
committed
Update parameters for method Collection.import_bulk
1 parent 711be8d commit cfae343

File tree

6 files changed

+189
-28
lines changed

6 files changed

+189
-28
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Features
4040

4141
- Clean, Pythonic interface
4242
- Lightweight
43-
- 95%+ ArangoDB REST API coverage
43+
- High ArangoDB REST API coverage
4444

4545
Compatibility
4646
=============

arango/client.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ def log_levels(self):
412412
"""Return the current logging levels.
413413
414414
.. note::
415-
416415
This method is only compatible with ArangoDB version 3.1+ only.
417416
418417
:return: the current logging levels
@@ -438,11 +437,9 @@ def set_log_levels(self, **kwargs):
438437
)
439438
440439
.. note::
441-
442440
Keys that are not valid logger names are simply ignored.
443441
444442
.. note::
445-
446443
This method is only compatible with ArangoDB version 3.1+ only.
447444
448445
:return: the new logging levels
@@ -611,7 +608,7 @@ def users(self):
611608
612609
.. note::
613610
Root privileges (i.e. access to the ``_system`` database) are
614-
required to use this method
611+
required to use this method.
615612
"""
616613
res = self._conn.get('/_api/user')
617614
if res.status_code not in HTTP_OK:
@@ -633,7 +630,7 @@ def user(self, username):
633630
634631
.. note::
635632
Root privileges (i.e. access to the ``_system`` database) are
636-
required to use this method
633+
required to use this method.
637634
"""
638635
res = self._conn.get('/_api/user/{}'.format(username))
639636
if res.status_code not in HTTP_OK:
@@ -661,7 +658,7 @@ def create_user(self, username, password, active=None, extra=None):
661658
662659
.. note::
663660
Root privileges (i.e. access to the ``_system`` database) are
664-
required to use this method
661+
required to use this method.
665662
"""
666663
data = {'user': username, 'passwd': password}
667664
if active is not None:
@@ -695,7 +692,7 @@ def update_user(self, username, password=None, active=None, extra=None):
695692
696693
.. note::
697694
Root privileges (i.e. access to the ``_system`` database) are
698-
required to use this method
695+
required to use this method.
699696
"""
700697
data = {}
701698
if password is not None:
@@ -734,7 +731,7 @@ def replace_user(self, username, password, active=None, extra=None):
734731
735732
.. note::
736733
Root privileges (i.e. access to the ``_system`` database) are
737-
required to use this method
734+
required to use this method.
738735
"""
739736
data = {'user': username, 'passwd': password}
740737
if active is not None:
@@ -768,7 +765,7 @@ def delete_user(self, username, ignore_missing=False):
768765
769766
.. note::
770767
Root privileges (i.e. access to the ``_system`` database) are
771-
required to use this method
768+
required to use this method.
772769
"""
773770
res = self._conn.delete('/_api/user/{user}'.format(user=username))
774771
if res.status_code in HTTP_OK:
@@ -788,7 +785,7 @@ def user_access(self, username):
788785
789786
.. note::
790787
Root privileges (i.e. access to the ``_system`` database) are
791-
required to use this method
788+
required to use this method.
792789
"""
793790
res = self._conn.get('/_api/user/{}/database'.format(username))
794791
if res.status_code in HTTP_OK:
@@ -808,7 +805,7 @@ def grant_user_access(self, username, database):
808805
809806
.. note::
810807
Root privileges (i.e. access to the ``_system`` database) are
811-
required to use this method
808+
required to use this method.
812809
"""
813810
res = self._conn.put(
814811
'/_api/user/{}/database/{}'.format(username, database),
@@ -831,7 +828,7 @@ def revoke_user_access(self, username, database):
831828
832829
.. note::
833830
Root privileges (i.e. access to the ``_system`` database) are
834-
required to use this method
831+
required to use this method.
835832
"""
836833
res = self._conn.put(
837834
'/_api/user/{}/database/{}'.format(username, database),

arango/collections/standard.py

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -888,36 +888,106 @@ def handler(res):
888888
return request, handler
889889

890890
@api_method
891-
def import_bulk(self, documents, halt_on_error=True, details=True):
891+
def import_bulk(self,
892+
documents,
893+
halt_on_error=None,
894+
details=True,
895+
from_prefix=None,
896+
to_prefix=None,
897+
overwrite=None,
898+
on_duplicate=None,
899+
sync=None):
892900
"""Insert multiple documents into the collection.
893901
894902
This is faster than :func:`~arango.collections.Collection.insert_many`
895903
but does not return as much information. Any ``"_id"`` and ``"_rev"``
896904
fields in **documents** are ignored.
897905
898-
:param documents: the list of the new documents to insert
906+
:param documents: the list of the new documents to insert in bulk
899907
:type documents: list
900-
:param halt_on_error: halt the whole import on a failure
908+
:param halt_on_error: halt the entire import on an error
901909
(default: ``True``)
902910
:type halt_on_error: bool
903911
:param details: if ``True``, the returned result will include an
904-
additional list of details error messages (default: ``True``)
912+
additional list of detailed error messages (default: ``True``)
905913
:type details: bool
914+
:param from_prefix: the string prefix to prepend to the ``"_from"``
915+
field of each edge document inserted. *This only works for edge
916+
collections.*
917+
:type from_prefix: str | unicode
918+
:param to_prefix: the string prefix to prepend to the ``"_to"`` field
919+
of each edge document inserted. *This only works for edge
920+
collections.*
921+
:type to_prefix: str | unicode
922+
:param overwrite: if ``True``, all existing documents in the collection
923+
are removed prior to the import. Indexes are still preserved.
924+
:type overwrite: bool
925+
:param on_duplicate: the action to take on unique key constraint
926+
violations. Possible values are:
927+
928+
.. code-block:: none
929+
930+
"error" : do not import the new documents and count them as
931+
errors (this is the default)
932+
933+
"update" : update the existing documents while preserving any
934+
fields missing in the new ones
935+
936+
"replace" : replace the existing documents with the new ones
937+
938+
"ignore" : do not import the new documents and count them as
939+
ignored, as opposed to counting them as errors
940+
941+
:type on_duplicate: str | unicode
942+
:param sync: wait for the operation to sync to disk
943+
:type sync: bool
906944
:returns: the result of the bulk import
907945
:rtype: dict
908946
:raises arango.exceptions.DocumentInsertError: if the documents cannot
909947
be inserted into the collection
948+
949+
.. note::
950+
Parameters **from_prefix** and **to_prefix** only work for edge
951+
collections. When the prefix is prepended, it is followed by a
952+
``"/"`` character. For example, prefix ``"foo"`` prepended to an
953+
edge document with ``"_from": "bar"`` will result in a new value
954+
``"_from": "foo/bar"``.
955+
956+
.. note::
957+
Parameter **on_duplicate** actions ``"update"``, ``"replace"``
958+
and ``"ignore"`` will work only when **documents** contain the
959+
``"_key"`` fields.
960+
961+
.. warning::
962+
Parameter **on_duplicate** actions ``"update"`` and ``"replace"``
963+
may fail on secondary unique key constraint violations.
910964
"""
965+
params = {
966+
'type': 'array',
967+
'collection': self._name,
968+
'complete': halt_on_error,
969+
'details': details,
970+
}
971+
if halt_on_error is not None:
972+
params['complete'] = halt_on_error
973+
if details is not None:
974+
params['details'] = details
975+
if from_prefix is not None:
976+
params['fromPrefix'] = from_prefix
977+
if to_prefix is not None:
978+
params['toPrefix'] = to_prefix
979+
if overwrite is not None:
980+
params['overwrite'] = overwrite
981+
if on_duplicate is not None:
982+
params['onDuplicate'] = on_duplicate
983+
if sync is not None:
984+
params['waitForSync'] = sync
985+
911986
request = Request(
912987
method='post',
913988
endpoint='/_api/import',
914989
data=documents,
915-
params={
916-
'type': 'array',
917-
'collection': self._name,
918-
'complete': halt_on_error,
919-
'details': details
920-
}
990+
params=params
921991
)
922992

923993
def handler(res):

arango/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = '3.6.0'
1+
VERSION = '3.7.0'

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Features
1616

1717
- Clean, Pythonic interface
1818
- Lightweight
19-
- 95%+ ArangoDB REST API coverage
19+
- High ArangoDB REST API coverage
2020

2121
Compatibility
2222
=============

tests/test_document.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
db = arango_client.create_database(db_name)
1919
col_name = generate_col_name(db)
2020
col = db.create_collection(col_name)
21+
edge_col_name = generate_col_name(db)
22+
edge_col = db.create_collection(edge_col_name, edge=True)
2123
geo_index = col.add_geo_index(['coordinates'])
2224
bad_col_name = generate_col_name(db)
2325
bad_col = db.collection(bad_col_name)
@@ -30,6 +32,13 @@
3032
test_docs = [doc1, doc2, doc3, doc4, doc5]
3133
test_doc_keys = [d['_key'] for d in test_docs]
3234

35+
edge1 = {'_key': '1', '_to': '1', '_from': '2'}
36+
edge2 = {'_key': '2', '_to': '2', '_from': '3'}
37+
edge3 = {'_key': '3', '_to': '3', '_from': '4'}
38+
edge4 = {'_key': '4', '_to': '4', '_from': '5'}
39+
edge5 = {'_key': '5', '_to': '5', '_from': '1'}
40+
test_edges = [edge1, edge2, edge3, edge4, edge5]
41+
3342

3443
def teardown_module(*_):
3544
arango_client.delete_database(db_name, ignore_missing=True)
@@ -1000,7 +1009,7 @@ def test_has():
10001009
bad_col.has('1')
10011010

10021011
with pytest.raises(DocumentInError):
1003-
'1' in bad_col
1012+
assert '1' in bad_col
10041013

10051014

10061015
def test_get():
@@ -1495,6 +1504,9 @@ def test_import_bulk():
14951504
result = col.import_bulk(test_docs)
14961505
assert result['created'] == 5
14971506
assert result['errors'] == 0
1507+
assert result['empty'] == 0
1508+
assert result['updated'] == 0
1509+
assert result['ignored'] == 0
14981510
assert 'details' in result
14991511
assert len(col) == 5
15001512
for doc in test_docs:
@@ -1505,10 +1517,13 @@ def test_import_bulk():
15051517
assert col[key]['coordinates'] == doc['coordinates']
15061518
col.truncate()
15071519

1508-
# Test import bulk without details
1509-
result = col.import_bulk(test_docs, details=False)
1520+
# Test import bulk without details and with sync
1521+
result = col.import_bulk(test_docs, details=False, sync=True)
15101522
assert result['created'] == 5
15111523
assert result['errors'] == 0
1524+
assert result['empty'] == 0
1525+
assert result['updated'] == 0
1526+
assert result['ignored'] == 0
15121527
assert 'details' not in result
15131528
assert len(col) == 5
15141529
for doc in test_docs:
@@ -1528,9 +1543,88 @@ def test_import_bulk():
15281543
result = col.import_bulk([doc2, doc2], halt_on_error=False)
15291544
assert result['created'] == 1
15301545
assert result['errors'] == 1
1546+
assert result['empty'] == 0
1547+
assert result['updated'] == 0
1548+
assert result['ignored'] == 0
15311549
assert len(col) == 1
15321550

15331551
# Test import bulk in missing collection
15341552
with pytest.raises(DocumentInsertError):
15351553
bad_col.import_bulk([doc3, doc4], halt_on_error=True)
15361554
assert len(col) == 1
1555+
1556+
# Test import bulk with overwrite
1557+
result = col.import_bulk([doc3, doc4], overwrite=True)
1558+
assert result['created'] == 2
1559+
assert result['errors'] == 0
1560+
assert result['empty'] == 0
1561+
assert result['updated'] == 0
1562+
assert result['ignored'] == 0
1563+
assert '1' not in col
1564+
assert '2' not in col
1565+
assert '3' in col
1566+
assert '4' in col
1567+
col.truncate()
1568+
1569+
# Test import bulk to_prefix and from_prefix
1570+
result = edge_col.import_bulk(
1571+
test_edges, from_prefix='foo', to_prefix='bar'
1572+
)
1573+
assert result['created'] == 5
1574+
assert result['errors'] == 0
1575+
assert result['empty'] == 0
1576+
assert result['updated'] == 0
1577+
assert result['ignored'] == 0
1578+
for edge in test_edges:
1579+
key = edge['_key']
1580+
assert key in edge_col
1581+
assert edge_col[key]['_from'] == 'foo/' + edge['_from']
1582+
assert edge_col[key]['_to'] == 'bar/' + edge['_to']
1583+
edge_col.truncate()
1584+
1585+
# Test import bulk on_duplicate actions
1586+
old_doc = {'_key': '1', 'foo': '2'}
1587+
new_doc = {'_key': '1', 'bar': '3'}
1588+
1589+
col.insert(old_doc)
1590+
result = col.import_bulk([new_doc], on_duplicate='error')
1591+
assert len(col) == 1
1592+
assert result['created'] == 0
1593+
assert result['errors'] == 1
1594+
assert result['empty'] == 0
1595+
assert result['updated'] == 0
1596+
assert result['ignored'] == 0
1597+
assert col['1']['foo'] == '2'
1598+
assert 'bar' not in col['1']
1599+
1600+
result = col.import_bulk([new_doc], on_duplicate='ignore')
1601+
assert len(col) == 1
1602+
assert result['created'] == 0
1603+
assert result['errors'] == 0
1604+
assert result['empty'] == 0
1605+
assert result['updated'] == 0
1606+
assert result['ignored'] == 1
1607+
assert col['1']['foo'] == '2'
1608+
assert 'bar' not in col['1']
1609+
1610+
result = col.import_bulk([new_doc], on_duplicate='update')
1611+
assert len(col) == 1
1612+
assert result['created'] == 0
1613+
assert result['errors'] == 0
1614+
assert result['empty'] == 0
1615+
assert result['updated'] == 1
1616+
assert result['ignored'] == 0
1617+
assert col['1']['foo'] == '2'
1618+
assert col['1']['bar'] == '3'
1619+
1620+
col.truncate()
1621+
col.insert(old_doc)
1622+
result = col.import_bulk([new_doc], on_duplicate='replace')
1623+
assert len(col) == 1
1624+
assert result['created'] == 0
1625+
assert result['errors'] == 0
1626+
assert result['empty'] == 0
1627+
assert result['updated'] == 1
1628+
assert result['ignored'] == 0
1629+
assert 'foo' not in col['1']
1630+
assert col['1']['bar'] == '3'

0 commit comments

Comments
 (0)