mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
benchmark for cattrs (#513)
* benchmark for attr * update HISTORY.rst * * added benchmark using cattr * add env "ATTRS=1" to benchmark runner to only compare again the two and save results in separate csv * added section to docs/index * nits * re-run benchmark with cython pydantic; merge results back to main benchmarks results table * pin pydantic to top of benchmark report * remove attrs, fix cattrs * update benchmarks output * add change
This commit is contained in:
committed by
Samuel Colvin
parent
f9576e7a45
commit
b87ca4ee05
@@ -4,3 +4,5 @@ django # pyup: ignore
|
||||
djangorestframework # pyup: ignore
|
||||
#marshmallow # pyup: ignore
|
||||
toastedmarshmallow # pyup: ignore
|
||||
attr # pyup: ignore
|
||||
cattrs # pyup: ignore
|
||||
|
||||
+6
-1
@@ -33,6 +33,11 @@ try:
|
||||
except Exception:
|
||||
TestToastedMarshmallow = None
|
||||
|
||||
try:
|
||||
from test_cattr import TestCAttr
|
||||
except Exception:
|
||||
TestCAttr = None
|
||||
|
||||
PUNCTUATION = ' \t\n!"#$%&\'()*+,-./'
|
||||
LETTERS = string.ascii_letters
|
||||
UNICODE = '\xa0\xad¡¢£¤¥¦§¨©ª«¬ ®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
|
||||
@@ -42,7 +47,7 @@ random = random.SystemRandom()
|
||||
# in order of performance for csv
|
||||
other_tests = [
|
||||
t for t in
|
||||
[TestToastedMarshmallow, TestMarshmallow, TestTrafaret, TestDRF]
|
||||
[TestCAttr, TestToastedMarshmallow, TestMarshmallow, TestTrafaret, TestDRF]
|
||||
if t is not None
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
import attr
|
||||
import cattr
|
||||
from dateutil.parser import parse
|
||||
|
||||
|
||||
class TestCAttr:
|
||||
package = 'cattr'
|
||||
version = attr.__version__
|
||||
|
||||
def __init__(self, allow_extra):
|
||||
# cf. https://github.com/Tinche/cattrs/issues/26 why at least structure_str is needed
|
||||
def structure_str(s, _):
|
||||
if not isinstance(s, str):
|
||||
raise ValueError()
|
||||
return s
|
||||
|
||||
def structure_int(i, _):
|
||||
if not isinstance(i, int):
|
||||
raise ValueError()
|
||||
return i
|
||||
|
||||
class PositiveInt(int):
|
||||
...
|
||||
|
||||
def structure_posint(i, x):
|
||||
i = PositiveInt(i)
|
||||
if not isinstance(i, PositiveInt):
|
||||
raise ValueError()
|
||||
if i <= 0:
|
||||
raise ValueError()
|
||||
return i
|
||||
|
||||
cattr.register_structure_hook(datetime, lambda isostring, _: parse(isostring))
|
||||
cattr.register_structure_hook(str, structure_str)
|
||||
cattr.register_structure_hook(int, structure_int)
|
||||
cattr.register_structure_hook(PositiveInt, structure_posint)
|
||||
|
||||
def str_len_val(max_len: int, min_len: int = 0, required: bool = False):
|
||||
# validate the max len of a string and optionally its min len and whether None is
|
||||
# an acceptable value
|
||||
def _check_str_len(self, attribute, value):
|
||||
if value is None:
|
||||
if required:
|
||||
raise ValueError("")
|
||||
else:
|
||||
return
|
||||
if len(value) > max_len:
|
||||
raise ValueError("")
|
||||
if min_len and len(value) < min_len:
|
||||
raise ValueError("")
|
||||
|
||||
return _check_str_len
|
||||
|
||||
def pos_int(self, attribute, value):
|
||||
# Validate that value is a positive >0 integer; None is allowed
|
||||
if value is None:
|
||||
return
|
||||
if value <= 0:
|
||||
raise ValueError("")
|
||||
|
||||
@attr.s(auto_attribs=True, frozen=True, kw_only=True)
|
||||
class Skill:
|
||||
subject: str
|
||||
subject_id: int
|
||||
category: str
|
||||
qual_level: str
|
||||
qual_level_id: int
|
||||
qual_level_ranking: float = 0
|
||||
|
||||
@attr.s(auto_attribs=True, frozen=True, kw_only=True)
|
||||
class Location:
|
||||
latitude: float = None
|
||||
longitude: float = None
|
||||
|
||||
@attr.s(auto_attribs=True, frozen=True, kw_only=True)
|
||||
class Model:
|
||||
id: int
|
||||
sort_index: float
|
||||
client_name: str = attr.ib(validator=str_len_val(255))
|
||||
# client_email: EmailStr = None
|
||||
client_phone: Optional[str] = attr.ib(default=None, validator=str_len_val(255))
|
||||
location: Optional[Location] = None
|
||||
|
||||
contractor: Optional[PositiveInt]
|
||||
upstream_http_referrer: Optional[str] = attr.ib(default=None, validator=str_len_val(1023))
|
||||
grecaptcha_response: str = attr.ib(validator=str_len_val(1000, 20, required=True))
|
||||
last_updated: Optional[datetime] = None
|
||||
skills: List[Skill] = []
|
||||
|
||||
self.model = Model
|
||||
|
||||
def validate(self, data):
|
||||
try:
|
||||
return True, cattr.structure(data, self.model)
|
||||
except ValueError as e:
|
||||
return False, str(e)
|
||||
except TypeError as e:
|
||||
return False, str(e)
|
||||
@@ -0,0 +1 @@
|
||||
Add benchmarks for `cattrs`
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
Package | Version | Relative Performance | Mean validation time
|
||||
--- | --- | --- | ---
|
||||
pydantic | `1.0b1` | | 46.5μs
|
||||
marshmallow | `2.15.1` | 2.9x slower | 136.1μs
|
||||
trafaret | `1.2.0` | 3.3x slower | 154.2μs
|
||||
toasted-marshmallow | `2.15.2post1` | 3.4x slower | 159.4μs
|
||||
django-restful-framework | `3.10.3` | 12.4x slower | 577.0μs
|
||||
pydantic | `1.1` | | 46.1μs
|
||||
cattr | `19.3.0` | 1.3x slower | 62.1μs
|
||||
marshmallow | `2.15.1` | 2.9x slower | 132.7μs
|
||||
toasted-marshmallow | `2.15.2post1` | 2.9x slower | 134.1μs
|
||||
trafaret | `2.0.0` | 3.3x slower | 153.5μs
|
||||
django-restful-framework | `3.10.3` | 12.7x slower | 586.1μs
|
||||
|
||||
Reference in New Issue
Block a user