{ "info": { "author": "Sergey Arkhipov", "author_email": "serge@aerialsounds.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Testing" ], "description": "isitbullshit\n============\n\n|Build Status| |Code Coverage| |Static Analysis| |PyPi Package|\n\n``isitbullshit`` is small and funny library which is intended to be used like lightweight schema verification for JSONs\nbut basically it could be used as a schema validator for every generic Python structure: dict, list, tuple etc. It is\nwritten to be pretty much Pythonic in a good sense: easy to use and very clean syntax but powerful enough to clean\nyour needs. But mostly for verification of incoming JSONs. Actually it is really stable and I am using it in several\nproduction projects, this is an excerpt because I really got tired of reinventing the wheel.\n\nYes, this is a wheel reinvention also but probably you will like it. Let me show the code.\n\n\n\nAn example\n----------\n\nOkay, let's say you are doing some backend for the library and you have to process JSONs like this:\n\n.. code-block:: javascript\n\n {\n \"model\": \"book_collection\",\n \"pk\": 318,\n \"fields\": {\n \"books\": [\n {\n \"model\": \"book\",\n \"pk\": 18,\n \"fields\": {\n \"title\": \"Jane Eyre\",\n \"author\": \"Charlotte Bront\u00eb\",\n \"isbn\": {\n \"10\": \"0142437204\",\n \"13\": \"978-0142437209\"\n },\n \"rate\": null,\n \"language\": \"English\",\n \"type\": \"paperback\",\n \"tags\": [\n \"Penguin Classics\",\n \"Classics\",\n \"Favorites\"\n ],\n \"published\": {\n \"publisher\": \"Penguin Books\",\n \"date\": {\n \"day\": 24,\n \"month\": 4,\n \"year\": 2003\n }\n }\n }\n },\n {\n \"model\": \"book\",\n \"pk\": 18,\n \"fields\": {\n \"title\": \"The Great Gatsby\",\n \"author\": \"F.Scott Fitzgerald\",\n \"isbn\": {\n \"10\": \"185326041X\",\n \"13\": \"978-1853260414\"\n },\n \"language\": \"English\",\n \"type\": \"paperback\",\n \"finished\": true,\n \"rate\": 4,\n \"tags\": [\n \"Wordsworth Classics\",\n \"Classics\",\n \"Favorites\"\n ],\n \"published\": {\n \"publisher\": \"Wordsworth Editions Ltd\",\n \"date\": {\n \"day\": 1,\n \"month\": 5,\n \"year\": 1992\n }\n }\n }\n }\n ]\n }\n }\n\nYou've got an idea, right? Pretty common and rather simple. Let's compose a schema and verify it.\n\n.. code-block:: python\n\n from json import loads\n from isitbullshit import isitbullshit, OrSkipped\n\n def rate_validator(value):\n if not (1 <= int(value) <= 5):\n raise ValueError(\n \"Value {} has to be from 1 till 5\".format(value)\n )\n\n data = loads(request)\n schema = {\n \"model\": str,\n \"pk\": int,\n \"fields\": {\n \"books\": [\n {\n \"model\": str,\n \"pk\": int,\n \"fields\": {\n \"title\": str,\n \"author\": str,\n \"isbn\": {\n \"10\": str,\n \"13\": str\n },\n \"language\": str,\n \"type\": (\"paperback\", \"kindle\"),\n \"finished\": OrSkipped(True),\n \"rate\": (rate_validator, None),\n \"tags\": [str],\n \"published\": {\n \"publisher\": str,\n \"date\": OrSkipped(\n {\n \"day\": int,\n \"month\": int,\n \"year\": int\n }\n )\n }\n }\n }\n ]\n }\n }\n\n if isitbullshit(data, schema):\n raise Error400(\"Incoming request is not valid\")\n process(data)\n\nPretty straightforward. Let me explain what is going on here.\n\n\n\nBasic concepts\n--------------\n\nisitbullshit was created to be used with JSONs and actively uses the fact that JSON perfectly matches to Python\ninternal data structures. Basic rule here: if elements are equal then they should be validated without any problems.\n\nSo if you have a code like\n\n.. code-block:: python\n\n >>> suspicious = {\n ... \"foo\": 1,\n ... \"bar\": 2\n ... }\n\nthen\n\n.. code-block:: python\n\n >>> print isitbullshit(suspicious, suspicious)\n False\n\nKeep this in mind.\n\nIf elements are equal then no additional validation steps have to be used. Otherwise it tries to match types and do\nsome explicit assertions.\n\nSo there are some rules.\n\n\n\nValue validation\n----------------\n\nValue validation is pretty straighforward: if values are the same or they are equal to each other (operation ``=``)\nthen validation has to be passed. So the rule is: if ``is`` or ``=`` works, then matching is successful.\n\n.. code-block:: python\n\n >>> print isitbullshit(1, 1)\n False\n >>> print isitbullshit(1.0, 1.0)\n False\n >>> print isitbullshit(1.0, decimal.Decimal(\"1.0\"))\n False\n >>> print isitbullshit(None, None)\n False\n >>> obj = object()\n >>> print isitbullshit(obj, obj)\n False\n\n\nType validation\n---------------\n\nIf value validation is not passed then type validation is performed. The idea is: ``1`` is ``1``, right? But you will\nbe satisfied if you know that ``1`` is ``int`` as well, right?\n\nSo\n\n.. code-block:: python\n\n >>> print isitbullshit(1, int)\n False\n >>> print isitbullshit(1.0, float)\n False\n >>> print isitbullshit(decimal.Decimal(\"1.0\"), decimal.Decimal)\n False\n >>> obj = object()\n >>> print isitbullshit(obj, object)\n False\n\n\n\nCustom validation\n-----------------\n\nLet's get back to an example. Have you mentioned that we have ``rate_validator`` function there? It is custom validator.\n\nIt works pretty simple: you define custom callable (function, lambda, class, etc) and ``isitbullshit`` gives it your\nvalue. If no exception is raised than we consider the value as successfully validated. So in our example if a ``rate``\nfield is not in (1, 5) interval or not integer then exception will be raised.\n\nCustom validators are used mostly in cases if you have to check a content or do not so shallow verifications.\n\nBut there is only one pitfall you may face with: **custom validators have to be a functions**. Basically, this is an\nobligatory rule and there are several reasons. Let's checkout the code:\n\n.. code-block:: python\n\n >>> print isitbullshit(1, str)\n\nWhat do you expect to have as result? I guess ``True`` because integer is not an instance of the string type, right?\nBut wait a minute, in Python 2:\n\n.. code-block:: python\n\n >>> type(str)\n \n\nand in Python 3\n\n.. code-block:: python\n\n >>> type(str)\n \n\nSo they are types! They have the same type as, let's say, ``Exception`` or ``object``, right? But validation rules\nhave to be consistent so I am trying to keep absolutely the same behaviour to have it clean and predictable.\n\nWhat is the story? Here is the story:\n\n.. code-block:: python\n\n >>> print str(1)\n '1'\n\nSo to avoid such situations when ``isitbullshit(1, str) == False`` it is better to use functions. Functions are the most\nreasonable agreement I see here. So if you want to verify MongoDB's ObjectId's do the following:\n\n.. code-block:: python\n\n >>> print isitbullshit(1, lambda value: bson.ObjectId(value))\n True\n >>> print isitbullshit(\"507c7f79bcf86cd7994f6c0e\", lambda value: bson.ObjectId(value))\n False\n\nIt brings some clutter but at least you will not hike in the minefield.\n\n\n\nOrSkipped validator\n-------------------\n\nSometimes we live in a real world which sucks. Sometimes we have schemaless data (and it sucks of course) so some\nfields from your requests are missed. Or you do not care. ``isitbullshit`` has 2 different fixes for\nthat: ``OrSkipped`` and ``WHATEVER``.\n\nIf you wrap a part of your validator in ``OrSkipped`` than you mark that it is ok if this field would be absent.\nArgument is a validator of course. And if field is in place, it will be validated as expected.\n\n.. code-block:: python\n\n >>> schema = {\n ... \"foo\": 1,\n ... \"bar\": OrSkipped(int),\n ... \"baz\": OrSkipped(str)\n >>> }\n >>> print isitbullshit({\"foo\": 1, \"bar\": 1}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": \"str\"}, schema)\n True\n >>> print isitbullshit({\"foo\": 1, \"bar\": 1, \"baz\": 1}, schema)\n True\n >>> print isitbullshit({\"foo\": 1, \"bar\": 1, \"baz\": \"str\"}, schema)\n False\n\nSo if we miss any field, it is ok. Unless it is presented and validator-argument point us to a bullshit.\n\n``OrSkipped`` has to be used only with dictionary field validation. You can put it anywhere but then it has no special\nmeaning, just an object.\n\nBy the way, type validation rule is still here: ``itisbullshit(something, something) == False`` anyway so the following\ncode is valid (and it is reasonable, right?)\n\n.. code-block:: python\n\n >>> schema = {\n ... \"foo\": 1,\n ... \"bar\": OrSkipped(int),\n ... \"baz\": OrSkipped(str)\n >>> }\n >>> isitbullshit(schema, schema)\n False\n >>> stripped_schema = dict((k, v) for k, v in schema.iteritems() if k != \"baz\")\n >>> isitbullshit(stripped_schema, schema)\n False\n >>> isitbullshit(schema, stripped_schema)\n False\n\nGuess why.\n\n\n\nWHATEVER validator\n------------------\n\n``WHATEVER`` is a mark that you do not care what value is. It could be anything, nobody cares.\n\n.. code-block:: python\n\n >>> schema = {\n ... \"foo\": 1,\n ... \"bar\": WHATEVER\n >>> }\n >>> print isitbullshit({\"foo\": 1, \"bar\": 1}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": \"str\"}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": object()}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": os.path}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": [1, 2, 3]}, schema)\n False\n\nSee? We do not care about a value of a ``bar``.\n\n``WHATEVER`` could be used with any type.\n\n\nDict validation\n---------------\n\nYou've already seen a ``dict`` validation so let me repeat your assumptions: yes, we match values with the same keys.\nBut there is only one pitfall: if suspicious element has more fields than schema, then validation is ok also.\n\nIt has it's own meaning: we can put only those keys and fields we actually care about. Our software later will work\nonly with this subset so why should we care about the rest of rubbish?\n\nSo, an example again:\n\n.. code-block:: python\n\n >>> schema = {\n ... \"foo\": 1,\n ... \"bar\": str\n >>> }\n >>> print isitbullshit({\"foo\": 1, \"bar\": \"st\"}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": \"str\", \"baz\": 1}, schema)\n False\n >>> print isitbullshit({\"foo\": 1, \"bar\": \"str\", \"baz\": object()}, schema)\n False\n\nAs you can see, we did not mention any ``baz`` in an element but validation still passed.\n\n\n\nList validation\n---------------\n\nList validation is pretty simple: we define one validator and it will be matched to any list element.\n\n.. code-block:: python\n\n >>> print isitbullshit([1, 2, 3], [int])\n False\n >>> print isitbullshit([1, 2, 3], [str])\n True\n >>> print isitbullshit([1, 2, \"3\"], [int])\n True\n\nIn the last example, ``\"3\"`` is not an integer so validation fails.\n\nHow could we manage situations when we have heterogeneous elements? We have to use tuples.\n\nAnd please remember that ``isitbullshit(something, something) == False``.\n\n\nTuple validation\n----------------\n\nTuple validation is pretty easy to understand if you consider it as an OR condition. We define several validators\nand and the value has to match at least one of them. So\n\n.. code-block:: python\n\n >>> print isitbullshit(1, (str, dict))\n True\n >>> print isitbullshit(1, (str, int))\n False\n\n``1`` is not ``str`` but it is ``int``.\n\nNow let's try to fix an example in the previous chapter.\n\n.. code-block:: python\n\n >>> print isitbullshit([1, 2, \"3\"], [int])\n True\n >>> print isitbullshit([1, 2, \"3\"], [(int, str)])\n False\n\nAnd again, do not forget about a rule of thumb: ``isitbullshit(something, something) == False``.\n\n\n\nraise_for_problem function\n--------------------------\n\nThis package also provides you with another method to validate, ``raise_for_problem`` actually this is a core method\nwhich raises an exception on a problem. ``isitbullshit`` allows you to get an idea what is happening in both Python2 and\nPython3, let's check an example.\n\n.. code-block:: python\n\n >>> try:\n ... raise_for_problem({\"foo\": \"1\", \"bar\": {\"baz\": 2}}, {\"foo\": \"1\", \"bar\": {\"baz\": str}})\n ... except ItIsBullshitError as err:\n ... print err\n {'foo': '1', 'bar': {'baz': 2}}:\n {'baz': 2}:\n 2: Scheme mismatch \n\nQuite clear and nice. If you want just to extract a pure message lines, iterate ``ItIsBullshitError`` and\nyou are good.\n\n\n\nIsItBullshitMixin mixin\n-----------------------\n\n``isitbullshit`` also supplied with ``IsItBullshitMixin`` which is intended to be mixed with ``unittest.TestCase``. It\nallows you to use 2 additional methods:\n\n* ``assertBullshit``\n* ``assertNotBullshit``\n\nGuess what they do.\n\n.. code-block:: python\n\n from unittest import TestCase\n from isitbullshit import IsItBullshitMixin\n\n class BullshitTestCase(IsIsBullshitMixin, TestCase):\n\n def test_bullshit(self):\n self.assertBullshit(1, None)\n\n def test_not_bullshit(self):\n self.assertNotBullshit(1, int)\n\n\n.. |Build Status| image:: https://travis-ci.org/9seconds/isitbullshit.svg?branch=master\n :target: https://travis-ci.org/9seconds/isitbullshit\n\n.. |Code Coverage| image:: https://coveralls.io/repos/9seconds/isitbullshit/badge.png?branch=master\n :target: https://coveralls.io/r/9seconds/isitbullshit?branch=master\n\n.. |Static Analysis| image:: https://landscape.io/github/9seconds/isitbullshit/master/landscape.png\n :target: https://landscape.io/github/9seconds/isitbullshit/master\n\n.. |PyPi Package| image:: https://badge.fury.io/py/isitbullshit.svg\n :target: http://badge.fury.io/py/isitbullshit", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/9seconds/isitbullshit/", "keywords": "json validation jsonschema", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "isitbullshit", "package_url": "https://pypi.org/project/isitbullshit/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/isitbullshit/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/9seconds/isitbullshit/" }, "release_url": "https://pypi.org/project/isitbullshit/0.2.1/", "requires_dist": null, "requires_python": null, "summary": "Small library for verifying parsed JSONs if they are bullshit or not", "version": "0.2.1" }, "last_serial": 1203259, "releases": { "0.2.0": [ { "comment_text": "", "digests": { "md5": "0b2bf370223e0f436fc7c15b3232016d", "sha256": "4546dd98f2fe6a9940278d56f077f02b145d436a589ae6202fc382d9b914c6e5" }, "downloads": -1, "filename": "isitbullshit-0.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "0b2bf370223e0f436fc7c15b3232016d", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 13560, "upload_time": "2014-08-21T03:34:04", "url": "https://files.pythonhosted.org/packages/dc/99/e80489bb36bb32e7fb7142d7373d36e3f31267b5677e794f7a89624c5809/isitbullshit-0.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ad0dc18e97e39cce98271d6ed485e921", "sha256": "8bd60936091ea430b235dfc43bea9da8ce233ceae42f1607af17078eaabe6e49" }, "downloads": -1, "filename": "isitbullshit-0.2.0.tar.gz", "has_sig": false, "md5_digest": "ad0dc18e97e39cce98271d6ed485e921", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11364, "upload_time": "2014-08-21T03:33:53", "url": "https://files.pythonhosted.org/packages/8d/22/e907316bcfa3d59f40a6fad91ac9524146789ebca83092352fdddff570b5/isitbullshit-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "c76eb0aa701e71bd0727609fd8e3128a", "sha256": "f4a520de667632fd7a8543777bc9c09eb8555a9a130b02565837b9704f056f59" }, "downloads": -1, "filename": "isitbullshit-0.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c76eb0aa701e71bd0727609fd8e3128a", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 14494, "upload_time": "2014-08-26T18:36:29", "url": "https://files.pythonhosted.org/packages/62/38/6c8e2e71236ef1d02ce29bce823234cfa0869a6600f98a273543f77aa5e5/isitbullshit-0.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "46516f5e3393d990bb0486f93fdb0746", "sha256": "82249591f378801394e34ed7cd79b6e79b12eab9fcde4398b378a23fcf165b8e" }, "downloads": -1, "filename": "isitbullshit-0.2.1.tar.gz", "has_sig": false, "md5_digest": "46516f5e3393d990bb0486f93fdb0746", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13668, "upload_time": "2014-08-26T18:36:25", "url": "https://files.pythonhosted.org/packages/d0/02/9c893669d4e0ca3247417eb1e70184e20ba0388213f719dfa084cf00f184/isitbullshit-0.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "c76eb0aa701e71bd0727609fd8e3128a", "sha256": "f4a520de667632fd7a8543777bc9c09eb8555a9a130b02565837b9704f056f59" }, "downloads": -1, "filename": "isitbullshit-0.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c76eb0aa701e71bd0727609fd8e3128a", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 14494, "upload_time": "2014-08-26T18:36:29", "url": "https://files.pythonhosted.org/packages/62/38/6c8e2e71236ef1d02ce29bce823234cfa0869a6600f98a273543f77aa5e5/isitbullshit-0.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "46516f5e3393d990bb0486f93fdb0746", "sha256": "82249591f378801394e34ed7cd79b6e79b12eab9fcde4398b378a23fcf165b8e" }, "downloads": -1, "filename": "isitbullshit-0.2.1.tar.gz", "has_sig": false, "md5_digest": "46516f5e3393d990bb0486f93fdb0746", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13668, "upload_time": "2014-08-26T18:36:25", "url": "https://files.pythonhosted.org/packages/d0/02/9c893669d4e0ca3247417eb1e70184e20ba0388213f719dfa084cf00f184/isitbullshit-0.2.1.tar.gz" } ] }