diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16d3c8c..a7f9523 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,17 @@ +2.0.1 +===== + +* Fixed max retries for write conflicts + +2.0 +===== + +* changed the default value of reject_zero in NotNull from True to False +* added to_default function to reset a document to its default values +* fixed bug in default documents where default values could be overwritten +* default value for fields is now None +* defaual value for fields can now be a callable + 1.3.5 ===== diff --git a/pyArango/collection.py b/pyArango/collection.py index 1832cf9..9e74fff 100644 --- a/pyArango/collection.py +++ b/pyArango/collection.py @@ -118,8 +118,8 @@ def __repr__(self): class Field(object): """The class for defining pyArango fields.""" - def __init__(self, validators = None, default = ""): - """validators must be a list of validators""" + def __init__(self, validators = None, default = None): + """validators must be a list of validators. default can also be a callable""" if not validators: validators = [] self.validators = validators @@ -238,16 +238,6 @@ class Collection(with_metaclass(Collection_metaclass, object)): def __init__(self, database, jsonData): - def getDefaultDoc(fields, dct): - for k, v in fields.items(): - if isinstance(v, dict): - dct[k] = getDefaultDoc(fields[k], {}) - elif isinstance(v, Field): - dct[k] = v.default - else: - raise ValueError("Field '%s' is of invalid type '%s'" % (k, type(v)) ) - return dct - self.database = database self.connection = self.database.connection self.name = self.__class__.__name__ @@ -267,13 +257,30 @@ def getDefaultDoc(fields, dct): "fulltext" : {}, } self.indexes_by_name = {} - - self.defaultDocument = getDefaultDoc(self._fields, {}) + # self.defaultDocument = None #getDefaultDoc(self._fields, {}) self._isBulkInProgress = False self._bulkSize = 0 self._bulkCache = [] self._bulkMode = BulkMode.NONE + def getDefaultDocument(self, fields=None, dct=None): + if dct is None: + dct = {} + if fields is None: + fields = self._fields + + for k, v in fields.items(): + if isinstance(v, dict): + dct[k] = self.getDefaultDocument(fields[k], None) + elif isinstance(v, Field): + if callable(v.default): + dct[k] = v.default() + else : + dct[k] = v.default + else: + raise ValueError("Field '%s' is of invalid type '%s'" % (k, type(v)) ) + return dct + def getURL(self): return "%s/collection/%s" % (self.database.getURL(), self.name) @@ -316,7 +323,9 @@ def delete(self): def createDocument(self, initDict = None): """create and returns a completely empty document or one populated with initDict""" - res = dict(self.defaultDocument) + # res = dict(self.defaultDocument) + res = self.getDefaultDocument() + if initDict is not None: res.update(initDict) @@ -638,55 +647,6 @@ def validatePrivate(self, field, value): self._fields[field].validate(value) return True - # @classmethod - # def validateField(cls, fieldName, value): - # """checks if 'value' is valid for field 'fieldName'. If the validation is unsuccessful, raises a SchemaViolation or a ValidationError. - # for nested dicts ex: {address : { street: xxx} }, fieldName can take the form address.street - # """ - - # def _getValidators(cls, fieldName): - # path = fieldName.split(".") - # v = cls._fields - # for k in path: - # try: - # v = v[k] - # except KeyError: - # return None - # return v - - # field = _getValidators(cls, fieldName) - - # if field is None: - # if not cls._validation["allow_foreign_fields"]: - # raise SchemaViolation(cls, fieldName) - # else: - # return field.validate(value) - - # @classmethod - # def validateDct(cls, dct): - # "validates a dictionary. The dictionary must be defined such as {field: value}. If the validation is unsuccefull, raises an InvalidDocument" - # def _validate(dct, res, parentsStr=""): - # for k, v in dct.items(): - # if len(parentsStr) == 0: - # ps = k - # else: - # ps = "%s.%s" % (parentsStr, k) - - # if type(v) is dict: - # _validate(v, res, ps) - # elif k not in cls.arangoPrivates: - # try: - # cls.validateField(ps, v) - # except (ValidationError, SchemaViolation) as e: - # res[k] = str(e) - - # res = {} - # _validate(dct, res) - # if len(res) > 0: - # raise InvalidDocument(res) - - # return True - @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""" @@ -982,3 +942,4 @@ def __enter__(self): return self.coll def __exit__(self, type, value, traceback): self.coll._finalizeBatch(); + diff --git a/pyArango/connection.py b/pyArango/connection.py index 6b7c86a..ac6927d 100644 --- a/pyArango/connection.py +++ b/pyArango/connection.py @@ -14,6 +14,8 @@ from .ca_certificate import CA_Certificate +from json.decoder import JSONDecodeError + class JsonHook(object): """This one replaces requests' original json() function. If a call to json() fails, it will print a message with the request content""" def __init__(self, ret): @@ -50,11 +52,17 @@ def __call__(self, *args, **kwargs): kwargs["verify"] = self.verify try: - status_code = 1200 + do_retry = True retry = 0 - while status_code == 1200 and retry < self.max_conflict_retries : + while do_retry and retry < self.max_conflict_retries : ret = self.fct(*args, **kwargs) - status_code = ret.status_code + do_retry = ret.status_code == 1200 + try : + data = ret.json() + do_retry = do_retry or ("errorNum" in data and data["errorNum"] == 1200) + except JSONDecodeError: + pass + retry += 1 except: print ("===\nUnable to establish connection, perhaps arango is not running.\n===") diff --git a/pyArango/document.py b/pyArango/document.py index 560f43b..b0fd290 100644 --- a/pyArango/document.py +++ b/pyArango/document.py @@ -230,6 +230,10 @@ def reset(self, collection, jsonFieldInit = None, on_load_validation=False) : self.modified = True + def to_default(self): + """reset the document to the default values""" + self.reset(self.collection, self.collection.getDefaultDocument()) + def validate(self): """validate the document""" self._store.validate() diff --git a/pyArango/tests/tests.py b/pyArango/tests/tests.py index 822335c..69d3eb3 100644 --- a/pyArango/tests/tests.py +++ b/pyArango/tests/tests.py @@ -101,6 +101,28 @@ def deleteManyUsersBulk(self, collection, batchSize, skip, docs): count += 1 return count + # @unittest.skip("stand by") + def test_to_default(self): + class theCol(Collection): + _fields = { + 'address' : { + 'street' : Field(default="Paper street"), + }, + "name": Field(default = "Tyler Durden") + } + + col = self.db.createCollection("theCol") + doc = col.createDocument() + self.assertEqual(doc["address"]["street"], "Paper street") + self.assertEqual(doc["name"], "Tyler Durden") + doc["address"]["street"] = "North street" + doc["name"] = "Jon Snow" + self.assertEqual(doc["address"]["street"], "North street") + self.assertEqual(doc["name"], "Jon Snow") + doc.to_default() + self.assertEqual(doc["address"]["street"], "Paper street") + self.assertEqual(doc["name"], "Tyler Durden") + # @unittest.skip("stand by") def test_bulk_operations(self): (collection, docs) = self.createManyUsersBulk(55, 17) diff --git a/pyArango/validation.py b/pyArango/validation.py index 4bf2db5..8ab36d5 100644 --- a/pyArango/validation.py +++ b/pyArango/validation.py @@ -15,7 +15,7 @@ def __str__(self): class NotNull(Validator): """Checks that the Field has a non null value. False is not considered a Null Value""" - def __init__(self, reject_zero=True, reject_empty_string=True): + def __init__(self, reject_zero=False, reject_empty_string=True): self.reject_zero = reject_zero self.reject_empty_string = reject_empty_string diff --git a/setup.py b/setup.py index 2e0048a..d885f69 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='pyArango', - version='1.3.5', + version='2.0.1', description='An easy to use python driver for ArangoDB with built-in validation', long_description=long_description,