{ "info": { "author": "Krisztian Toth", "author_email": "tkrisztiana@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# Endorser\n\n[![codecov](https://codecov.io/gh/watsta/endorser/branch/master/graph/badge.svg)](https://codecov.io/gh/watsta/endorser)\n\nA lightweight data validation and converter package for Python 3.6+.\nIt's always better to work with a structured set of data instead of just a simple dictionary. This package provides an easy way to do the conversion seamlessly while it provides a set of tools to validate the data.\nThe main purpose of this package is to create structured data from unstructured types while validating it.\n\n```Python\nfrom endorser import DocumentConverter\nfrom endorser import Schema\nfrom endorser import min_size\n\nclass Address(Schema):\n zip_code: str\n house_number: int\n addition: str = None\n\nclass User(Schema):\n email: str\n username: str\n firstname: str = None # assigning None as default makes it optional during instantiation\n address: Address = None # nest Schema classes\n\n @min_size(5)\n def validate_username(self, username):\n \"\"\"Validates the username field, it has to be at least 5 chars long\"\"\"\n return username\n\ndata = {\n \"email\": \"example@email.com\",\n \"username\": \"krisz\",\n \"address\": {\n \"zip_code\": \"6757\",\n \"house_number\": 12,\n \"addition\": \"A1\"\n }\n}\n\nconverter = DocumentConverter()\nuser = converter.convert(data, User) # converts the data dictionary to a User object\nassert type(user) is User\nassert user.email is \"example@email.com\"\nassert type(user.address) is Address\nassert user.address.zip_code is \"6757\"\n\n```\n\n## Install\nEndorser is hosted on PyPI, you can install it via pip:\n```\npip install endorser\n```\nTo run the tests:\n```\npython setup.py install\npython setup.py test\n```\nEndorser doesn't have any dependencies outside of pytest and pytest-runner.\n\n## Features\n\n### endorser.Schema\nBase class for documents. \n* Must not be instantiated directly\n* Every attribute must be type hinted\n* As of now, supported type hints are the primivites, list, dict, typing.List and subclasses of Schema\n* Every subclass of `Schema` must be considered as final and immutable\n```Python\nclass User(Schema):\n email: str\n username: str\n firstname: str = None # assigning None as default makes it optional\n age: int = 0 # assigning anything will be used as default value\n address: Address = None # must be an instance of Schema\n```\nNote that it's possible for every attribute to have `None` as it's value, the default `None` only means that the value can be omitted from the document. If you want to make sure that the value cannot be `None`, apply the `@validator.not_none` decorator:\n```Python\nclass User(Schema):\n email: str\n username: str = None\n\nuser = User(email=\"some@email.com\") # valid, as username can be omitted\nuser = User(email=None) # valid, as email can have the value None\n\n ...\n from endorser.validator import not_none\n\n @not_none\n def validate_email(self, email):\n return email\n\nuser = User(email=None) # not valid, as it's both mandatory and cannot be None\n```\n\n### Validation\nYou can validate `Schema` objects with following this convention:\n```Python\nfrom endorser import min_size\n\nclass SomeDocument(Schema):\n some_prop: str\n\n @min_size(5) # has to be at least 5 chars long\n def validate_some_prop(self, value):\n return value\n\n```\nEvery validation method has to start with the `validate_` prefix followed by the name of the property. The value argument is the value which will be set during instantiation. The method has to return the value as we set this value on the object.\nYou can see all validation methods in the `endorser.validator` package.\n\n### Custom validation\nYou can either create a new decorator and apply it on the validator (for examples see the `endorser.validator` package) or apply the validation on the validation method itself.\n```Python\nfrom endorser.error import construct_error\n\nclass SomeDocument(Schema):\n some_prop: str\n\n @some_custom_validator # apply custom decorators\n def validate_some_prop(self, value):\n for c in value:\n if c is \" \":\n self.instance_errors.append(construct_error(\n \"some_prop\", \"cannot contain spaces\"))\n return value # make sure to always return the value\n```\n\n### Alter values\nIt's possible to alter the value of `Schema` objects during validation:\n```Python\nimport uuid\nfrom endorser import valid_uuid\n\nclass User(Schema):\n id: uuid.UUID\n email: str\n username: str = None\n\n @valid_uuid # ensures that the ID is a valid UUID\n def validate_id(self, id): \n return uuid.UUID(id)\n\nuser = User(id=\"7b4f95e3-4fbe-4f94-838f-c34950240274\", \n email=\"some@email.com\")\nassert isinstance(user.id, uuid.UUID)\n```\nYou can also create custom decorators to modify property values. Note that we hinted the `id` property to be of type `uuid.UUID` but we instantiate it with a string value. You are responsible to return the correct value type which you defined on the `Schema` class.\n\n### Instantiation\nYou have to use keyword arguments to instantiate a `Schema` object:\n```Python\nuser = User(email=\"some@email.com\", username=\"krisz\")\n```\n\nYou can set the `_allow_unknown` property on any `Schema` object to allow unknown properties:\n```Python\nuser = User(_allow_unknown=True, email=\"some@email.com\", unknown_prop=\"any value\")\nassert user.unknown_prop == \"any value\"\n```\n\nValidation happens during the instantiation of the `Schema` object. Note that there aren't any exception raised, you have to check if there were any errors yourself:\n```Python\nimport uuid\nfrom endorser import valid_uuid\n\nclass User(Schema):\n id: uuid.UUID\n email: str\n username: str = None\n\n @valid_uuid # ensures that the ID is a valid UUID\n def validate_id(self, id): \n return uuid.UUID(id)\n\nuser = User(id=\"invalid-uuid\", \n email=\"some@email.com\")\nassert user.id == \"invalid-uuid\"\nif user.instance_errors:\n for error in user.instance_errors:\n print(\"invalid value for property %s: %s\" % (error[\"field\"], error[\"error\"]))\n```\nYou can use the `obj.instance_errors` property to check for errors on the instance and `obj.doc_errors` to check for validation errors on the whole document. This means if you have nested `Schema` objects, this property will return every error on every object from the root object:\n```Python\nfrom endorser import Schema\nfrom endorser.validator import min_size\n\nclass Address(Schema):\n zip_code: str\n house_number: int\n addition: str = None\n\nclass User(Schema):\n email: str\n username: str\n firstname: str = None \n address: Address = None\n\n @min_size(5)\n def validate_username(self, username):\n return username\n\nuser = User(email=\"some@email.com\", username=\"Joe\", \n address=Address(zip_code=\"67ZZ\", \n house_number=\"invalid_type\")) # validation fails on username and house_number\nassert len(user.instance_errors) == 1\nassert len(user.address.instance_errors) == 1\nassert len(user.doc_errors) == 2\n```\n\n### DocumentConverter\nThe DocumentConverter class is used to build structured data from a document. A document can either be a dictionary or a list of dictionaries. The DocumentConverter uses the `Schema` class to validate and build the objects from the document.\n```Python\nfrom endorser import DocumentConverter\nfrom endorser import Schema\nfrom endorser.validator import min_size\n\nclass Address(Schema):\n zip_code: str\n house_number: int\n addition: str = None\n\nclass User(Schema):\n email: str\n username: str\n firstname: str = None\n address: Address = None\n\n @min_size(5)\n def validate_username(self, username):\n \"\"\"Validates the username field, it has to be at least 5 chars long\"\"\"\n return username\n\ndata = {\n \"email\": \"example@email.com\",\n \"username\": \"krisz\",\n \"address\": {\n \"zip_code\": \"6757\",\n \"house_number\": 12,\n \"addition\": \"A1\"\n }\n}\n\nconverter = DocumentConverter()\nuser = converter.convert(data, User)\nassert type(user) is User\nassert user.email is \"example@email.com\"\nassert type(user.address) is Address\nassert user.address.zip_code is \"6757\"\n```\nThe `DocumentConverter#convert` method raises a `ConversionError` if validation fails. It holds the error messages in the `ConversionError.errors` list.\nYou can pass the `allow_unknown=True` property to the `convert` method to allow unknown properties:\n```Python\nclass SomeClass(Schema):\n prop: str\ndata = {\n \"prop\": \"some property\",\n \"unknown_prop\": \"not defined on the class\"\n}\n\nconverter = DocumentConverter()\nsome_obj = converter.convert(data, SomeClass, allow_unknown=True)\nassert some_obj.unknown_prop == \"not defined on the class\"\n```\nYou can also pass a list of objects to the converter:\n```Python\ndata = [{\n \"prop\": \"a property\"\n }, {\n \"prop\": \"another property\"\n}]\n\nlist_of_objs = converter.convert(data, List[SomeClass])\nassert type(list_of_objs) is list\nassert type(list_of_objs[0]) is SomeClass\nassert len(list_of_objs) == 2\n```\n\n### Examples\nFor more examples see the `test.example` package.\n\n### Limitations\n* Only works on Python 3.6+\n* Currently supported types are all primitives, list, dict and typing.List, and of course other Schema objects as well\n* Classes which inherit from Schema are effectively final, you must not inherit from them\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/watsta/endorser", "keywords": "object validator validate converter convert", "license": "", "maintainer": "", "maintainer_email": "", "name": "endorser", "package_url": "https://pypi.org/project/endorser/", "platform": "", "project_url": "https://pypi.org/project/endorser/", "project_urls": { "Homepage": "https://github.com/watsta/endorser" }, "release_url": "https://pypi.org/project/endorser/0.24/", "requires_dist": null, "requires_python": "", "summary": "Annotation based python object validator", "version": "0.24" }, "last_serial": 4466155, "releases": { "0.13": [ { "comment_text": "", "digests": { "md5": "f4a591747aa82a7a2e542d886feff7ab", "sha256": "23358bc7c79731c8a2c5a33bccf57fc5a7a960671eac1ad89c6fde1dd33b9fbf" }, "downloads": -1, "filename": "endorser-0.13-py3.6.egg", "has_sig": false, "md5_digest": "f4a591747aa82a7a2e542d886feff7ab", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": null, "size": 27561, "upload_time": "2018-07-26T15:39:33", "url": "https://files.pythonhosted.org/packages/f5/ec/e87a1eaaace2a747e719a51831164448c5d5604bcf5a583bd38af3a8ad99/endorser-0.13-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "3ddf3dfa29c9f9bef73d594eb7978beb", "sha256": "15ba2eb803ab2b8983a65921cb871d4c43704e9396b3ae98d1d6ed40eb21ec3d" }, "downloads": -1, "filename": "endorser-0.13-py3-none-any.whl", "has_sig": false, "md5_digest": "3ddf3dfa29c9f9bef73d594eb7978beb", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 13000, "upload_time": "2018-07-26T15:39:31", "url": "https://files.pythonhosted.org/packages/15/13/72889743f392a3774de0077ffaad36af9b352a622956ff7d3e64aa1bf954/endorser-0.13-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8a8c012367f37ee2ac7651c9a373a9b5", "sha256": "54b54562f90f0c7d1a837be1272419e8bb378326889e5a31df25d95bc0113cb6" }, "downloads": -1, "filename": "endorser-0.13.tar.gz", "has_sig": false, "md5_digest": "8a8c012367f37ee2ac7651c9a373a9b5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12387, "upload_time": "2018-07-26T15:39:34", "url": "https://files.pythonhosted.org/packages/93/bc/80ad77a643c3a0848e3e15218ae5f1b31a5d4cc38a3c54fb6ccbfa670099/endorser-0.13.tar.gz" } ], "0.14": [ { "comment_text": "", "digests": { "md5": "a33e28f08ea89a71cb4c1e9f66dab376", "sha256": "03443660656de16de15e8b3766a4a9c9a438bda604dc1617320309141134b973" }, "downloads": -1, "filename": "endorser-0.14-py3-none-any.whl", "has_sig": false, "md5_digest": "a33e28f08ea89a71cb4c1e9f66dab376", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 13468, "upload_time": "2018-07-26T16:48:02", "url": "https://files.pythonhosted.org/packages/8f/dc/203d974713bc2073e03296568dcbc1a34a7f91acbd7fcbc26310dc5ba674/endorser-0.14-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4b43dfde65a63df6a5a7663ba9fe089b", "sha256": "28b217a46e86dbc9087264c33bd5698584dd260652feff8911a8b6383ad21f3d" }, "downloads": -1, "filename": "endorser-0.14.tar.gz", "has_sig": false, "md5_digest": "4b43dfde65a63df6a5a7663ba9fe089b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12241, "upload_time": "2018-07-26T16:48:05", "url": "https://files.pythonhosted.org/packages/45/e1/9f64d54276713eec8b4126c7a77d1292f0a1c861eeb6f399b9cacc3be30e/endorser-0.14.tar.gz" } ], "0.20": [ { "comment_text": "", "digests": { "md5": "55005ec45b09d3a93da614144ebfe03b", "sha256": "f3aecaef1d6ed7658953cdb0e7e9677195ec55b264ea4b3904b72332207cb670" }, "downloads": -1, "filename": "endorser-0.20-py3-none-any.whl", "has_sig": false, "md5_digest": "55005ec45b09d3a93da614144ebfe03b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 14466, "upload_time": "2018-10-30T15:56:55", "url": "https://files.pythonhosted.org/packages/cd/51/0ec554257c435a1df4b081f13cbfe062acbbf53b0f20225f9851d0e11539/endorser-0.20-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "265a14b0ba7a4b12270678cd24abd00a", "sha256": "718fa4f23708f8ab1bca14445ed43e6f146c08495707101b5b502b64df27048b" }, "downloads": -1, "filename": "endorser-0.20.tar.gz", "has_sig": false, "md5_digest": "265a14b0ba7a4b12270678cd24abd00a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10547, "upload_time": "2018-10-30T15:56:57", "url": "https://files.pythonhosted.org/packages/00/90/8383bc32a3e568ea2c83f97059f3abcb0f549af91c93e3bbf312437dc0a8/endorser-0.20.tar.gz" } ], "0.22": [ { "comment_text": "", "digests": { "md5": "38b5536eec76d87dd237f8bb8864d8d5", "sha256": "07cd08849c9c9bcfab846eb807d74d0405970d176bbdb9205888d90d029c3b6c" }, "downloads": -1, "filename": "endorser-0.22-py3-none-any.whl", "has_sig": false, "md5_digest": "38b5536eec76d87dd237f8bb8864d8d5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 14558, "upload_time": "2018-10-30T16:57:08", "url": "https://files.pythonhosted.org/packages/46/23/2c5dd274792681477d9286a93d4745ab5c876d415046b817bf775cfa058e/endorser-0.22-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b1ce8a36d9639439a79768e27b5e6aa4", "sha256": "a19912526eeac6ab8b209a0856f0a39cbc273510d46ce72c103e68079561cab3" }, "downloads": -1, "filename": "endorser-0.22.tar.gz", "has_sig": false, "md5_digest": "b1ce8a36d9639439a79768e27b5e6aa4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10635, "upload_time": "2018-10-30T16:57:10", "url": "https://files.pythonhosted.org/packages/ed/24/e4937a0c46e52e46115f04325ec5c9293fbe7db67ff1bcad25ee8f2a6c33/endorser-0.22.tar.gz" } ], "0.23": [ { "comment_text": "", "digests": { "md5": "5e8a441abceedcc1a8d99450bf2c69e7", "sha256": "2d87720f0c4e9340d852e0781783f9a4fecc57f81d066da08c66349735c7013a" }, "downloads": -1, "filename": "endorser-0.23-py3-none-any.whl", "has_sig": false, "md5_digest": "5e8a441abceedcc1a8d99450bf2c69e7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12915, "upload_time": "2018-10-30T18:42:22", "url": "https://files.pythonhosted.org/packages/20/e2/0ea4413c4b5633696224665d6e4b236b1343aacb0560bf46114a7094d8a0/endorser-0.23-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d7bb6fecfa249127feb88c71cf6900c7", "sha256": "2e2e8c3b55d32acfdd5a3717e48e6a11a17276ee78b885a6b212f28c65e4a413" }, "downloads": -1, "filename": "endorser-0.23.tar.gz", "has_sig": false, "md5_digest": "d7bb6fecfa249127feb88c71cf6900c7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10943, "upload_time": "2018-10-30T18:42:24", "url": "https://files.pythonhosted.org/packages/49/58/bd7d1eff413f0e4b1652c429470cec9db4ef681e7ba8c532cca9a4fc3490/endorser-0.23.tar.gz" } ], "0.24": [ { "comment_text": "", "digests": { "md5": "87a474db8dbbbb07cc9ce319ae5238c2", "sha256": "1400a51bd498093f6b7b7f0e692c77c20b588ce8798feeccb33e3f6c5d881473" }, "downloads": -1, "filename": "endorser-0.24-py3-none-any.whl", "has_sig": false, "md5_digest": "87a474db8dbbbb07cc9ce319ae5238c2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12977, "upload_time": "2018-11-08T15:55:43", "url": "https://files.pythonhosted.org/packages/30/80/17fecc3e27ebeb91f57c115fd3784de68371c3f5c25d35264b81ac5729d8/endorser-0.24-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1eefe4b2ab3fa958568647cf912c2138", "sha256": "34959ddf955382c74d645bf03a3146e6dd4168b52e392a6314e1780e2a3a911b" }, "downloads": -1, "filename": "endorser-0.24.tar.gz", "has_sig": false, "md5_digest": "1eefe4b2ab3fa958568647cf912c2138", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12903, "upload_time": "2018-11-08T15:55:44", "url": "https://files.pythonhosted.org/packages/71/0e/6ed58b3b20a5d109bc97eb4fe071076fbc1490ef9d765e38c8c332d022b2/endorser-0.24.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "87a474db8dbbbb07cc9ce319ae5238c2", "sha256": "1400a51bd498093f6b7b7f0e692c77c20b588ce8798feeccb33e3f6c5d881473" }, "downloads": -1, "filename": "endorser-0.24-py3-none-any.whl", "has_sig": false, "md5_digest": "87a474db8dbbbb07cc9ce319ae5238c2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12977, "upload_time": "2018-11-08T15:55:43", "url": "https://files.pythonhosted.org/packages/30/80/17fecc3e27ebeb91f57c115fd3784de68371c3f5c25d35264b81ac5729d8/endorser-0.24-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1eefe4b2ab3fa958568647cf912c2138", "sha256": "34959ddf955382c74d645bf03a3146e6dd4168b52e392a6314e1780e2a3a911b" }, "downloads": -1, "filename": "endorser-0.24.tar.gz", "has_sig": false, "md5_digest": "1eefe4b2ab3fa958568647cf912c2138", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12903, "upload_time": "2018-11-08T15:55:44", "url": "https://files.pythonhosted.org/packages/71/0e/6ed58b3b20a5d109bc97eb4fe071076fbc1490ef9d765e38c8c332d022b2/endorser-0.24.tar.gz" } ] }