typedload/setup.py 0000744 0001750 0001750 00000001744 14551242574 013627 0 ustar salvo salvo #!/usr/bin/python3
# This file is auto generated. Do not modify
from setuptools import setup
setup(
name='typedload',
version='2.27',
description='Load and dump data from json-like format into typed data structures',
readme='README.md',
url='https://ltworf.github.io/typedload/',
author="Salvo 'LtWorf' Tomaselli",
author_email='tiposchi@tiscali.it',
license='GPL-3.0-only',
classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Typing :: Typed', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12'],
keywords='typing types mypy json schema json-schema python3 namedtuple enums dataclass pydantic',
packages=['typedload'],
package_data={"typedload": ["py.typed", "__init__.pyi"]},
)
typedload/Makefile 0000644 0001750 0001750 00000010446 14551206771 013552 0 ustar salvo salvo MINIMUM_PYTHON_VERSION=3.8
all: pypi
.PHONY: test
test:
python3 -m tests
.PHONY: mypy
mypy:
mypy --python-version=$(MINIMUM_PYTHON_VERSION) --config-file mypy.conf typedload
mypy --python-version=$(MINIMUM_PYTHON_VERSION) example.py
pyproject.toml: docs/CHANGELOG.md
./gensetup.py --$@
setup.py: docs/CHANGELOG.md README.md
./gensetup.py --$@
chmod u+x setup.py
pypi: pyproject.toml setup.py typedload
mkdir -p dist pypi
./setup.py sdist
./setup.py bdist_wheel
mv dist/typedload-`head -1 CHANGELOG`.tar.gz pypi
mv dist/*whl pypi
rmdir dist
gpg --detach-sign -a pypi/typedload-`head -1 CHANGELOG`.tar.gz
gpg --detach-sign -a pypi/typedload-`head -1 CHANGELOG`-py3-none-any.whl
# Debian needs setup and pyproject to be kept since they are in the
# dist file. However I want to clean them or they will become outdated
# and not regenerated
.PHONY: debian_clean
debian_clean:
$(RM) -r pypi
$(RM) -r .mypy_cache
$(RM) -r typedload.egg-info/
$(RM) -r .pybuild
$(RM) MANIFEST
$(RM) -r `find . -name __pycache__`
$(RM) typedload_`head -1 CHANGELOG`.orig.tar.gz
$(RM) typedload_`head -1 CHANGELOG`.orig.tar.gz.asc
$(RM) -r deb-pkg
$(RM) -r html
$(RM) -r perftest.output
$(RM) docs/*_docgen.md
.PHONY: clean
clean: debian_clean
$(RM) setup.py
$(RM) pyproject.toml
.PHONY: dist
dist: clean setup.py pyproject.toml
cd ..; tar -czvvf typedload.tar.gz \
typedload/setup.py \
typedload/Makefile \
typedload/tests \
typedload/docs \
typedload/docgen \
typedload/mkdocs.yml \
typedload/LICENSE \
typedload/CONTRIBUTING.md \
typedload/CHANGELOG \
typedload/README.md \
typedload/example.py \
typedload/mypy.conf \
typedload/pyproject.toml \
typedload/typedload
mv ../typedload.tar.gz typedload_`./setup.py --version`.orig.tar.gz
gpg --detach-sign -a *.orig.tar.gz
.PHONY: upload
upload: pypi
twine upload --username __token__ --password `cat .token` pypi/*
deb-pkg: dist
mv typedload_`./setup.py --version`.orig.tar.gz* /tmp
cd /tmp; tar -xf typedload_*.orig.tar.gz
cp -r debian /tmp/typedload/
cd /tmp/typedload/; dpkg-buildpackage --changes-option=-S
mkdir deb-pkg
mv /tmp/typedload_* /tmp/python3-typedload*.deb deb-pkg
$(RM) -r /tmp/typedload
lintian --pedantic -E --color auto -i -I deb-pkg/*.changes deb-pkg/*.deb
docs/typedload_docgen.md: typedload/__init__.py
./docgen $@
docs/typedload.dataloader_docgen.md: typedload/dataloader.py
./docgen $@
docs/typedload.datadumper_docgen.md: typedload/datadumper.py
./docgen $@
docs/typedload.exceptions_docgen.md: typedload/exceptions.py
./docgen $@
docs/typedload.typechecks_docgen.md: typedload/typechecks.py
./docgen $@
html: \
docs/*.svg \
docs/CHANGELOG.md \
docs/CODE_OF_CONDUCT.md \
docs/comparisons.md \
docs/CONTRIBUTING.md \
docs/deferred_evaluation.md \
docs/docs \
docs/docs/gpl3logo.png \
docs/errors.md \
docs/examples.md \
docs/gpl3logo.png \
docs/origin_story.md \
docs/performance.md \
docs/README.md \
docs/SECURITY.md \
docs/supported_types.md \
docs/typedload.datadumper_docgen.md \
docs/typedload.dataloader_docgen.md \
docs/typedload_docgen.md \
docs/typedload.exceptions_docgen.md \
docs/typedload.typechecks_docgen.md \
mkdocs.yml
mkdocs build
# Download cloudflare crap
mkdir -p html/cdn
cd html/cdn; wget --continue `cat ../*html | grep cloudflare | grep min.css | sort | uniq | cut -d\" -f4`
cd html/cdn; wget --continue `cat ../*html | grep cloudflare | grep min.js | sort | uniq | cut -d\" -f2`
# Fix html pages
for page in html/*.html; do \
sed -i 's,https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/github.min.css,cdn/github.min.css,g' $${page}; \
sed -i 's,https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js,cdn/highlight.min.js,g' $${page}; \
echo "" >> $${page}; \
done
.PHONY: publish_html
publish_html: html
git checkout gh-pages
rm -rf cdn css docs fonts img js search
mv html/* .
git add cdn css docs fonts img js search
git add `git status --porcelain | grep '^ M' | cut -d\ -f3`
git commit -m "Deployed manually to workaround MkDocs"
git push
git checkout -
perftest.output/perf.p:
@echo export MOREVERSIONS=1 to compare more versions
perftest/performance.py
.PHONY: gnuplot
gnuplot: perftest.output/perf.p
cd "perftest.output"; gnuplot -persist -c perf.p
typedload/tests/ 0000755 0001750 0001750 00000000000 14551242412 013237 5 ustar salvo salvo typedload/tests/test_attrload.py 0000644 0001750 0001750 00000021132 14377476050 016476 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2021 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union
import unittest
import sys
from attr import attrs, attrib, define, field
from typedload import load, dump, exceptions, typechecks
from typedload import datadumper
class Hair(Enum):
BROWN = 'brown'
BLACK = 'black'
BLONDE = 'blonde'
WHITE = 'white'
@attrs
class Person:
name = attrib(default='Turiddu', type=str)
address = attrib(type=Optional[str], default=None)
@attrs
class DetailedPerson(Person):
hair = attrib(type=Hair, default=Hair.BLACK)
@attrs
class Students:
course = attrib(type=str)
students = attrib(type=List[Person])
@attrs
class Mangle:
value = attrib(type=int, metadata={'name': 'va.lue'})
class TestAttrDump(unittest.TestCase):
def test_basicdump(self):
assert dump(Person()) == {}
assert dump(Person('Alfio')) == {'name': 'Alfio'}
assert dump(Person('Alfio', '33')) == {'name': 'Alfio', 'address': '33'}
def test_norepr(self):
@attrs
class A:
i = attrib(type=int)
j = attrib(type=int, repr=False)
assert dump(A(1,1)) == {'i': 1}
def test_dumpdefault(self):
dumper = datadumper.Dumper()
dumper.hidedefault = False
assert dumper.dump(Person()) == {'name': 'Turiddu', 'address': None}
def test_factory_dump(self):
@attrs
class A:
a = attrib(factory=list, metadata={'ciao': 'ciao'}, type=List[int])
assert dump(A()) == {}
assert dump(A(), hidedefault=False) == {'a': []}
def test_nesteddump(self):
assert dump(
Students('advanced coursing', [
Person('Alfio'),
Person('Carmelo', 'via mulino'),
])) == {
'course': 'advanced coursing',
'students': [
{'name': 'Alfio'},
{'name': 'Carmelo', 'address': 'via mulino'},
]
}
class TestAttrload(unittest.TestCase):
def test_condition(self):
assert typechecks.is_attrs(Person)
assert typechecks.is_attrs(Students)
assert typechecks.is_attrs(Mangle)
assert typechecks.is_attrs(DetailedPerson)
assert not typechecks.is_attrs(int)
assert not typechecks.is_attrs(List[int])
assert not typechecks.is_attrs(Union[str, int])
assert not typechecks.is_attrs(Tuple[str, int])
def test_basicload(self):
assert load({'name': 'gino'}, Person) == Person('gino')
assert load({}, Person) == Person('Turiddu')
def test_nestenum(self):
assert load({'hair': 'white'}, DetailedPerson) == DetailedPerson(hair=Hair.WHITE)
def test_nested(self):
assert load(
{
'course': 'advanced coursing',
'students': [
{'name': 'Alfio'},
{'name': 'Carmelo', 'address': 'via mulino'},
]
},
Students,
) == Students('advanced coursing', [
Person('Alfio'),
Person('Carmelo', 'via mulino'),
])
def test_uuid(self):
import uuid
@attrs
class A:
a = attrib(type=int)
uuid_value = attrib(type=str, init=False)
def __attrs_post_init__(self):
self.uuid_value = str(uuid.uuid4())
assert type(load({'a': 1}, A).uuid_value) == str
assert load({'a': 1}, A) != load({'a': 1}, A)
class TestMangling(unittest.TestCase):
def test_mangle_extra(self):
@attrs
class Mangle:
value = attrib(metadata={'name': 'Value'}, type=int)
assert load({'value': 12, 'Value': 12}, Mangle) == Mangle(12)
with self.assertRaises(exceptions.TypedloadValueError):
load({'value': 12, 'Value': 12}, Mangle, failonextra=True)
def test_load_metanames(self):
a = {'va.lue': 12}
b = a.copy()
assert load(a, Mangle) == Mangle(12)
assert a == b
def test_case(self):
@attrs
class Mangle:
value = attrib(type = int, metadata={'name': 'Value'})
assert load({'Value': 1}, Mangle) == Mangle(1)
assert 'Value' in dump(Mangle(1))
with self.assertRaises(TypeError):
load({'value': 1}, Mangle)
def test_dump_metanames(self):
assert dump(Mangle(12)) == {'va.lue': 12}
def test_mangle_rename(self):
@attrs
class Mangle:
a = attrib(type=int, metadata={'name': 'b'})
b = attrib(type=str, metadata={'name': 'a'})
assert load({'b': 1, 'a': 'ciao'}, Mangle) == Mangle(1, 'ciao')
assert dump(Mangle(1, 'ciao')) == {'b': 1, 'a': 'ciao'}
def test_weird_mangle(self):
@attrs
class Mangle:
a = attrib(type=int, metadata={'name': 'b', 'alt': 'q'})
b = attrib(type=str, metadata={'name': 'a'})
assert load({'b': 1, 'a': 'ciao'}, Mangle) == Mangle(1, 'ciao')
assert load({'q': 1, 'b': 'ciao'}, Mangle, mangle_key='alt') == Mangle(1, 'ciao')
assert dump(Mangle(1, 'ciao')) == {'b': 1, 'a': 'ciao'}
assert dump(Mangle(1, 'ciao'), mangle_key='alt') == {'q': 1, 'b': 'ciao'}
def test_correct_exception_when_mangling(self):
@attrs
class A:
a = attrib(type=str, metadata={'name': 'q'})
with self.assertRaises(exceptions.TypedloadAttributeError):
load(1, A)
class TestAttrExceptions(unittest.TestCase):
def test_wrongtype_simple(self):
try:
load(3, Person)
except exceptions.TypedloadAttributeError:
pass
def test_wrongtype_nested(self):
data = {
'course': 'how to be a corsair',
'students': [
{'name': 'Alfio'},
3
]
}
try:
load(data, Students)
except exceptions.TypedloadAttributeError as e:
assert e.trace[-1].annotation[1] == 1
def test_index(self):
try:
load(
{
'course': 'advanced coursing',
'students': [
{'name': 'Alfio'},
{'name': 'Carmelo', 'address': 'via mulino'},
[],
]
},
Students,
)
except Exception as e:
assert e.trace[-2].annotation[1] == 'students'
assert e.trace[-1].annotation[1] == 2
class TestAttrConverter(unittest.TestCase):
def test_old_style_int_conversion_any(self):
@attrs
class C:
a: int = attrib(converter=int)
b: int = attrib()
assert load({'a': '1', 'b': 1}, C) == C(1, 1)
with self.assertRaises(ValueError):
load({'a': 'a', 'b': 1}, C)
def test_new_style_int_conversion_any(self):
@define
class C:
a: int = field(converter=int)
b: int
assert load({'a': '1', 'b': 1}, C) == C(1, 1)
with self.assertRaises(ValueError):
load({'a': 'a', 'b': 1}, C)
def test_typed_conversion(self):
if sys.version_info.minor < 8:
# Skip for older than 3.8
return
from typing import Literal
@define
class A:
type: Literal['A']
value: int
@define
class B:
type: Literal['B']
value: str
def conv(param: Union[A, B]) -> B:
if isinstance(param, B):
return param
return B('B', str(param.value))
@define
class Outer:
inner: B = field(converter=conv)
v = load({'inner': {'type': 'A', 'value': 33}}, Outer)
assert v.inner.type == 'B'
assert v.inner.value == '33'
v = load({'inner': {'type': 'B', 'value': '33'}}, Outer)
assert v.inner.type == 'B'
assert v.inner.value == '33'
typedload/tests/test_dataloader.py 0000644 0001750 0001750 00000050003 14551206771 016756 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2023 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
import argparse
from enum import Enum
from ipaddress import IPv4Address, IPv6Address, IPv6Network, IPv4Network, IPv4Interface, IPv6Interface
from pathlib import Path
import re
import sys
import typing
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union, Any, NewType, FrozenSet
import unittest
from typedload import dataloader, load, exceptions
class TestRealCase(unittest.TestCase):
def test_stopboard(self):
class VehicleType(Enum):
ST = 'ST'
TRAM = 'TRAM'
BUS = 'BUS'
WALK = 'WALK'
BOAT = 'BOAT'
class BoardItem(NamedTuple):
name: str
type: VehicleType
date: str
time: str
stop: str
stopid: str
journeyid: str
sname: Optional[str] = None
track: str = ''
rtDate: Optional[str] = None
rtTime: Optional[str] = None
direction: Optional[str] = None
accessibility: str = ''
bgColor: str = '#0000ff'
fgColor: str = '#ffffff'
stroke: Optional[str] = None
night: bool = False
c = {
'JourneyDetailRef': {'ref': 'https://api.vasttrafik.se/bin/rest.exe/v2/journeyDetail?ref=859464%2F301885%2F523070%2F24954%2F80%3Fdate%3D2018-04-08%26station_evaId%3D5862002%26station_type%3Ddep%26format%3Djson%26'},
'accessibility': 'wheelChair',
'bgColor': '#00394d',
'date': '2018-04-08',
'direction': 'Kortedala',
'fgColor': '#fa8719',
'journeyid': '9015014500604285',
'name': 'Spårvagn 6',
'rtDate': '2018-04-08',
'rtTime': '12:27',
'sname': '6',
'stop': 'SKF, Göteborg',
'stopid': '9022014005862002',
'stroke': 'Solid',
'time': '12:17',
'track': 'B',
'type': 'TRAM'
}
loader = dataloader.Loader()
loader.load(c, BoardItem)
class TestStrconstructed(unittest.TestCase):
def test_load_strconstructed(self):
loader = dataloader.Loader()
class Q:
def __init__(self, p):
self.param = p
loader.strconstructed.add(Q)
data = loader.load('42', Q)
assert data.param == '42'
class TestUnion(unittest.TestCase):
def test_json(self):
'''
This test would normally be flaky, but with the scoring of
types in union, it should always work.
'''
Json = Union[int, float, str, bool, None, List['Json'], Dict[str, 'Json']]
data = [{},[]]
loader = dataloader.Loader()
loader.basiccast = False
loader.frefs = {'Json' : Json}
assert loader.load(data, Json) == data
def test_str_obj(self):
'''
Possibly flaky test. Testing automatic type sorting in Union
It depends on python internal magic of sorting the union types
'''
loader = dataloader.Loader()
class Q(NamedTuple):
a: int
expected = Q(12)
for _ in range(5000):
t = eval('Union[str, Q]')
assert loader.load({'a': 12}, t) == expected
def test_ComplicatedUnion(self):
class A(NamedTuple):
a: int
class B(NamedTuple):
a: str
class C(NamedTuple):
val: Union[A, B]
loader = dataloader.Loader()
loader.basiccast = False
assert type(loader.load({'val': {'a': 1}}, C).val) == A
assert type(loader.load({'val': {'a': '1'}}, C).val) == B
def test_optional(self):
loader = dataloader.Loader()
assert loader.load(1, Optional[int]) == 1
assert loader.load(None, Optional[int]) == None
assert loader.load('1', Optional[int]) == 1
with self.assertRaises(ValueError):
loader.load('ciao', Optional[int])
loader.basiccast = False
loader.load('1', Optional[int])
def test_union(self):
loader = dataloader.Loader()
loader.basiccast = False
assert loader.load(1, Optional[Union[int, str]]) == 1
assert loader.load('a', Optional[Union[int, str]]) == 'a'
assert loader.load(None, Optional[Union[int, str]]) == None
assert type(loader.load(1, Optional[Union[int, float]])) == int
assert type(loader.load(1.0, Optional[Union[int, float]])) == float
with self.assertRaises(ValueError):
loader.load('', Optional[Union[int, float]])
loader.basiccast = True
assert type(loader.load(1, Optional[Union[int, float]])) == int
assert type(loader.load(1.0, Optional[Union[int, float]])) == float
assert loader.load(None, Optional[str]) is None
def test_debug_union(self):
loader = dataloader.Loader()
class A(NamedTuple):
a: int
class B(NamedTuple):
a: int
assert isinstance(loader.load({'a': 1}, Union[A, B]), (A, B))
loader.uniondebugconflict = True
with self.assertRaises(TypeError):
loader.load({'a': 1}, Union[A, B])
class TestFastIterableLoad(unittest.TestCase):
def yielder(self):
yield from range(2)
yield "1"
def test_tupleload_from_generator_with_exception(self):
loader = dataloader.Loader(basiccast=False)
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), Tuple[int, ...])
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), Tuple[Union[float, int], ...])
loader = dataloader.Loader(basiccast=True)
assert loader.load(self.yielder(), Tuple[int, ...]) == (0, 1, 1)
assert loader.load(self.yielder(), Tuple[Union[float, int], ...]) == (0, 1, 1)
assert loader.load(self.yielder(), Tuple[Union[str, int], ...]) == (0, 1, '1')
def test_listload_from_generator_with_exception(self):
loader = dataloader.Loader(basiccast=False)
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), List[int])
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), List[Union[int, float]])
loader = dataloader.Loader(basiccast=True)
assert loader.load(self.yielder(), List[int]) == [0, 1, 1]
assert loader.load(self.yielder(), List[Union[float, int]]) == [0, 1, 1]
assert loader.load(self.yielder(), List[Union[int, str]]) == [0, 1, "1"]
def test_frozensetload_from_generator_with_exception(self):
loader = dataloader.Loader(basiccast=False)
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), FrozenSet[int])
loader = dataloader.Loader(basiccast=True)
assert loader.load(self.yielder(), FrozenSet[int]) == frozenset((0, 1, 1))
def test_setload_from_generator_with_exception(self):
loader = dataloader.Loader(basiccast=False)
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), Set[int])
with self.assertRaises(exceptions.TypedloadValueError):
a = loader.load(self.yielder(), Set[Union[int, float]])
loader = dataloader.Loader(basiccast=True)
assert loader.load(self.yielder(), Set[int]) == {0, 1, 1}
assert loader.load(self.yielder(), Set[Union[float, int]]) == {0, 1, 1}
assert loader.load(self.yielder(), Set[Union[int, str]]) == {0, 1, "1"}
class TestTupleLoad(unittest.TestCase):
def test_ellipsis(self):
loader = dataloader.Loader()
l = list(range(33))
t = tuple(l)
assert loader.load(l, Tuple[int, ...]) == t
assert loader.load('abc', Tuple[str, ...]) == ('a', 'b', 'c')
assert loader.load('a', Tuple[str, ...]) == ('a', )
def test_tuple(self):
loader = dataloader.Loader()
with self.assertRaises(ValueError):
assert loader.load([1], Tuple[int, int]) == (1, 2)
assert loader.load([1, 2, 3], Tuple[int, int]) == (1, 2)
loader.failonextra = True
# Now the same will fail
with self.assertRaises(ValueError):
loader.load([1, 2, 3], Tuple[int, int]) == (1, 2)
class TestNamedTuple(unittest.TestCase):
def test_simple(self):
class A(NamedTuple):
a: int
b: str
loader = dataloader.Loader()
r = A(1,'1')
assert loader.load({'a': 1, 'b': 1}, A) == r
assert loader.load({'a': 1, 'b': 1, 'c': 3}, A) == r
loader.failonextra = True
with self.assertRaises(ValueError):
loader.load({'a': 1, 'b': 1, 'c': 3}, A)
def test_simple_defaults(self):
class A(NamedTuple):
a: int = 1
b: str = '1'
loader = dataloader.Loader()
r = A(1,'1')
assert loader.load({}, A) == r
def test_nested(self):
class A(NamedTuple):
a: int
class B(NamedTuple):
a: A
loader = dataloader.Loader()
r = B(A(1))
assert loader.load({'a': {'a': 1}}, B) == r
with self.assertRaises(TypeError):
loader.load({'a': {'a': 1}}, A)
def test_fail(self):
class A(NamedTuple):
a: int
q: str
loader = dataloader.Loader()
with self.assertRaises(ValueError):
loader.load({'a': 3}, A)
class TestEnum(unittest.TestCase):
def test_load_difficult_enum(self):
class TestEnum(Enum):
A: int = 1
B: Tuple[int,int,int] = (1, 2, 3)
loader = dataloader.Loader()
assert loader.load(1, TestEnum) == TestEnum.A
assert loader.load((1, 2, 3), TestEnum) == TestEnum.B
assert loader.load([1, 2, 3], TestEnum) == TestEnum.B
assert loader.load([1, 2, 3, 4], TestEnum) == TestEnum.B
loader.failonextra = True
with self.assertRaises(ValueError):
loader.load([1, 2, 3, 4], TestEnum)
def test_load_enum(self):
loader = dataloader.Loader()
class TestEnum(Enum):
LABEL1 = 1
LABEL2 = '2'
assert loader.load(1, TestEnum) == TestEnum.LABEL1
assert loader.load('2', TestEnum) == TestEnum.LABEL2
with self.assertRaises(ValueError):
loader.load(2, TestEnum)
assert loader.load(['2', 1], Tuple[TestEnum, TestEnum]) == (TestEnum.LABEL2, TestEnum.LABEL1)
class TestForwardRef(unittest.TestCase):
def test_known_refs(self):
class Node(NamedTuple):
value: int = 1
next: Optional['Node'] = None
l = {'next': {}, 'value': 12}
loader = dataloader.Loader()
assert loader.load(l, Node) == Node(value=12,next=Node())
def test_disable(self):
class A(NamedTuple):
i: 'int'
loader = dataloader.Loader(frefs=None)
with self.assertRaises(Exception):
loader.load(3, A)
def test_add_fref(self):
class A(NamedTuple):
i: 'alfio'
loader = dataloader.Loader()
with self.assertRaises(ValueError):
loader.load({'i': 3}, A)
loader.frefs['alfio'] = int
assert loader.load({'i': 3}, A) == A(3)
class TestLoaderIndex(unittest.TestCase):
def test_removal(self):
loader = dataloader.Loader()
assert loader.load(3, int) == 3
loader = dataloader.Loader()
loader.handlers.pop(loader.index(int))
with self.assertRaises(TypeError):
loader.load(3, int)
class TestExceptions(unittest.TestCase):
def test_dict_is_not_list(self):
loader = dataloader.Loader()
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load({1: 1}, List[int])
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load({1: 1}, Tuple[int, ...])
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load({1: 1}, Set[int])
def test_dict_exception(self):
loader = dataloader.Loader()
with self.assertRaises(exceptions.TypedloadAttributeError):
loader.load(None, Dict[int, int])
def test_index(self):
loader = dataloader.Loader()
try:
loader.load([1, 2, 3, 'q'], List[int])
except Exception as e:
assert e.trace[-1].annotation[1] == 3
try:
loader.load(['q', 2], Tuple[int,int])
except Exception as e:
assert e.trace[-1].annotation[1] == 0
try:
loader.load({'q': 1}, Dict[int,int])
except Exception as e:
assert e.trace[-1].annotation[1] == 'q'
def test_attrname(self):
class A(NamedTuple):
a: int
class B(NamedTuple):
a: A
b: int
loader = dataloader.Loader()
try:
loader.load({'a': 'q'}, A)
except Exception as e:
assert e.trace[-1].annotation[1] == 'a'
try:
loader.load({'a':'q','b': {'a': 1}}, B)
except Exception as e:
assert e.trace[-1].annotation[1] == 'a'
try:
loader.load({'a':3,'b': {'a': 'q'}}, B)
except Exception as e:
assert e.trace[-1].annotation[1] == 'a'
def test_typevalue(self):
loader = dataloader.Loader()
try:
loader.load([1, 2, 3, 'q'], List[int])
except Exception as e:
assert e.value == 'q'
assert e.type_ == int
class TestDictEquivalence(unittest.TestCase):
def test_namespace(self):
loader = dataloader.Loader()
data = argparse.Namespace(a=12, b='33')
class A(NamedTuple):
a: int
b: int
c: int = 1
assert loader.load(data, A) == A(12, 33, 1)
assert loader.load(data, Dict[str, int]) == {'a': 12, 'b': 33}
def test_nonamespace(self):
loader = dataloader.Loader(dictequivalence=False)
data = argparse.Namespace(a=1)
with self.assertRaises(AttributeError):
loader.load(data, Dict[str, int])
class TestCommonTypes(unittest.TestCase):
def test_path(self):
loader = dataloader.Loader()
assert loader.load('/', Path) == Path('/')
def test_pattern_str(self):
loader = dataloader.Loader()
if sys.version_info[:2] <= (3, 8):
with self.assertRaises(TypeError):
assert loader.load(r'[bc](at|ot)\d+', re.Pattern[str])
else:
assert loader.load(r'[bc](at|ot)\d+', re.Pattern[str]) == re.compile(r'[bc](at|ot)\d+')
assert loader.load(r'[bc](at|ot)\d+', typing.Pattern[str]) == re.compile(r'[bc](at|ot)\d+')
def test_pattern_bytes(self):
loader = dataloader.Loader()
if sys.version_info[:2] <= (3, 8):
with self.assertRaises(TypeError):
assert loader.load(br'[bc](at|ot)\d+', re.Pattern[bytes])
else:
assert loader.load(br'[bc](at|ot)\d+', re.Pattern[bytes]) == re.compile(br'[bc](at|ot)\d+')
assert loader.load(br'[bc](at|ot)\d+', typing.Pattern[bytes]) == re.compile(br'[bc](at|ot)\d+')
def test_pattern(self):
loader = dataloader.Loader()
assert loader.load(r'[bc](at|ot)\d+', re.Pattern) == re.compile(r'[bc](at|ot)\d+')
assert loader.load(br'[bc](at|ot)\d+', re.Pattern) == re.compile(br'[bc](at|ot)\d+')
assert loader.load(r'[bc](at|ot)\d+', typing.Pattern) == re.compile(r'[bc](at|ot)\d+')
assert loader.load(br'[bc](at|ot)\d+', typing.Pattern) == re.compile(br'[bc](at|ot)\d+')
# Right type, invalid value
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(r'((((((', re.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(br'((((((', re.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(r'((((((', typing.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(br'((((((', typing.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(r'(?P[bc])(?P(at|ot))\d+', re.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(br'(?P[bc])(?P(at|ot))\d+', re.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(r'(?P[bc])(?P(at|ot))\d+', typing.Pattern)
with self.assertRaises(exceptions.TypedloadException) as e:
assert loader.load(br'(?P[bc])(?P(at|ot))\d+', typing.Pattern)
# Wrong type
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(33, re.Pattern)
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(33, typing.Pattern)
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(False, re.Pattern)
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(False, typing.Pattern)
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(None, re.Pattern)
with self.assertRaises(exceptions.TypedloadTypeError) as e:
assert loader.load(None, typing.Pattern)
def test_ipaddress(self):
loader = dataloader.Loader()
assert loader.load('10.10.10.1', IPv4Address) == IPv4Address('10.10.10.1')
assert loader.load('10.10.10.1', IPv4Network) == IPv4Network('10.10.10.1/32')
assert loader.load('10.10.10.1', IPv4Interface) == IPv4Interface('10.10.10.1/32')
assert loader.load('fe80::123', IPv6Address) == IPv6Address('fe80::123')
assert loader.load('10.10.10.0/24', IPv4Network) == IPv4Network('10.10.10.0/24')
assert loader.load('fe80::/64', IPv6Network) == IPv6Network('fe80::/64')
assert loader.load('10.10.10.1/24', IPv4Interface) == IPv4Interface('10.10.10.1/24')
assert loader.load('fe80::123/64', IPv6Interface) == IPv6Interface('fe80::123/64')
# Wrong IP version
with self.assertRaises(ValueError):
loader.load('10.10.10.1', IPv6Address)
with self.assertRaises(ValueError):
loader.load('fe80::123', IPv4Address)
with self.assertRaises(ValueError):
loader.load('10.10.10.0/24', IPv6Network)
with self.assertRaises(ValueError):
loader.load('fe80::123', IPv4Network)
with self.assertRaises(ValueError):
loader.load('10.10.10.1/24', IPv6Interface)
with self.assertRaises(ValueError):
loader.load('fe80::123/64', IPv4Interface)
# Wrong ipaddress type
with self.assertRaises(ValueError):
loader.load('10.10.10.1/24', IPv4Address)
with self.assertRaises(ValueError):
loader.load('10.10.10.1/24', IPv4Network)
class TestAny(unittest.TestCase):
def test_any(self):
loader = dataloader.Loader()
o = object()
assert loader.load(o, Any) is o
class TestNewType(unittest.TestCase):
def test_newtype(self):
loader = dataloader.Loader()
Foo = NewType("Foo", str)
bar = loader.load("bar", Foo)
assert bar == "bar"
assert type(bar) is str
typedload/tests/test_datetime.py 0000644 0001750 0001750 00000010256 14450500327 016450 0 ustar salvo salvo # typedload
# Copyright (C) 2023 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
import datetime
import unittest
from typedload import load, dump, dataloader, datadumper
class TestDatetimedump(unittest.TestCase):
def test_isodatetime(self):
dumper = datadumper.Dumper(isodates=True)
assert dumper.dump(datetime.date(2011, 12, 12)) == '2011-12-12'
assert dumper.dump(datetime.time(15, 41)) == '15:41:00'
assert dumper.dump(datetime.datetime(2019, 5, 31, 12, 44, 22)) == '2019-05-31T12:44:22'
assert dumper.dump(datetime.datetime(2023, 3, 20, 7, 43, 19, 906439, tzinfo=datetime.timezone.utc)) == '2023-03-20T07:43:19.906439+00:00'
def test_datetime(self):
dumper = datadumper.Dumper()
assert dumper.dump(datetime.date(2011, 12, 12)) == [2011, 12, 12]
assert dumper.dump(datetime.time(15, 41)) == [15, 41, 0, 0]
assert dumper.dump(datetime.datetime(2019, 5, 31, 12, 44, 22)) == [2019, 5, 31, 12, 44, 22, 0]
class TestDatetimeLoad(unittest.TestCase):
def test_isoload(self):
now = datetime.datetime.now()
assert load(now.isoformat(), datetime.datetime) == now
withtz = datetime.datetime(2023, 3, 20, 7, 43, 19, 906439, tzinfo=datetime.timezone.utc)
assert load(withtz.isoformat(), datetime.datetime) == withtz
date = datetime.date(2023, 4, 1)
assert load(date.isoformat(), datetime.date) == date
time = datetime.time(23, 44, 12)
assert load(time.isoformat(), datetime.time) == time
def test_date(self):
loader = dataloader.Loader()
assert loader.load((2011, 1, 1), datetime.date) == datetime.date(2011, 1, 1)
assert loader.load((15, 33), datetime.time) == datetime.time(15, 33)
assert loader.load((15, 33, 0), datetime.time) == datetime.time(15, 33)
assert loader.load((2011, 1, 1), datetime.datetime) == datetime.datetime(2011, 1, 1)
assert loader.load((2011, 1, 1, 22), datetime.datetime) == datetime.datetime(2011, 1, 1, 22)
# Same but with lists
assert loader.load([2011, 1, 1], datetime.date) == datetime.date(2011, 1, 1)
assert loader.load([15, 33], datetime.time) == datetime.time(15, 33)
assert loader.load([15, 33, 0], datetime.time) == datetime.time(15, 33)
assert loader.load([2011, 1, 1], datetime.datetime) == datetime.datetime(2011, 1, 1)
assert loader.load([2011, 1, 1, 22], datetime.datetime) == datetime.datetime(2011, 1, 1, 22)
def test_exception(self):
loader = dataloader.Loader()
with self.assertRaises(TypeError):
loader.load((2011, ), datetime.datetime)
loader.load(33, datetime.datetime)
class TestTimedelta(unittest.TestCase):
def test_findhandlers(self):
l = dataloader.Loader()
d = datadumper.Dumper()
l.index(datetime.timedelta)
d.index(datetime.timedelta(1))
def test_dumpdelta(self):
assert dump(datetime.timedelta(0, 1)) == 1.0
assert dump(datetime.timedelta(1, 1)) == 86400 + 1
assert dump(datetime.timedelta(3, 0.1)) == 86400 * 3 + 0.1
def test_loaddelta(self):
assert load(1.0, datetime.timedelta) == datetime.timedelta(0, 1)
assert load(86400, datetime.timedelta) == datetime.timedelta(1, 0)
assert load(86400.0, datetime.timedelta) == datetime.timedelta(1, 0)
def test_loaddump(self):
for i in [(0, 1), (2,12), (9, 50), (600, 0.4), (1000, 501)]:
delta = datetime.timedelta(*i)
assert load(dump(delta), datetime.timedelta) == delta
typedload/tests/test_legacytuples_dataloader.py 0000644 0001750 0001750 00000021014 14142203534 021525 0 ustar salvo salvo # typedload
# Copyright (C) 2018 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from enum import Enum
from typing import Dict, FrozenSet, List, NamedTuple, Optional, Set, Tuple, Union
import unittest
from typedload import dataloader, load
class TestLegacy_oldsyntax(unittest.TestCase):
def test_legacyload(self):
A = NamedTuple('A', [('a', int), ('b', str)])
assert load({'a': 101, 'b': 'ciao'}, A) == A(101, 'ciao')
def test_nestedlegacyload(self):
A = NamedTuple('A', [('a', int), ('b', str)])
B = NamedTuple('B', [('a', A), ('b', List[A])])
assert load({'a': {'a': 101, 'b': 'ciao'}, 'b': []}, B) == B(A(101, 'ciao'), [])
assert load(
{'a': {'a': 101, 'b': 'ciao'}, 'b': [{'a': 1, 'b': 'a'},{'a': 0, 'b': 'b'}]},
B
) == B(A(101, 'ciao'), [A(1, 'a'),A(0, 'b')])
class TestUnion_oldsyntax(unittest.TestCase):
def test_ComplicatedUnion(self):
A = NamedTuple('A', [('a', int)])
B = NamedTuple('B', [('a', str)])
C = NamedTuple('C', [('val', Union[A, B])])
loader = dataloader.Loader()
loader.basiccast = False
assert type(loader.load({'val': {'a': 1}}, C).val) == A
assert type(loader.load({'val': {'a': '1'}}, C).val) == B
def test_optional(self):
loader = dataloader.Loader()
assert loader.load(1, Optional[int]) == 1
assert loader.load(None, Optional[int]) == None
assert loader.load('1', Optional[int]) == 1
with self.assertRaises(ValueError):
loader.load('ciao', Optional[int])
loader.basiccast = False
loader.load('1', Optional[int])
def test_union(self):
loader = dataloader.Loader()
loader.basiccast = False
assert loader.load(1, Optional[Union[int, str]]) == 1
assert loader.load('a', Optional[Union[int, str]]) == 'a'
assert loader.load(None, Optional[Union[int, str]]) == None
assert type(loader.load(1, Optional[Union[int, float]])) == int
assert type(loader.load(1.0, Optional[Union[int, float]])) == float
with self.assertRaises(ValueError):
loader.load('', Optional[Union[int, float]])
loader.basiccast = True
assert type(loader.load(1, Optional[Union[int, float]])) == int
assert type(loader.load(1.0, Optional[Union[int, float]])) == float
assert loader.load(None, Optional[str]) is None
class TestNamedTuple_oldsyntax(unittest.TestCase):
def test_simple(self):
A = NamedTuple('A', [('a', int), ('b', str)])
loader = dataloader.Loader()
r = A(1,'1')
assert loader.load({'a': 1, 'b': 1}, A) == r
assert loader.load({'a': 1, 'b': 1, 'c': 3}, A) == r
loader.failonextra = True
with self.assertRaises(ValueError):
loader.load({'a': 1, 'b': 1, 'c': 3}, A)
def test_nested(self):
A = NamedTuple('A', [('a', int)])
B = NamedTuple('B', [('a', A)])
loader = dataloader.Loader()
r = B(A(1))
assert loader.load({'a': {'a': 1}}, B) == r
with self.assertRaises(TypeError):
loader.load({'a': {'a': 1}}, A)
def test_fail(self):
A = NamedTuple('A', [('a', int), ('q', str)])
loader = dataloader.Loader()
with self.assertRaises(ValueError):
loader.load({'a': 3}, A)
class TestSet(unittest.TestCase):
def test_load_set(self):
loader = dataloader.Loader()
r = {(1, 1), (2, 2), (0, 0)}
assert loader.load(zip(range(3), range(3)), Set[Tuple[int,int]]) == r
assert loader.load([1, '2', 2], Set[int]) == {1, 2}
def test_load_frozen_set(self):
loader = dataloader.Loader()
assert loader.load(range(4), FrozenSet[float]) == frozenset((0.0, 1.0, 2.0, 3.0))
class TestDict(unittest.TestCase):
def test_load_dict(self):
loader = dataloader.Loader()
class State(Enum):
OK = 'ok'
FAILED = 'failed'
v = {'1': 'ok', '15': 'failed'}
r = {1: State.OK, 15: State.FAILED}
assert loader.load(v, Dict[int, State]) == r
def test_load_nondict(self):
class SimDict():
def items(self):
return zip(range(12), range(12))
loader = dataloader.Loader()
assert loader.load(SimDict(), Dict[str, int]) == {str(k): v for k,v in zip(range(12), range(12))}
with self.assertRaises(AttributeError):
loader.load(33, Dict[int, str])
class TestTuple(unittest.TestCase):
def test_load_list_of_tuples(self):
t = List[Tuple[str, int, Tuple[int, int]]]
v = [
['a', 12, [1, 1]],
['b', 15, [3, 2]],
]
r = [
('a', 12, (1, 1)),
('b', 15, (3, 2)),
]
loader = dataloader.Loader()
assert loader.load(v, t) == r
def test_load_nested_tuple(self):
loader = dataloader.Loader()
assert loader.load([1, 2, 3, [1, 2]], Tuple[int,int,int,Tuple[str,str]]) == (1, 2, 3, ('1', '2'))
def test_load_tuple(self):
loader = dataloader.Loader()
assert loader.load([1, 2, 3], Tuple[int,int,int]) == (1, 2, 3)
assert loader.load(['2', False, False], Tuple[int, bool]) == (2, False)
with self.assertRaises(ValueError):
loader.load(['2', False], Tuple[int, bool, bool])
loader.failonextra = True
assert loader.load(['2', False, False], Tuple[int, bool]) == (2, False)
class TestLoader(unittest.TestCase):
def test_kwargs(self):
with self.assertRaises(ValueError):
load(1, str, basiccast=False)
load(1, int, handlers=[])
class TestBasicTypes(unittest.TestCase):
def test_basic_casting(self):
# Casting enabled, by default
loader = dataloader.Loader()
assert loader.load(1, int) == 1
assert loader.load(1.1, int) == 1
assert loader.load(False, int) == 0
assert loader.load('ciao', str) == 'ciao'
assert loader.load('1', float) == 1.0
with self.assertRaises(ValueError):
loader.load('ciao', float)
def test_list_basic(self):
loader = dataloader.Loader()
assert loader.load(range(12), List[int]) == list(range(12))
assert loader.load(range(12), List[str]) == [str(i) for i in range(12)]
def test_extra_basic(self):
# Add more basic types
loader = dataloader.Loader()
with self.assertRaises(TypeError):
assert loader.load(b'ciao', bytes) == b'ciao'
loader.basictypes.add(bytes)
assert loader.load(b'ciao', bytes) == b'ciao'
def test_none_basic(self):
loader = dataloader.Loader()
loader.load(None, type(None))
with self.assertRaises(ValueError):
loader.load(12, type(None))
def test_basic_nocasting(self):
# Casting enabled, by default
loader = dataloader.Loader()
loader.basiccast = False
assert loader.load(1, int) == 1
assert loader.load(True, bool) == True
assert loader.load(1.5, float) == 1.5
with self.assertRaises(ValueError):
loader.load(1.1, int)
loader.load(False, int)
loader.load('ciao', str)
loader.load('1', float)
class TestHandlers(unittest.TestCase):
def test_custom_handler(self):
class Q:
def __eq__(self, other):
return isinstance(other, Q)
loader = dataloader.Loader()
loader.handlers.append((
lambda t: t == Q,
lambda l, v, t: Q()
))
assert loader.load('test', Q) == Q()
def test_broken_handler(self):
loader = dataloader.Loader()
loader.handlers.insert(0, (lambda t: 33 + t is None, lambda l, v, t: None))
with self.assertRaises(TypeError):
loader.load(1, int)
loader.raiseconditionerrors = False
assert loader.load(1, int) == 1
typedload/tests/test_dumpload.py 0000644 0001750 0001750 00000002721 14142203534 016455 0 ustar salvo salvo # typedload
# Copyright (C) 2018 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union
import unittest
from typedload import dump, load
class Result(Enum):
PASS = True
FAIL = False
class Student(NamedTuple):
name: str
id: int
email: Optional[str] = None
class ExamResults(NamedTuple):
results: List[Tuple[Student, Result]]
class TestDumpLoad(unittest.TestCase):
def test_dump_load_results(self):
results = ExamResults(
[
(Student('Anna', 1), Result.PASS),
(Student('Alfio', 2), Result.PASS),
(Student('Iano', 3, 'iano@iano.it'), Result.PASS),
]
)
assert load(dump(results), ExamResults) == results
typedload/tests/__main__.py 0000644 0001750 0001750 00000003102 14551206771 015335 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2023 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
import unittest
import sys
print('Running tests using %s' % sys.version)
if sys.version_info.major != 3 or sys.version_info.minor < 8:
raise Exception('Only version 3.5 and above supported')
from .test_dataloader import *
from .test_datadumper import *
from .test_dumpload import *
from .test_exceptions import *
from .test_dataclass import *
from .test_deferred import *
from .test_legacytuples_dataloader import *
from .test_typechecks import *
from .test_datetime import *
from .test_literal import *
from .test_typeddict import *
if sys.version_info.minor >= 10:
from .test_orunion import *
# Run tests for the attr plugin only if it is loaded
try:
import attr
attr_module = True
except ImportError:
attr_module = False
if attr_module:
from .test_attrload import *
if __name__ == '__main__':
unittest.main()
typedload/tests/test_typechecks.py 0000644 0001750 0001750 00000015433 14376641230 017026 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2022 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from enum import Enum
from typing import Dict, FrozenSet, List, NamedTuple, Optional, Set, Tuple, Union, Any, NewType
import unittest
import sys
if sys.version_info.minor >= 8:
from typing import Literal
from typedload import typechecks
class TestChecks(unittest.TestCase):
def test_is_not_required(self):
if sys.version_info.minor >= 11:
from typing import NotRequired
assert typechecks.is_notrequired(NotRequired[int])
assert typechecks.is_notrequired(NotRequired[str])
assert typechecks.is_notrequired(NotRequired[Union[int, str]])
assert (not typechecks.is_notrequired(None))
def test_not_required(self):
if sys.version_info.minor < 11:
# Only from 3.11
return
from typing import NotRequired
assert int == typechecks.notrequiredtype(NotRequired[int])
def test_is_literal(self):
if sys.version_info.minor >= 8:
l = Literal[1, 2, 3]
assert typechecks.is_literal(l)
assert not typechecks.is_literal(3)
assert not typechecks.is_literal(int)
assert not typechecks.is_literal(str)
assert not typechecks.is_literal(None)
assert not typechecks.is_literal(List[int])
def test_is_not_typeddict(self):
assert not typechecks.is_typeddict(int)
assert not typechecks.is_typeddict(3)
assert not typechecks.is_typeddict(str)
assert not typechecks.is_typeddict({})
assert not typechecks.is_typeddict(dict)
assert not typechecks.is_typeddict(set)
assert not typechecks.is_typeddict(None)
assert not typechecks.is_typeddict(List[str])
def test_is_list(self):
assert typechecks.is_list(List)
assert typechecks.is_list(List[int])
assert typechecks.is_list(List[str])
assert not typechecks.is_list(list)
assert not typechecks.is_list(Tuple[int, str])
assert not typechecks.is_list(Dict[int, str])
assert not typechecks.is_list([])
if sys.version_info.minor >= 9:
assert typechecks.is_list(list[str])
assert not typechecks.is_list(tuple[str])
def test_is_dict(self):
assert typechecks.is_dict(Dict[int, int])
assert typechecks.is_dict(Dict)
assert typechecks.is_dict(Dict[str, str])
assert not typechecks.is_dict(Tuple[str, str])
assert not typechecks.is_dict(Set[str])
if sys.version_info.minor >= 9:
assert typechecks.is_dict(dict[str, str])
assert not typechecks.is_dict(tuple[str])
def test_is_set(self):
assert typechecks.is_set(Set[int])
assert typechecks.is_set(Set)
if sys.version_info.minor >= 9:
assert typechecks.is_set(set[str])
assert not typechecks.is_set(tuple[str])
def test_is_frozenset_(self):
assert not typechecks.is_frozenset(Set[int])
assert typechecks.is_frozenset(FrozenSet[int])
assert typechecks.is_frozenset(FrozenSet)
if sys.version_info.minor >= 9:
assert typechecks.is_frozenset(frozenset[str])
assert not typechecks.is_frozenset(tuple[str])
def test_is_tuple(self):
assert typechecks.is_tuple(Tuple[str, int, int])
assert typechecks.is_tuple(Tuple)
assert not typechecks.is_tuple(tuple)
assert not typechecks.is_tuple((1,2))
if sys.version_info.minor >= 9:
assert typechecks.is_tuple(tuple[str])
assert not typechecks.is_tuple(list[str])
def test_is_union(self):
assert typechecks.is_union(Optional[int])
assert typechecks.is_union(Optional[str])
assert typechecks.is_union(Union[bytes, str])
assert typechecks.is_union(Union[str, int, float])
assert not typechecks.is_union(FrozenSet[int])
assert not typechecks.is_union(int)
def test_is_optional(self):
assert typechecks.is_optional(Optional[int])
assert typechecks.is_optional(Optional[str])
assert not typechecks.is_optional(Union[bytes, str])
assert not typechecks.is_optional(Union[str, int, float])
assert not typechecks.is_union(FrozenSet[int])
assert not typechecks.is_union(int)
def test_is_nonetype(self):
assert typechecks.is_nonetype(type(None))
assert not typechecks.is_nonetype(List[int])
def test_is_enum(self):
class A(Enum):
BB = 3
assert typechecks.is_enum(A)
assert not typechecks.is_enum(Set[int])
def test_is_namedtuple(self):
A = NamedTuple('A', [
('val', int),
])
assert typechecks.is_namedtuple(A)
assert not typechecks.is_namedtuple(Tuple)
assert not typechecks.is_namedtuple(tuple)
assert not typechecks.is_namedtuple(Tuple[int, int])
def test_is_forwardref(self):
try:
# Since 3.7
from typing import ForwardRef # type: ignore
except ImportError:
from typing import _ForwardRef as ForwardRef # type: ignore
assert typechecks.is_forwardref(ForwardRef('SomeType'))
def test_uniontypes(self):
assert set(typechecks.uniontypes(Optional[bool])) == {typechecks.NONETYPE, bool}
assert set(typechecks.uniontypes(Optional[int])) == {typechecks.NONETYPE, int}
assert set(typechecks.uniontypes(Optional[Union[int, float]])) == {typechecks.NONETYPE, float, int}
assert set(typechecks.uniontypes(Optional[Union[int, str, Optional[float]]])) == {typechecks.NONETYPE, str, int, float}
with self.assertRaises(AttributeError):
typechecks.uniontypes(Union[int])
def test_any(self):
assert typechecks.is_any(Any)
assert not typechecks.is_any(str)
assert not typechecks.is_any(Tuple[int, ...])
assert not typechecks.is_any(int)
assert not typechecks.is_any(List[float])
def test_isnewtype(self):
assert typechecks.is_newtype(NewType("foo", str))
assert not typechecks.is_newtype(type(NewType("foo", str)("bar")))
assert not typechecks.is_typeddict(str)
typedload/tests/test_deferred.py 0000644 0001750 0001750 00000003276 14376641230 016446 0 ustar salvo salvo # typedload
# Copyright (C) 2022 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from __future__ import annotations
from dataclasses import dataclass
from typing import NamedTuple, Optional
import unittest
from typedload import load
class A(NamedTuple):
a: Optional[int]
@dataclass
class B:
a: Optional[int]
class TestDeferred(unittest.TestCase):
'''
This test should entirely be deleted when the PEP is superseeded.
'''
def test_deferred_named_tuple(self):
assert load({'a': None}, A, pep563=True) == A(None)
assert load({'a': 3}, A, pep563=True) == A(3)
with self.assertRaises(ValueError):
load({'a': None}, A)
with self.assertRaises(ValueError):
load({'a': 3}, A)
def test_deferred_dataclass(self):
assert load({'a': None}, B, pep563=True) == B(None)
assert load({'a': 3}, B, pep563=True) == B(3)
with self.assertRaises(TypeError):
load({'a': None}, B)
with self.assertRaises(TypeError):
load({'a': 3}, B)
typedload/tests/test_datadumper.py 0000644 0001750 0001750 00000012045 14551206771 017010 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2023 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from enum import Enum
from ipaddress import IPv4Address, IPv4Network, IPv4Interface, IPv6Address, IPv6Network, IPv6Interface
from pathlib import Path
import re
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union
import unittest
from typedload import datadumper, dump, load
class EnumA(Enum):
A: int = 1
B: str = '2'
C: Tuple[int, int] = (1, 2)
class NamedA(NamedTuple):
a: int
b: str
c: str = 'no'
class TestDumpLoad(unittest.TestCase):
def test_enum(self):
assert load(dump(EnumA.C), EnumA) == EnumA.C
class TestLegacyDump(unittest.TestCase):
def test_dump(self):
A = NamedTuple('A',[('a', int), ('b', str)])
assert dump(A(1, '12')) == {'a': 1, 'b': '12'}
class TestStrconstructed(unittest.TestCase):
def test_dump_strconstructed(self):
dumper = datadumper.Dumper()
class Q:
def __str__(self):
return '42'
dumper.strconstructed.add(Q)
assert dumper.dump(Q()) == '42'
class TestBasicDump(unittest.TestCase):
def test_dump_namedtuple(self):
dumper = datadumper.Dumper()
assert dumper.dump(NamedA(1, 'a')) == {'a': 1, 'b': 'a'}
assert dumper.dump(NamedA(1, 'a', 'yes')) == {'a': 1, 'b': 'a', 'c': 'yes'}
dumper.hidedefault = False
assert dumper.dump(NamedA(1, 'a')) == {'a': 1, 'b': 'a', 'c': 'no'}
def test_dump_dict(self):
dumper = datadumper.Dumper()
assert dumper.dump({EnumA.B: 'ciao'}) == {'2': 'ciao'}
def test_dump_set(self):
dumper = datadumper.Dumper()
assert dumper.dump(set(range(3))) == [0, 1, 2]
assert dumper.dump(frozenset(range(3))) == [0, 1, 2]
def test_dump_enums(self):
dumper = datadumper.Dumper()
assert dumper.dump(EnumA.A) == 1
assert dumper.dump(EnumA.B) == '2'
assert dumper.dump(EnumA.C) == [1, 2]
def test_dump_iterables(self):
dumper = datadumper.Dumper()
assert dumper.dump([1]) == [1]
assert dumper.dump((1, 2)) == [1, 2]
assert dumper.dump([(1, 1), (0, 0)]) == [[1, 1], [0, 0]]
assert dumper.dump({1, 2}) == [1, 2]
def test_basic_types(self):
# Casting enabled, by default
dumper = datadumper.Dumper()
assert dumper.dump(1) == 1
assert dumper.dump('1') == '1'
assert dumper.dump(None) == None
dumper.basictypes = {int, str}
assert dumper.dump('1') == '1'
assert dumper.dump(1) == 1
with self.assertRaises(ValueError):
assert dumper.dump(None) == None
assert dumper.dump(True) == True
class TestHandlersDumper(unittest.TestCase):
def test_custom_handler(self):
class Q:
def __eq__(self, other):
return isinstance(other, Q)
dumper = datadumper.Dumper()
dumper.handlers.append((
lambda v: isinstance(v, Q),
lambda l, v: 12
))
assert dumper.dump(Q()) == 12
def test_broken_handler(self):
dumper = datadumper.Dumper()
dumper.handlers.insert(0, (lambda v: 'a' + v is None, lambda l, v: None))
with self.assertRaises(TypeError):
dumper.dump(1)
dumper.raiseconditionerrors = False
assert dumper.dump(1) == 1
def test_replace_handler(self):
dumper = datadumper.Dumper()
index = dumper.index([])
dumper.handlers[index] = (dumper.handlers[index][0], lambda *args: 3)
assert dumper.dump([11]) == 3
class TestDumper(unittest.TestCase):
def test_kwargs(self):
with self.assertRaises(ValueError):
dump(1, handlers=[])
class TestDumpCommonTypes(unittest.TestCase):
def test_path(self):
assert dump(Path('/')) == '/'
def test_ipaddress(self):
assert dump(IPv4Address('10.10.10.1')) == '10.10.10.1'
assert dump(IPv4Network('10.10.10.0/24')) == '10.10.10.0/24'
assert dump(IPv4Interface('10.10.10.1/24')) == '10.10.10.1/24'
assert dump(IPv6Address('fe80::123')) == 'fe80::123'
assert dump(IPv6Network('fe80::/64')) == 'fe80::/64'
assert dump(IPv6Interface('fe80::123/64')) == 'fe80::123/64'
def test_pattern(self):
assert dump(re.compile(r'[bc](at|ot)\d+')) == r'[bc](at|ot)\d+'
assert dump(re.compile(br'[bc](at|ot)\d+')) == br'[bc](at|ot)\d+'
typedload/tests/test_literal.py 0000644 0001750 0001750 00000011253 14376641230 016314 0 ustar salvo salvo # typedload
# Copyright (C) 2019-2022 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from dataclasses import dataclass
from typing import Literal, NamedTuple, TypedDict, Union
import unittest
from typedload import dataloader, load, dump, typechecks
class TestLiteralLoad(unittest.TestCase):
def test_literalvalues(self):
assert isinstance(typechecks.literalvalues(Literal[1]), set)
assert typechecks.literalvalues(Literal[1]) == {1}
assert typechecks.literalvalues(Literal[1, 1]) == {1}
assert typechecks.literalvalues(Literal[1, 2]) == {1, 2}
def test_load(self):
l = Literal[1, 2, 'a']
assert load(1, l) == 1
assert load(2, l) == 2
assert load('a', l) == 'a'
def test_fail(self):
l = Literal[1, 2, 'a']
with self.assertRaises(ValueError):
load(3, l)
def test_discriminatorliterals_wrong(self):
assert typechecks.discriminatorliterals(int) == {}
def test_discriminatorliterals_namedtuple(self):
class A(NamedTuple):
t: Literal['a', 'b']
i: int
q: str
class B(NamedTuple):
t: Literal[33]
q: Literal[12]
i: int
class C(NamedTuple):
t: Literal['a']
i: int
assert typechecks.discriminatorliterals(A) == {'t': {'a', 'b'}}
assert typechecks.discriminatorliterals(B) == {'t': {33,}, 'q': {12,}}
assert typechecks.discriminatorliterals(C) == {'t': {'a', }}
def test_discriminatorliterals_typeddict(self):
class A(TypedDict):
t: Literal['a', 'b']
i: int
q: str
class B(TypedDict):
t: Literal[33]
q: Literal[12]
i: int
class C(TypedDict):
t: Literal['a']
i: int
assert typechecks.discriminatorliterals(A) == {'t': {'a', 'b'}}
assert typechecks.discriminatorliterals(B) == {'t': {33,}, 'q': {12,}}
assert typechecks.discriminatorliterals(C) == {'t': {'a', }}
def test_discriminatorliterals_dataclass(self):
@dataclass
class A:
t: Literal['a', 'b']
i: int
q: str
@dataclass
class B:
t: Literal[33]
q: Literal[12]
i: int
@dataclass
class C:
t: Literal['a']
i: int
assert typechecks.discriminatorliterals(A) == {'t': {'a', 'b'}}
assert typechecks.discriminatorliterals(B) == {'t': {33,}, 'q': {12,}}
assert typechecks.discriminatorliterals(C) == {'t': {'a', }}
def test_discriminatorliterals_attr(self):
try:
from attr import attrs, attrib
except ImportError:
return
@attrs
class A:
t: Literal['a', 'b'] = attrib()
i: int = attrib()
q: str = attrib()
@attrs
class B:
t: Literal[33] = attrib()
q: Literal[12] = attrib()
i: int = attrib()
@attrs
class C:
t: Literal['a'] = attrib()
i: int = attrib()
assert typechecks.discriminatorliterals(A) == {'t': {'a', 'b'}}
assert typechecks.discriminatorliterals(B) == {'t': {33,}, 'q': {12,}}
assert typechecks.discriminatorliterals(C) == {'t': {'a', }}
def test_literal_sorting(self):
class A(NamedTuple):
t: Literal[1]
i: int
class B(NamedTuple):
t: Literal[2, 3]
i: int
assert load({'t': 1, 'i': 12}, Union[A, B]) == A(1, 12)
assert load({'t': 2, 'i': 12}, Union[A, B]) == B(2, 12)
assert load({'t': 3, 'i': 12}, Union[A, B]) == B(3, 12)
def test_multiple_literal_sorting(self):
class A(NamedTuple):
t: Literal[1]
u: Literal[1]
class B(NamedTuple):
t: Literal[2]
i: int
assert load({'t': 1, 'u': 1}, Union[A, B]) == A(1, 1)
assert load({'t': 2, 'i': 12}, Union[A, B]) == B(2, 12)
typedload/tests/test_exceptions.py 0000644 0001750 0001750 00000012512 14551206771 017042 0 ustar salvo salvo # typedload
# Copyright (C) 2021-2023 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
import enum
from typing import List, NamedTuple, Optional, Tuple, Set, FrozenSet
import unittest
from typedload import dataloader, load, dump, typechecks, exceptions
class Firewall(NamedTuple):
open_ports: List[int]
class Networking(NamedTuple):
nic: Optional[str]
firewall: Firewall
class Remote(NamedTuple):
networking: Networking
class Config(NamedTuple):
remote: Optional[Remote]
class Enumeration(enum.Enum):
A = 1
B = '2'
C = 3.0
class TestExceptionsStr(unittest.TestCase):
def test_tuple_exceptions(self):
try:
load(('1',), Tuple[int, ...], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.[0]'
try:
load((1, '1',), Tuple[int, ...], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.[1]'
try:
load(('1'), Tuple[int], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.[0]'
try:
load(('1', 1), Tuple[int, int], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.[0]'
try:
load((1, '1'), Tuple[int, int], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.[1]'
try:
load((1,), Tuple[int, int], basiccast=False)
except exceptions.TypedloadException as e:
assert e._path(e.trace) == '.'
def test_exceptions_str(self):
incorrect = [
{'remote': {'networking': {'nic': "eth0", "firewall": {"open_ports":[1,2,3, 'a']}}}},
{'remote': {'networking': {'nic': "eth0", "firewall": {"closed_ports": [], "open_ports":[1,2,3]}}}},
{'remote': {'networking': {'noc': "eth0", "firewall": {"open_ports":[2,3]}}}},
{'romote': {'networking': {'nic': "eth0", "firewall": {"open_ports":[2,3]}}}},
{'remote': {'nitworking': {'nic': "eth0", "firewall": {"open_ports":[2,3]}}}},
]
paths = []
for i in incorrect:
try:
load(i, Config, basiccast=False, failonextra=True)
assert False
except exceptions.TypedloadException as e:
for i in e.exceptions:
paths.append(e._path(e.trace) + '.' + i._path(i.trace[1:]))
#1st object
assert paths[0] == '.remote.networking.firewall.open_ports.[3]'
assert paths[1] == '.remote.'
#2nd object
assert paths[2] == '.remote.networking.firewall'
assert paths[3] == '.remote.'
#3rd object
assert paths[4] == '.remote.networking'
assert paths[5] == '.remote.'
#4th object
# Nothing because of no sub-exceptions, fails before the union
#5th object
assert paths[6] == '.remote.'
assert paths[7] == '.remote.'
assert len(paths) == 8
def test_tuple_exceptions_str(self):
incorrect = [
[1, 1],
[1, 1, 1],
[1],
[1, 1.2],
[1, None],
[1, None, 1],
]
for i in incorrect:
try:
load(i, Tuple[int, int], basiccast=False, failonextra=True)
except Exception as e:
str(e)
def test_enum_exceptions_str(self):
incorrect = [
[1, 1],
'3',
12,
]
for i in incorrect:
try:
load(i, Enumeration, basiccast=False, failonextra=True)
except Exception as e:
str(e)
def test_nested_wrong_type(self):
with self.assertRaises(exceptions.TypedloadException):
load([[1]], List[List[bytes]])
with self.assertRaises(exceptions.TypedloadException):
load([[1]], List[Tuple[bytes, ...]])
with self.assertRaises(exceptions.TypedloadException):
load([[1]], List[Set[bytes]])
with self.assertRaises(exceptions.TypedloadException):
load([[1]], List[FrozenSet[bytes]])
def test_notiterable_exception(self):
loader = dataloader.Loader()
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load(None, List[int])
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load(None, Tuple[int, ...])
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load(None, Set[int])
with self.assertRaises(exceptions.TypedloadTypeError):
loader.load(None, FrozenSet[int])
typedload/tests/test_typeddict.py 0000644 0001750 0001750 00000007276 14376641230 016663 0 ustar salvo salvo # typedload
# Copyright (C) 2019-2021 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from typing import TypedDict
import unittest
import sys
from typedload import dataloader, load, dump, typechecks
class Person(TypedDict):
name: str
age: float
class A(TypedDict):
val: str
class B(TypedDict, total=False):
val: str
class C(A, total=False):
vel: int
class TestTypeddictLoad(unittest.TestCase):
def test_mixed_totality(self):
if sys.version_info.minor == 8:
# This only works from 3.9
return
with self.assertRaises(ValueError):
load({}, C)
assert load({'val': 'a'}, C) == {'val': 'a'}
with self.assertRaises(ValueError):
load({'val': 'a', 'vel': 'q'}, C)
assert load({'val': 'a', 'vel': 1}, C) == {'val': 'a', 'vel': 1}
assert load({'val': 'a', 'vel': '1'}, C) == {'val': 'a', 'vel': 1}
assert load({'val': 'a','vil': 2}, C) == {'val': 'a'}
with self.assertRaises(ValueError):
load({'val': 'a','vil': 2}, C, failonextra=True)
def test_totality(self):
with self.assertRaises(ValueError):
load({}, A)
assert load({}, B) == {}
assert load({'val': 'a'}, B) == {'val': 'a'}
assert load({'vel': 'q'}, B) == {}
with self.assertRaises(ValueError):
load({'vel': 'q'}, B, failonextra=True)
def test_loadperson(self):
o = {'name': 'pino', 'age': 1.1}
assert load(o, Person) == o
assert load({'val': 3}, A) == {'val': '3'}
assert load({'val': 3, 'vil': 4}, A) == {'val': '3'}
with self.assertRaises(ValueError):
o.pop('age')
load(o, Person)
with self.assertRaises(ValueError):
load({'val': 3, 'vil': 4}, A, failonextra=True)
def test_is_typeddict(self):
assert typechecks.is_typeddict(A)
assert typechecks.is_typeddict(Person)
assert typechecks.is_typeddict(B)
if sys.version_info.minor >= 11:
# NotRequired is present from 3.11
from typing import NotRequired
class TestNotRequired(unittest.TestCase):
def test_standard(self):
class A(TypedDict):
i: int
o: NotRequired[int]
assert load({'i': 1}, A) == {'i': 1}
assert load({'i': 1, 'o': 2}, A) == {'i': 1, 'o': 2}
def test_nontotal(self):
class A(TypedDict, total = False):
i: int
o: NotRequired[int]
assert load({}, A) == {}
assert load({'i': 1}, A) == {'i': 1}
assert load({'i': 1, 'o': 2}, A) == {'i': 1, 'o': 2}
def test_mixtotal(self):
class A(TypedDict):
a: int
b: NotRequired[int]
class B(A, total=False):
c: int
d: NotRequired[int]
with self.assertRaises(ValueError):
load({}, B)
assert load({'a': 1}, B) == {'a': 1}
assert load({'a': 1, 'd':12}, B) == {'a': 1, 'd': 12}
typedload/tests/test_dataclass.py 0000644 0001750 0001750 00000013352 14376641230 016621 0 ustar salvo salvo # typedload
# Copyright (C) 2018-2021 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union
import unittest
from typedload import dataloader, load, dump, typechecks, exceptions
class TestDataclassLoad(unittest.TestCase):
def test_do_not_init(self):
@dataclass
class Q:
a: int
b: int
c: int = field(init=False)
def __post_init__(self):
self.c = self.a + self.b
assert typechecks.is_dataclass(Q)
assert load({'a': 1, 'b': 2}, Q).c == 3
a = load({'a': 12, 'b': 30}, Q)
assert a.c == 42
a.c = 1
assert a.c == 1
assert a.a == 12
assert a.b == 30
def test_is_dataclass(self):
@dataclass
class A:
pass
class B(NamedTuple):
pass
assert typechecks.is_dataclass(A)
assert not typechecks.is_dataclass(List[int])
assert not typechecks.is_dataclass(Tuple[int, int])
assert not typechecks.is_dataclass(B)
def test_factory_load(self):
@dataclass
class A:
a: List[int] = field(default_factory=list)
assert load({'a': [1, 2, 3]}, A) == A([1, 2, 3])
assert load({'a': []}, A) == A()
assert load({}, A) == A()
def test_load(self):
@dataclass
class A:
a: int
b: str
assert load({'a': 101, 'b': 'ciao'}, A) == A(101, 'ciao')
def test_nestedload(self):
@dataclass
class A:
a: int
b: str
@dataclass
class B:
a: A
b: List[A]
assert load({'a': {'a': 101, 'b': 'ciao'}, 'b': []}, B) == B(A(101, 'ciao'), [])
assert load(
{'a': {'a': 101, 'b': 'ciao'}, 'b': [{'a': 1, 'b': 'a'},{'a': 0, 'b': 'b'}]},
B
) == B(A(101, 'ciao'), [A(1, 'a'),A(0, 'b')])
def test_defaultvalue(self):
@dataclass
class A:
a: int
b: Optional[str] = None
assert load({'a': 1}, A) == A(1)
assert load({'a': 1, 'b': 'io'}, A) == A(1, 'io')
class TestDataclassUnion(unittest.TestCase):
def test_ComplicatedUnion(self):
@dataclass
class A:
a: int
@dataclass
class B:
a: str
@dataclass
class C:
val: Union[A, B]
loader = dataloader.Loader()
loader.basiccast = False
assert type(loader.load({'val': {'a': 1}}, C).val) == A
assert type(loader.load({'val': {'a': '1'}}, C).val) == B
class TestDataclassDump(unittest.TestCase):
def test_dump(self):
@dataclass
class A:
a: int
b: int = 0
assert dump(A(12)) == {'a': 12}
assert dump(A(12), hidedefault=False) == {'a': 12, 'b': 0}
def test_factory_dump(self):
@dataclass
class A:
a: int
b: List[int] = field(default_factory=list)
assert dump(A(3)) == {'a': 3}
assert dump(A(12), hidedefault=False) == {'a': 12, 'b': []}
class TestDataclassMangle(unittest.TestCase):
def test_mangle_extra(self):
@dataclass
class Mangle:
value: int = field(metadata={'name': 'Value'})
assert load({'value': 12, 'Value': 12}, Mangle) == Mangle(12)
with self.assertRaises(exceptions.TypedloadValueError):
load({'value': 12, 'Value': 12}, Mangle, failonextra=True)
def test_mangle_load(self):
@dataclass
class Mangle:
value: int = field(metadata={'name': 'va.lue'})
assert load({'va.lue': 1}, Mangle) == Mangle(1)
assert dump(Mangle(1)) == {'va.lue': 1}
def test_case(self):
@dataclass
class Mangle:
value: int = field(metadata={'name': 'Value'})
assert load({'Value': 1}, Mangle) == Mangle(1)
assert 'Value' in dump(Mangle(1))
with self.assertRaises(ValueError):
load({'value': 1}, Mangle)
def test_mangle_rename(self):
@dataclass
class Mangle:
a: int = field(metadata={'name': 'b'})
b: str = field(metadata={'name': 'a'})
assert load({'b': 1, 'a': 'ciao'}, Mangle) == Mangle(1, 'ciao')
assert dump(Mangle(1, 'ciao')) == {'b': 1, 'a': 'ciao'}
def test_weird_mangle(self):
@dataclass
class Mangle:
a: int = field(metadata={'name': 'b', 'alt': 'q'})
b: str = field(metadata={'name': 'a'})
assert load({'b': 1, 'a': 'ciao'}, Mangle) == Mangle(1, 'ciao')
assert load({'q': 1, 'b': 'ciao'}, Mangle, mangle_key='alt') == Mangle(1, 'ciao')
assert dump(Mangle(1, 'ciao')) == {'b': 1, 'a': 'ciao'}
assert dump(Mangle(1, 'ciao'), mangle_key='alt') == {'q': 1, 'b': 'ciao'}
def test_correct_exception_when_mangling(self):
@dataclass
class A:
a: str = field(metadata={'name': 'q'})
with self.assertRaises(exceptions.TypedloadAttributeError):
load(1, A)
typedload/tests/test_orunion.py 0000644 0001750 0001750 00000003143 14376641230 016350 0 ustar salvo salvo # typedload
# Copyright (C) 2022 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# author Salvo "LtWorf" Tomaselli
import unittest
from typedload import dataloader, load, dump, typechecks
class TestOrUnion(unittest.TestCase):
'''
From Python3.10 unions can be written as A | B.
That is a completely different internal than Union[A, B]
'''
def test_typechecker(self):
assert typechecks.is_union(int | str)
assert not typechecks.is_union(2 | 1)
def test_uniontypes(self):
u = int | str | float
assert int in typechecks.uniontypes(u)
assert str in typechecks.uniontypes(u)
assert float in typechecks.uniontypes(u)
assert bytes not in typechecks.uniontypes(u)
assert bool not in typechecks.uniontypes(u)
def test_loadnewunion(self):
t = list[int] | str
assert load('ciao', t) == 'ciao'
assert load(['1', 1.0, 0], t) == [1, 1, 0]
assert load(('1', 1.0, 0), t) == [1, 1, 0]
typedload/docs/ 0000755 0001750 0001750 00000000000 14551242513 013027 5 ustar salvo salvo typedload/docs/3.9_fail_realistic_union_of_objects_as_namedtuple.svg 0000644 0001750 0001750 00000024212 14551242513 025422 0 ustar salvo salvo
typedload/docs/3.9_load_list_of_lists.svg 0000644 0001750 0001750 00000023323 14551242513 020020 0 ustar salvo salvo
typedload/docs/3.9_realistic_union_of_objects_as_namedtuple.svg 0000644 0001750 0001750 00000025014 14551242513 024430 0 ustar salvo salvo
typedload/docs/3.9_dump_objects.svg 0000644 0001750 0001750 00000024724 14551242513 016630 0 ustar salvo salvo
typedload/docs/3.11_load_list_of_lists.svg 0000644 0001750 0001750 00000023316 14551242513 020073 0 ustar salvo salvo
typedload/docs/donate.svg 0000644 0001750 0001750 00000002661 14551242110 015020 0 ustar salvo salvo