{ "info": { "author": "Benjamin Le Forestier", "author_email": "benjamin@leforestier.org", "bugtrack_url": null, "classifiers": [ "Environment :: Other Environment", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": ".. image:: https://travis-ci.org/leforestier/naval.svg\n :target: https://travis-ci.org/leforestier/naval\n\n-----------------------------\nNaval is a validation library\n-----------------------------\n\nUsing *Naval*, you can define schemas to validate or transform python dictionaries or other objects.\n*Naval* provides you with error messages in multiple languages. You can use it to validate JSON documents.\n\n*Naval* offers a very flexible and readable way to transform python dictionaries, which makes it a valuable \ntool when implementing RESTful apis.\n\nYou could also use *Naval* to validate HTML forms.\n\n`table of contents`_\n\n---------------\nExamples of use\n---------------\n\nSimple validation\n=================\n\n.. code:: python\n\n >>> from naval import *\n >>> address_schema = Schema(\n ['house number', Type(int), Range(1, 10000)],\n ['street', Type(str), Length(min=5, max=255)],\n ['zipcode', Type(str), Regex('\\d{4,5}')],\n ['country', ('France', 'Germany', 'Spain')]\n )\n\n >>> address_schema.validate({\n 'house number': 12000,\n 'street': 'tapioca boulevard',\n 'country': 'Federal Kingdom of Portulombia'\n })\n ...\n ValidationError: {'house number': 'The maximum is 10000.', 'zipcode': 'Field is missing.', 'country': 'Incorrect value.'}\n\n >>> address_schema.validate({\n 'house number': 17,\n 'street': 'rambla del Raval',\n 'zipcode': '08001',\n 'country': 'Spain'\n })\n {'country': 'Spain',\n 'house number': 17,\n 'street': 'rambla del Raval',\n 'zipcode': '08001'}\n\nValidation and transformation\n=============================\n\n.. code:: python\n\n >>> from naval import *\n >>> from passlib.hash import bcrypt # we're going to use the passlib library to encrypt passwords\n\n >>> registration_form = Schema( \n ['username', Type(str), Length(min=3, max=16)],\n ['password', Type(str)],\n ['password2'],\n [\n Assert(\n (lambda d: d['password'] == d['password2']),\n error_message = \"Passwords don't match\"\n )\n ],\n ['password', lambda s: s.encode('utf-8'), bcrypt.encrypt, Save],\n ['password2', Delete],\n ['email', Email]\n )\n\n >>> registration_form.validate({\n 'email': 'the-king@example.com',\n 'username': 'TheKing',\n 'password': 'hackme',\n 'password2': 'hackme'\n })\n {'email': 'the-king@example.com',\n 'password': '$2a$12$JT2UlXP0REt3EX7kGIFGV.5uKPQJL4phDRpfcplW91sJAyB8RuKwm',\n 'username': 'TheKing'}\n\n >>> registration_form.validate({\n 'username': 'TheKing',\n 'email': '@@@@@@@@@@',\n 'password': 'hackme',\n 'password2': 'saltme'\n })\n ...\n ValidationError: {'email': 'This is not a valid email address.', '*': \"Passwords don't match\"}\n\nComposing schemas\n=================\n\nSchemas can be reused to build bigger schemas.\n\n.. code:: python\n\n >>> from naval import *\n\n >>> editor_schema = Schema(\n ['name', Type(str)],\n ['website', Optional, Url]\n )\n \n >>> book_schema = Schema(\n ['title', Type(str)],\n ['author', Type(str), Length(max=200)],\n ['isbn13', Type(str), Length(13,13), Regex('\\d+')],\n ['editor', editor_schema]\n )\n\n >>> book_schema.validate({\n 'title': 'Lose weight by eating pancakes',\n 'author': 'John Greedyquack',\n 'isbn13': '1234567890123',\n 'editor': {\n 'name': 'Flawed Books',\n 'website': 'http://#'\n }\n })\n ...\n ValidationError: {'editor': {'website': 'This is not a valid url.'}}\n\n\nInternationalization\n====================\n\nSupply a ``lang`` keyword argument to the ``validate`` method to obtain translated error messages.\n\n.. code:: python\n\n >>> editor_schema.validate({ 'website': 'http://#' }, lang = 'fr')\n ...\n ValidationError: {'name': 'Champ manquant.', 'website': \"Ce n'est pas une url valide.\"}\n\n\n-------\nFilters\n-------\n\nFilters are used to validate or transform python objects. Filters are instances of the many subclasses of ``naval.Filter``.\nA filter's ``validate`` method takes a value to examine, and either returns it (or a modified version of it), or it raises a\n``ValidationError`` exception. You can catch this exception like this:\n\n.. code:: python \n\n try:\n potentially_modified_version = my_filter.validate(obj)\n except ValidationError as exc:\n print(exc.error_details)\n\nThe ``ValidationError`` instance has a ``error_details`` attribute, that contains, well, details about the error.\nFor elementary filters, ``exc.error_details`` is just a string describing the error. \nFor the ``Schema`` filter (used to validate python dictionaries), ``exc.error_details`` is a dictionary \n(each key of this dictionary contains details about the errors generated by a particular item).\n\nIt's always possible to supply custom error messages when constructing a filter.\n\n\nElementary filters\n==================\n\nRange\n-----\n\n.. code:: python\n\n >>> Range(5, 10).validate(7)\n 7\n\n >>> Range(5, 10).validate(-16)\n ...\n ValidationError: The minimum is 5.\n\nLength\n------\n\n.. code:: python\n\n >>> Length(max=3).validate(['one', 'two', 'three'])\n ['one', 'two', 'three']\n\n >>> Length(max=3).validate(['one', 'two', 'three', 'four'])\n ValidationError: The value is too long. Max length is 3.\n\n # customizing the error message\n >>> Length(max=3, too_long_error=\"Please, no more than {max_length} items\").validate(\n ['one', 'two', 'three', 'four']\n )\n ...\n ValidationError: Please, no more than 3 items\n\nType\n----\n\n.. code:: python\n\n >>> Type(int, float).validate(3.14)\n 3.14\n\nBy default, the type must match exactly.\nUse ``subclasses = True`` to allow for subclasses.\n\n.. code:: python\n\n >>> from collections import OrderedDict\n\n >>> Type(dict).validate(OrderedDict([('a', 1), ('b', 2)]))\n ...\n ValidationError: Wrong type. Expected dict. Got OrderedDict instead.\n \n >>> Type(dict, subclasses = True).validate(OrderedDict([('a', 1), ('b', 2)]))\n OrderedDict([('a', 1), ('b', 2)])\n\nRegex\n-----\n\n The pattern must match exactly, from the beginning to the end of the string.\n\n.. code:: python\n\n >>> Regex('[A-Za-z][-_A-Za-z0-9]+').validate('TheKing!!!')\n ...\n ValidationError: Incorrect value.\n\n >>> Regex('[A-Za-z][-_A-Za-z0-9]+').validate('TheKing')\n 'TheKing'\n\nEmail\n-----\n\nEmail validator.\n\nInternally, this filter uses the email validation function from the *validators* library: https://github.com/kvesteri/validators\n\n.. code:: python\n\n >>> Email.validate('email@example.com')\n 'email@example.com'\n\n.. code:: python\n\n >>> Email.validate('user@92.80.0.1')\n ...\n ValidationError: This is not a valid email address.\n\nUrl\n---\n\nUrl validator.\nThe regex used to validate urls was borrowed from the Spoon php library: http://spoon-library.be\n\n.. code:: python\n\n >>> Url.validate('http://www.example.com/v1/?sort=asc')\n 'http://www.example.com/v1/?sort=asc'\n\n.. code:: python\n\n >>> Url.validate('http://0.0.0.0')\n ...\n ValidationError: This is not a valid url.\n \nDomain\n------\n\nDomain name validator.\n\nInternally, this filter uses the domain name validation function from the *validators* library: https://github.com/kvesteri/validators\n\n.. code:: python\n\n >>> Domain.validate('example.com')\n 'example.com'\n\n.. code:: python\n\n >>> Domain.validate('example.com/')\n ...\n ValidationError: This is not a valid domain name.\n\nAssert\n------\n\nAssert builds a filter from a boolean function.\n\n.. code:: python\n\n >>> only_digits = Assert(str.isdigit, error_message = \"Only digits are allowed\")\n\n >>> only_digits.validate('12345')\n '12345'\n\n >>> only_digits.validate('12-345')\n ...\n ValidationError: Only digits are allowed\n \n\n\nApply\n-----\n\n``Apply`` applies a function to its argument and returns the result.\nBy default, it will reraise any exception as a ValidationError, but you can specify what kind of exception\n(if any) is expected. \n\n.. code:: python\n \n >>> hex_to_int = Apply(lambda h: int(h, 16))\n \n >>> hex_to_int.validate('aa')\n 170\n\n >>> hex_to_int.validate('zz')\n ...\n ValidationError: invalid literal for int() with base 16: 'zz'\n\nYou rarely have to use ``Apply`` inside a ``Schema``, because any callable is converted implicitly to an ``Apply`` filter.\n\n.. code:: python\n\n forum_post = Schema(\n ['title', Length(max=100), str.lower, str.capitalize, Save],\n ['post', Length(max=4000)]\n )\n\nHowever, it can sometimes be useful to explicitly use ``Apply`` to customize the error message, or to specify exactly what kind of exception\nis expected.\n\n.. code:: python\n\n >>> import numpy as np\n >>> matrix_inverter = Schema(\n ['matrix',\n np.array,\n Apply(\n np.linalg.inv,\n catch = (np.linalg.LinAlgError,),\n error_message = \"Please supply an invertible square matrix\"\n ),\n (lambda mat: mat.tolist()),\n MoveTo('inverse')\n ]\n )\n\nThis example uses three ``Apply`` filters. ``np.array`` and ``(lambda mat: mat.tolist())`` are implicitly converted \nto ``Apply`` filters by the ``Schema`` constructor.\n\n.. code:: python\n\n >>> matrix_inverter.validate({'matrix': [[1,1],[1,0]]})\n {'inverse': [[0.0, 1.0], [1.0, -1.0]]}\n\n >>> matrix_inverter.validate({'matrix': [[1,1],[1,1]]})\n ...\n ValidationError: {'matrix': 'Please supply an invertible square matrix', 'inverse': \"Couldn't compute field.\"}\n\nIn\n--\n\n.. code:: python\n\n >>> In(['red', 'blue', 'yellow']).validate('blue')\n 'blue'\n\n >>> In(['red', 'blue', 'yellow']).validate(\"broccoli\")\n ... \n ValidationError: Incorrect value.\n\n >>> In(\n ['red', 'blue', 'yellow'],\n error_message = \"Please choose one of the available colors.\"\n ).validate(\"broccoli\")\n ...\n ValidationError: Please choose one of the available colors.\n \n\nYou rarely have to use ``In`` explicitly in a ``Schema``. Any object that implements the ``__contains__`` special method (like for example, python lists, tuples, set, and many more) will be automatically converted to an ``In`` filter by the ``Schema`` constructor.\n\n.. code:: python\n \n shipping_schema = Schema(\n ['address', address_schema],\n ['shipping method', ('priority mail', 'parcel post', 'bottle to the sea')] \n )\n \nAs you can see, unless you want to customize the error message, you don't have to build a ``In`` filter explicitly, when \nyou define a ``Schema``.\n\n\nFilter builders\n===============\n\nYou can build filters from other filters.\nThe most sophisticated example is probably ``Schema`` which is used to create a filter for python dictionaries.\n\n.. code:: python\n\n >>> from naval import *\n >>> address_schema = Schema(\n ['house number', Type(int), Range(1, 10000)],\n ['street', Type(str), Length(min=5, max=255)],\n ['zipcode', Type(str), Regex('\\d{4,5}')],\n ['country', ('France', 'Germany', 'Spain')]\n )\n\n >>> address_schema.validate({\n 'house number': 12000,\n 'street': 'tapioca boulevard',\n 'country': 'Federal Kingdom of Portulombia'\n })\n ...\n ValidationError: {'house number': 'The maximum is 10000.', 'zipcode': 'Field is missing.', 'country': 'Incorrect value.'}\n\nBut first let's talk about some simpler filter builders.\n\nDo\n--\n\n``Do`` creates a new filter from existing filters. The filters will be applied one after another.\nFor example, the ``Url`` validator is actually defined this way:\n\n.. code:: python\n\n Url = Do(\n Type(str),\n Length(max=2083),\n Regex(\"a huge regex here\"),\n error_message = _(\"This is not a valid url.\")\n )\n\nAs you can see, it is possible to specify an error message.\nThis error message will override any error message that could be triggered by \nthe filters in the sequence. \n\nEach\n----\n\nUse ``Each`` if you want to apply a filter to every element of a collection.\n\nFor example, to validate that a field is a list of integers:\n\n.. code:: python\n\n >>> schema = Schema(\n ['integers', Type(list), Each(Type(int))]\n )\n\n >>> schema.validate({'integers': [1, 2, 3, 5]})\n {'integers': [1, 2, 3, 5]}\n\n >>> schema.validate({'integers': [8, \"broccoli\", 21]})\n ...\n ValidationError: {'integers': 'Item #2: Wrong type. Expected int. Got str instead.'}\n\nYou can use ``Each0`` if you want the items to be numbered from 0 when generating the error messages:\n\n.. code:: python\n\n >>> Each0(Type(int)).validate([8, \"broccoli\", 21])\n ...\n ValidationError: Item #1: Wrong type. Expected int. Got str instead.\n\nIt can prove useful to use ``Each`` in combination with ``Do`` in order to apply many filters\nto each elements of a list. For example:\n\n.. code:: python\n\n >>> schema = Schema(\n ['keywords', Type(list), Each( Do( Type(str), Length(min=2, max=30), str.lower) ), Save]\n )\n\n >>> schema.validate({'keywords': ['PANCAKES', 'FOOD', 'Recipe']})\n {'keywords': ['pancakes', 'food', 'recipe']}\n\nSchema\n------\n\n``Schema`` is the class used to define validation and transformation rules for python dictionaries.\nEach rule is expressed as a list. Like this:\n\n.. code:: python\n\n address_schema = Schema(\n ['house number', Type(int), Range(1, 10000)],\n ['street', Type(str), Length(min=5, max=255)],\n ['zipcode', Type(str), Regex('\\d{4,5}')],\n ['country', ('France', 'Germany', 'Spain')],\n )\n\nor this:\n\n.. code:: python\n\n registration_form = Schema( \n ['username', Type(str), Length(min=3, max=16)],\n ['password', Type(str)],\n ['password2'],\n [\n Assert(\n (lambda d: d['password'] == d['password2']),\n error_message = \"Passwords don't match\"\n )\n ],\n ['password', lambda s: s.encode('utf-8'), bcrypt.encrypt, Save],\n ['password2', Delete],\n ['email', Email]\n )\n\nEach rule either apply to a particular field of the dictionary, or it applies to the dictionary\nas a whole. If a rule starts with a filter, or a callable, then the rule applies to the whole dictionary.\nOtherwise (for example if the rule starts with a string like ``\"username\"``), then the rule applies to this\nparticular item of the dictionary.\n\nIn the preceding example, the rule\n\n.. code:: python\n\n [\n Assert(\n (lambda d: d['password'] == d['password2']),\n error_message = \"Passwords don't match\"\n )\n ]\n\nis a global rule. The ``Assert`` filter is called on the whole dictionary.\n\n\nHere's another example:\n\n.. code:: python\n\n schema = Schema(\n ['first name', Type(str), Length(min=1, max=50)],\n ['last name', Type(str), Length(min=1, max=50)],\n [lambda d: d['first name'] + ' ' + d['last name'], SaveAs('full name')]\n )\n\nThe last rule starts with a callable so it applies to the whole dictionary.\nI guess it's time to introduce the ``SaveAs`` instruction.\n\nEach rule can optionally end with a storage instruction: ``SaveAs``, ``MoveTo``, ``Save`` or ``Delete``.\n\nSaveAs\n~~~~~~\n\nUse SaveAs at the end of chain to save the current value under another key.\nKeep in mind that it doesn't modify the input dictionary. The modification is seen only\nin the output dictionary (the return value of the ``validate`` method).\n\nExample:\n\n.. code:: python\n\n >>> original = {'age': 25.4}\n\n >>> Schema(['age', round, SaveAs('age_round')]).validate(original)\n {'age': 25.4, 'age_round': 25}\n\n >>> original\n {'age': 25.4}\n\n\nMoveTo\n~~~~~~\n\nUse MoveTo at the end of a chain to move an item under another key, and delete the current key.\nKeep in mind that it doesn't modify the input dictionary. The modification is seen only\nin the output dictionary (the return value of the ``validate`` method).\n\nExample:\n\n.. code:: python\n\n >>> original = {'age': 25.4}\n\n >>> Schema(['age', round, MoveTo('age_round')]).validate(original)\n {'age_round': 25}\n\n >>> >>> original\n {'age': 25.4}\n\n\nSave\n~~~~\n\nUse Save at the end of a chain in order to save the current value under the current key.\nKeep in mind that it doesn't modify the input dictionary. The modification is seen only\nin the output dictionary (the return value of the ``validate`` method).\n\nExample:\n\n.. code:: python\n \n >>> original = {'age': 25.4}\n\n >>> Schema(['age', round, Save]).validate()\n {'age': 25}\n\n >>> original\n {'age': 25.4}\n\nDelete\n~~~~~~\n\nUse Delete at the end of a chain to delete the current key.\nKeep in mind that it doesn't modify the input dictionary. The modification is seen only\nin the output dictionary (the return value of the ``validate`` method).\n\nI have to introduce 3 other useful instructions now: ``Optional``, ``Default`` and ``Discard``.\n\nOptional\n~~~~~~~~\n\nOptional should be placed after a field name in a chain.\n\n.. code:: python\n\n >>> icecream_order = Schema(\n ['flavour', ('vanilla', 'chocolate', 'pistachio')],\n ['topping', Optional, ('whipped cream', 'chocolate sprinkles', 'peanuts')],\n ['quantity', int, Range(1, 12)]\n )\n\nThe schema will just skip to the next rule if it doesn't find the key in the dictionary.\n\n.. code:: python\n\n >>> icecream_order.validate({'flavour': 'vanilla', 'quantity': 4})\n {'flavour': 'vanilla', 'quantity': 4}\n\nDefault\n~~~~~~~\n\n``Default`` should be placed after a field name in a chain.\nThe ``Default`` constructor takes an object or a callable as an argument.\n\nExample:\n\n.. code:: python\n\n ['currency', Default('USD')]\n\nExample (using a callable):\n\n.. code:: python\n\n ['username', Default(lambda d: ''.join(random.choice(string.ascii_lowercase) for _ in range(6)))]\n \nThis would generate a random username if no username was supplied.\n\nIf you pass a callable, this should be a unary function. It will be passed the whole dictionary.\nThis way, it is possible to set a default value for a field using other items of the dictionary. For example:\n\n.. code:: python\n\n >>> schema = Schema(\n ['email', Email],\n ['username', Default(lambda d: d['email'])]\n )\n \nThis would set the username to be the email address if no username was supplied.\n\n.. code:: python\n\n >>> schema.validate({'email': 'the-king@example.com'})\n {'email': 'the-king@example.com', 'username': 'the-king@example.com'}\n \n\nDiscard\n~~~~~~~ \n\n``Discard`` should be placed after a field name in a chain. \n``Discard`` is used to indicate that if a key in the input dictionary contains a particular value, this\nkey should be regarded as absent from the dictionary.\n\n.. code:: python\n\n >>> schema = Schema(\n ['name', Type(str)],\n ['address', Discard(''), Type(str)]\n )\n\n >>> schema.validate({'name': 'Marcel Bichon', 'address': ''})\n ...\n ValidationError: {'address': 'Field is missing.'}\n\nIt can prove useful to combine ``Discard`` with ``Optional``:\n\n.. code:: python\n\n >>> schema = Schema(\n ['name', Type(str)],\n ['address', Discard(''), Optional, Type(str)]\n )\n\n >>> schema.validate({'name': 'Marcel Bichon', 'address': ''})\n {'name': 'Marcel Bichon'}\n\nOr with ``Default``:\n\n.. code:: python\n\n >>> household_schema = Schema(\n ['married', Type(bool)],\n ['number of children', Discard(''), Default('0'), int, Save]\n )\n\n >>> household_schema.validate({'married': False, 'number of children': ''})\n {'married': False, 'number of children': 0}\n\nYou can decide to discard multiple values. For example:\n\n.. code:: python\n\n ['task_id', Discard('', None)]\n\nThis would discard both ``''`` and ``None``.\n\nUnexpected Keys\n~~~~~~~~~~~~~~~\n\nThe Schema constructor takes an optional ``unexpected_keys`` argument.\nIt defines what should be done with keys that don't appear in your schema.\n\nWith ``unexpected_keys=Schema.FAIL``, the schema will refuse to validate a dictionary if it \ncontains unknown keys. This is the default.\n\nWith ``unexpected_keys=Schema.KEEP``, the schema will validate a dictionary even if it \ncontains unknown keys. These unknown items will appear in the output dictionary (the dictionary \nreturned by the ``validate`` method).\n\nWith ``unexpected_keys=Schema.DELETE``, the schema will agree to validate a dictionary that\ncontains unknown keys, but these items won't appear in the output dictionary.\n\n---------------------------------\nTranslation of the error messages\n---------------------------------\n\nBuilt-in messages\n=================\n\nThe ``validate`` method of the ``Filter`` class (and its subclasses, like for example, ``Schema``),\ntakes an optional ``lang`` keyword argument.\nUse this ``lang`` keyword argument to obtain the potential error messages in the desired language.\n\n.. code:: python\n\n >>> editor_schema = Schema(\n ['name', Type(str)],\n ['website', Optional, Url]\n )\n\n >>> editor_schema.validate(\n { 'website': 'http://#' },\n lang = 'fr'\n )\n ...\n ValidationError: {'name': 'Champ manquant.', 'website': \"Ce n'est pas une url valide.\"}\n\nIf the built-in error messages are not available in the language you're looking for, submit an issue,\nor (if you feel like contributing to the project by translating the messages yourself) a pull request at https://github.com/leforestier/naval .\n\nCustom messages\n===============\n\n*Naval* translation feature relies on the *postpone* library and the *gettext* module.\nHere's how you could define customized translatable error messages.\n\n.. code:: python\n\n from postpone import LazyString as _\n\n pencil_schema = Schema(\n ['thickness',\n Type(int),\n Range(1, 100, max_message = _(\"Maximum thickness is {max}.\"))\n ],\n ['color',\n Type(str),\n Regex(\n '[0-9a-fA-F]{6}',\n error_message = _(\"This is not a valid color.\")\n )\n ]\n )\n\nYou just added two new messages that aren't translatable yet.\n\n*Naval*'s ``locale`` directory contain the translations for the standard *Naval* messages.\nYou should copy this directory. For example, if you've installed the naval library inside\n/usr/local/lib/python3.5/site-packages:\n \n $ cp -r /usr/local/lib/python3.5/site-packages/naval/locale /home/myuser/myapp/naval-locale\n\nThen add your translations to the relevant .po files and, in your application code, insert the line:\n\n.. code:: python\n\n import naval\n naval.settings.locale_directory = '/home/myuser/myapp/naval-locale'\n\nAfter that, *Naval* will search for translations in the directory ``'/home/myuser/myapp/naval-locale'``\ninstead of *Naval*'s default locale directory.\n\n.. _`table of contents`:\n\n-----------------\nTable of contents\n-----------------\n.. contents::\n\n\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/leforestier/naval", "keywords": "validation", "license": "", "maintainer": "", "maintainer_email": "", "name": "naval", "package_url": "https://pypi.org/project/naval/", "platform": "", "project_url": "https://pypi.org/project/naval/", "project_urls": { "Homepage": "https://github.com/leforestier/naval" }, "release_url": "https://pypi.org/project/naval/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "Validation library with error messages in multiple languages and a readable syntax.", "version": "1.0.0" }, "last_serial": 5359765, "releases": { "0.5.0": [ { "comment_text": "", "digests": { "md5": "08c3c559a7f20e4d420a227a3674af3e", "sha256": "b8c97c47fca2083eb93d1c5bd60e6aa59c68772a0044d222213260390df1a7ea" }, "downloads": -1, "filename": "naval-0.5.0.tar.gz", "has_sig": false, "md5_digest": "08c3c559a7f20e4d420a227a3674af3e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23755, "upload_time": "2016-01-07T22:27:43", "url": "https://files.pythonhosted.org/packages/77/f0/266d8553271ad395bc9ebb9c46d9c73657de429a13a6bd7f6ae2e3063fc3/naval-0.5.0.tar.gz" }, { "comment_text": "", "digests": { "md5": "a076c1fe671c0d0666aee2e102bf0635", "sha256": "9bed80da6b90bb547743f8e594d449a55e1833eed8125fbd1a25b470263dec5f" }, "downloads": -1, "filename": "naval-0.5.0.zip", "has_sig": false, "md5_digest": "a076c1fe671c0d0666aee2e102bf0635", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29271, "upload_time": "2016-01-07T22:27:49", "url": "https://files.pythonhosted.org/packages/1e/6d/72a3d087d022465f3a74af94d14213280ebca21f6b2ee5b1505bd0e54c47/naval-0.5.0.zip" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "56eaa88d171c9b1f8364d344195ed2e9", "sha256": "ab40ac719baa84587d4e64c2d7d4dab8adcf6aa0cd5cab50346553a40f41e09d" }, "downloads": -1, "filename": "naval-0.5.1.tar.gz", "has_sig": false, "md5_digest": "56eaa88d171c9b1f8364d344195ed2e9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23747, "upload_time": "2016-01-08T10:46:50", "url": "https://files.pythonhosted.org/packages/d8/de/a0dbe77750805d78d084d92063147ce575afa6af87749ed731bd2d8cdfe2/naval-0.5.1.tar.gz" }, { "comment_text": "", "digests": { "md5": "eb1312942ef4b84ac2b3675c2005c3d9", "sha256": "2dc5de42c477dabaa9e27fd120f5e12b4b4a7a968f5e384aadff969c09e363d1" }, "downloads": -1, "filename": "naval-0.5.1.zip", "has_sig": false, "md5_digest": "eb1312942ef4b84ac2b3675c2005c3d9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29251, "upload_time": "2016-01-08T10:46:56", "url": "https://files.pythonhosted.org/packages/96/d0/fd4c67c0f82d9e83e694605b2abbe5ec3515d9465df4459b30ef49887324/naval-0.5.1.zip" } ], "0.6.0": [ { "comment_text": "", "digests": { "md5": "2db96edcf55df0405e96a182ba0fd7ec", "sha256": "8a113f1cdbe46f110d4585f0b0d62ee16393548e5fed927de261529c4f4019dd" }, "downloads": -1, "filename": "naval-0.6.0.tar.gz", "has_sig": false, "md5_digest": "2db96edcf55df0405e96a182ba0fd7ec", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23958, "upload_time": "2017-01-15T13:03:32", "url": "https://files.pythonhosted.org/packages/39/27/9f8dbdd0583fed15430c41da6de98b9eacda37b1bea0d0ae8676acd1b67d/naval-0.6.0.tar.gz" } ], "0.6.1": [ { "comment_text": "", "digests": { "md5": "9f9364b609fc9e634cabee945051c9b3", "sha256": "7a4ae022415dcae53475a912a27e20d99b96c6bf7fdf856d748eef3480bd17a0" }, "downloads": -1, "filename": "naval-0.6.1.tar.gz", "has_sig": false, "md5_digest": "9f9364b609fc9e634cabee945051c9b3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23958, "upload_time": "2017-01-15T16:06:56", "url": "https://files.pythonhosted.org/packages/8b/78/592d1cddfb7f56734fd15be358b409fcd27fc7ab4eead67b0924c8994c97/naval-0.6.1.tar.gz" } ], "0.6.2": [ { "comment_text": "", "digests": { "md5": "9041edbd077c4f4c4dc88c1764a5218d", "sha256": "0ad784739ae08b202ad1f6ee34523ba55534790208fc181864a23cbea9fb9a4e" }, "downloads": -1, "filename": "naval-0.6.2.tar.gz", "has_sig": false, "md5_digest": "9041edbd077c4f4c4dc88c1764a5218d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24031, "upload_time": "2017-03-02T17:24:04", "url": "https://files.pythonhosted.org/packages/20/2c/fdf098c808abc440154cd90eb67c7f50bec0a23584811f9d2f711f8b241b/naval-0.6.2.tar.gz" } ], "0.7.0": [ { "comment_text": "", "digests": { "md5": "7e6dc3142577dbd2c4df8ff79d892fae", "sha256": "6d404a62727dec23b7e364b7e72696db5ab841d0f7d779845d6d4e0f5873ce61" }, "downloads": -1, "filename": "naval-0.7.0.tar.gz", "has_sig": false, "md5_digest": "7e6dc3142577dbd2c4df8ff79d892fae", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24399, "upload_time": "2017-03-22T12:43:03", "url": "https://files.pythonhosted.org/packages/ab/cf/237c793d82cd49e73a0fc456f4e9ed3a63cbfa36a41d3d7df77af787fb01/naval-0.7.0.tar.gz" } ], "0.8.0": [ { "comment_text": "", "digests": { "md5": "561c1f15b811a0cb5371eafc5535ab2f", "sha256": "304562aefab4ca67f69a8e65af8aa5523b8605677ffda77471b7f0be91f944fd" }, "downloads": -1, "filename": "naval-0.8.0.tar.gz", "has_sig": false, "md5_digest": "561c1f15b811a0cb5371eafc5535ab2f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24402, "upload_time": "2017-03-23T09:29:10", "url": "https://files.pythonhosted.org/packages/ec/34/9d93f2e0c2cffb94969ea800ec924d9551d5483ed2a195ee410919e5b8d7/naval-0.8.0.tar.gz" } ], "0.9.0": [ { "comment_text": "", "digests": { "md5": "97c34d0d266fe5066519c607d142d2f9", "sha256": "0a99226d985cb9230d6ede54d93b4c4467778ca9b6e467496635005e2313dc38" }, "downloads": -1, "filename": "naval-0.9.0.tar.gz", "has_sig": false, "md5_digest": "97c34d0d266fe5066519c607d142d2f9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24435, "upload_time": "2018-04-26T22:58:42", "url": "https://files.pythonhosted.org/packages/1b/64/c332751e09ff999f2309b974ef9df1d989d258fe2d9d50ce347cb5945d93/naval-0.9.0.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "a21353086bb081e54a8c787abbb167d2", "sha256": "a74621dcd5b36bb8920f3f907b22c17e58e25fce28f32e533324dc7ce34cae69" }, "downloads": -1, "filename": "naval-1.0.0.tar.gz", "has_sig": false, "md5_digest": "a21353086bb081e54a8c787abbb167d2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20348, "upload_time": "2019-06-04T21:32:04", "url": "https://files.pythonhosted.org/packages/3e/72/aaec535bb9b5172ede4ffcfb82af184208387be537f8a7d84554373e8804/naval-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a21353086bb081e54a8c787abbb167d2", "sha256": "a74621dcd5b36bb8920f3f907b22c17e58e25fce28f32e533324dc7ce34cae69" }, "downloads": -1, "filename": "naval-1.0.0.tar.gz", "has_sig": false, "md5_digest": "a21353086bb081e54a8c787abbb167d2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20348, "upload_time": "2019-06-04T21:32:04", "url": "https://files.pythonhosted.org/packages/3e/72/aaec535bb9b5172ede4ffcfb82af184208387be537f8a7d84554373e8804/naval-1.0.0.tar.gz" } ] }