Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ Trafaret

-----


Read The Docs hosted documentation <http://trafaret.readthedocs.org/en/latest/>
or look to the docs/api/intro.rst for start.
Ultimate transformation library that supports validation, contexts and ``aiohttp``.

Trafaret is rigid and powerful lib to work with foreign data, configs etc.
It provides simple way to check anything, and convert it accordingly to your needs.
Expand All @@ -34,6 +32,25 @@ It have shortcut syntax and ability to express anything that you can code:
raise DataError(error=errors, trafaret=self)
trafaret.DataError: {'b': DataError({1: DataError(value is not a string)})}


Read The Docs hosted documentation <http://trafaret.readthedocs.org/en/latest/>
or look to the docs/api/intro.rst for start.


New
---

* converters and ``convert=False`` are deleted in favor of ``And`` and ``&``
* ``String`` parameter ``regex`` deleted in favor of ``Regexp`` and ``RegexpRaw`` usage
* new ``OnError`` to customize error message
* ``context=something`` argument for ``__call__`` and ``check`` Trafaret methods.
Supported by ``Or``, ``And``, ``Forward`` etc.
* new customizable method ``transform`` like ``change_and_return`` but takes ``context=`` arg
* new ``trafaret_instance.async_check`` method that works with ``await``

Doc
---

For simple example what can be done:

.. code-block:: python
Expand Down
8 changes: 6 additions & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
machine:
python:
version:
3.6.0
3.6.2
post:
- pyenv local 3.6.0 2.7.10
- pyenv local 3.6.2 3.5.2 2.7.10

test:
override:
- tox

dependencies:
pre:
Expand Down
12 changes: 12 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Changelog
=========

2017-08-04
----------

- converters and ``convert=False`` are deleted in favor of ``And`` and ``&``
- ``String`` parameter ``regex`` deleted in favor of ``Regexp`` and ``RegexpRaw`` usage
- new ``OnError`` to customize error message
- ``context=something`` argument for ``__call__`` and ``check`` Trafaret methods.
Supported by ``Or``, ``And``, ``Forward`` etc.
- new customizable method ``transform`` like ``change_and_return`` but takes ``context=`` arg
- new ``trafaret_instance.async_check`` method that works with ``await``


2017-05-12
----------

Expand Down
52 changes: 24 additions & 28 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
import unittest
import trafaret as t
from collections import Mapping as AbcMapping
from trafaret import catch_error, extract_error, ignore, DataError
from trafaret import catch_error, extract_error, DataError
from trafaret.extras import KeysSubset


class TestAnyTrafaret(unittest.TestCase):
def test_any(self):
obj = object()
self.assertEqual(
(t.Any() >> ignore).check(object()),
None
t.Any().check(obj),
obj
)


Expand Down Expand Up @@ -50,14 +51,14 @@ def validator(value):

class TestCallableTrafaret(unittest.TestCase):
def test_callable(self):
(t.Callable() >> t.ignore).check(lambda: 1)
t.Callable().check(lambda: 1)
res = extract_error(t.Callable(), 1)
self.assertEqual(res, 'value is not callable')


class TestDictTrafaret(unittest.TestCase):
def test_base(self):
trafaret = t.Dict(foo=t.Int, bar=t.String) >> t.ignore
trafaret = t.Dict(foo=t.Int, bar=t.String)
trafaret.check({"foo": 1, "bar": "spam"})
res = t.extract_error(trafaret, {"foo": 1, "bar": 2})
self.assertEqual(res, {'bar': 'value is not a string'})
Expand Down Expand Up @@ -102,8 +103,8 @@ def set_trafaret(self, trafaret):
trafaret = t.Dict({
OldKey(): t.Any
})
res = trafaret.check({'testkey': 123})
self.assertEqual(res, {'testkey': 123})
with self.assertRaises(ValueError):
trafaret.check({'testkey': 123})

def test_callable_key(self):
def simple_key(value):
Expand Down Expand Up @@ -237,27 +238,27 @@ def test_dict_keys(self):

class TestEmailTrafaret(unittest.TestCase):
def test_email(self):
res = t.Email().check('someone@example.net')
res = t.Email.check('someone@example.net')
self.assertEqual(res, 'someone@example.net')
res = extract_error(t.Email(),'someone@example') # try without domain-part
res = extract_error(t.Email,'someone@example') # try without domain-part
self.assertEqual(res, 'value is not a valid email address')
res = str(t.Email().check('someone@пример.рф')) # try with `idna` encoding
res = str(t.Email.check('someone@пример.рф')) # try with `idna` encoding
self.assertEqual(res, 'someone@xn--e1afmkfd.xn--p1ai')
res = (t.Email() >> (lambda m: m.groupdict()['domain'])).check('someone@example.net')
self.assertEqual(res, 'example.net')
res = extract_error(t.Email(), 'foo')
# res = (t.Email() >> (lambda m: m.groupdict()['domain'])).check('someone@example.net')
# self.assertEqual(res, 'example.net')
res = extract_error(t.Email, 'foo')
self.assertEqual(res, 'value is not a valid email address')
res = extract_error(t.Email(), 'f' * 10000 + '@correct.domain.edu')
res = extract_error(t.Email, 'f' * 10000 + '@correct.domain.edu')
self.assertEqual(res, 'value is not a valid email address')
res = extract_error(t.Email(), 'f' * 248 + '@x.edu') == 'f' * 248 + '@x.edu'
res = extract_error(t.Email, 'f' * 248 + '@x.edu') == 'f' * 248 + '@x.edu'
self.assertEqual(res, True)
res = extract_error(t.Email(), 123)
res = extract_error(t.Email, 123)
self.assertEqual(res, 'value is not a string')


class TestEnumTrafaret(unittest.TestCase):
def test_enum(self):
trafaret = t.Enum("foo", "bar", 1) >> ignore
trafaret = t.Enum("foo", "bar", 1)
self.assertEqual(repr(trafaret), "<Enum('foo', 'bar', 1)>")
res = trafaret.check("foo")
res = trafaret.check(1)
Expand Down Expand Up @@ -476,7 +477,6 @@ def test_raise_error(self):
self.assertEqual(res, 'other error')



class TestStrBoolTrafaret(unittest.TestCase):

def test_str_bool(self):
Expand Down Expand Up @@ -522,10 +522,6 @@ def test_string(self):
self.assertEqual(res, '')
res = extract_error(t.String(), 1)
self.assertEqual(res, 'value is not a string')
res = t.String(regex='\w+').check('wqerwqer')
self.assertEqual(res, 'wqerwqer')
res = extract_error(t.String(regex='^\w+$'), 'wqe rwqer')
self.assertEqual(res, "value does not match pattern: '^\\\\w+$'")
res = t.String(min_length=2, max_length=3).check('123')
self.assertEqual(res, '123')
res = extract_error(t.String(min_length=2, max_length=6), '1')
Expand Down Expand Up @@ -611,22 +607,22 @@ class Type(type):
class TestURLTrafaret(unittest.TestCase):

def test_url(self):
res = t.URL().check('http://example.net/resource/?param=value#anchor')
res = t.URL.check('http://example.net/resource/?param=value#anchor')
self.assertEqual(res, 'http://example.net/resource/?param=value#anchor')

res = str(t.URL().check('http://пример.рф/resource/?param=value#anchor'))
res = str(t.URL.check('http://пример.рф/resource/?param=value#anchor'))
self.assertEqual(res, 'http://xn--e1afmkfd.xn--p1ai/resource/?param=value#anchor')

res = t.URL().check('http://example_underscore.net/resource/?param=value#anchor')
res = t.URL.check('http://example_underscore.net/resource/?param=value#anchor')
self.assertEqual(res, 'http://example_underscore.net/resource/?param=value#anchor')

res = str(t.URL().check('http://user@example.net/resource/?param=value#anchor'))
res = str(t.URL.check('http://user@example.net/resource/?param=value#anchor'))
self.assertEqual(res, 'http://user@example.net/resource/?param=value#anchor')

res = str(t.URL().check('http://user:@example.net/resource/?param=value#anchor'))
res = str(t.URL.check('http://user:@example.net/resource/?param=value#anchor'))
self.assertEqual(res, 'http://user:@example.net/resource/?param=value#anchor')

res = str(t.URL().check('http://user:password@example.net/resource/?param=value#anchor'))
res = str(t.URL.check('http://user:password@example.net/resource/?param=value#anchor'))
self.assertEqual(res, 'http://user:password@example.net/resource/?param=value#anchor')


Expand Down
39 changes: 39 additions & 0 deletions tests/test_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
import trafaret as t


def check_context(value, context=None):
if value != context:
return t.DataError('have not context there')
return value


CONTEXT_TRAFARET = (t.String() | t.IntRaw()) & t.Any & check_context


class TestContext(unittest.TestCase):
def test_context(self):
self.assertEqual(CONTEXT_TRAFARET(123, context=123), 123)

def test_dict_context(self):
trafaret = t.Dict({
t.Key('b'): CONTEXT_TRAFARET,
})
self.assertEqual(trafaret({'b': 123}, context=123), {'b': 123})

def test_list(self):
trafaret = t.List(CONTEXT_TRAFARET)
self.assertEqual(trafaret([123], context=123), [123])

def test_tuple(self):
trafaret = t.Tuple(CONTEXT_TRAFARET)
self.assertEqual(trafaret([123], context=123), (123,))

def test_mapping(self):
trafaret = t.Mapping(CONTEXT_TRAFARET, CONTEXT_TRAFARET)
self.assertEqual(trafaret({123: 123}, context=123), {123: 123})

def test_forward(self):
trafaret = t.Forward()
trafaret << t.List(CONTEXT_TRAFARET)
self.assertEqual(trafaret([123], context=123), [123])
11 changes: 11 additions & 0 deletions tests/test_contrib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import unittest
import datetime

import trafaret as t
from trafaret.contrib.rfc_3339 import DateTime


class TestDateTime(unittest.TestCase):
def test_datetime(self):
check = DateTime()
assert check('2017-09-01 23:59') == datetime.datetime(2017, 9, 1, 23, 59)
55 changes: 55 additions & 0 deletions tests3k/test_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import asyncio
import unittest
import trafaret as t
from trafaret.lib import py3


async def check_int(value):
return value


class TestContext(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)

def tearDown(self):
self.loop.close()

def test_async_check(self):
trafaret = t.Int & int
res = self.loop.run_until_complete(trafaret.async_check('5'))
self.assertEqual(res, 5)

def test_async_call(self):
trafaret = t.Int & int & check_int
res = self.loop.run_until_complete(trafaret.async_check('5'))
self.assertEqual(res, 5)

def test_dict(self):
trafaret = t.Dict({
t.Key('b'): t.Int & check_int,
})
res = self.loop.run_until_complete(trafaret.async_check({'b': '5'}))
self.assertEqual(res, {'b': 5})

def test_list(self):
trafaret = t.List(t.Int & check_int)
res = self.loop.run_until_complete(trafaret.async_check(['5']))
self.assertEqual(res, [5])

def test_tuple(self):
trafaret = t.Tuple(t.Null, t.Int & check_int)
res = self.loop.run_until_complete(trafaret.async_check([None, '5']))
self.assertEqual(res, (None, 5))

def test_mapping(self):
trafaret = t.Mapping(t.String, t.Int & check_int)
res = self.loop.run_until_complete(trafaret.async_check({'a': '5'}))
self.assertEqual(res, {'a': 5})

def test_forward(self):
trafaret = t.Forward()
trafaret << t.List(t.Int & check_int)
res = self.loop.run_until_complete(trafaret.async_check(['5']))
self.assertEqual(res, [5])
17 changes: 15 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
[tox]
envlist = py27,py36
envlist = py27,py35,py36
whitelist_externals = {toxinidir}/utests.py

[testenv]
deps=
unittest2
flake8
pylint
pymongo
commands=python -m unittest discover {toxinidir}/tests
python-dateutil
commands=
python -m unittest discover {toxinidir}/tests


[testenv:py36]
commands=
python -m unittest discover {toxinidir}/tests
python -m unittest discover {toxinidir}/tests3k
flake8 trafaret

[flake8]
exclude = .tox,*.egg,build
max-line-length = 120
Loading