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:
Sebastian Mika
2019-11-08 18:04:41 +01:00
committed by Samuel Colvin
parent f9576e7a45
commit b87ca4ee05
5 changed files with 116 additions and 6 deletions
+2
View File
@@ -4,3 +4,5 @@ django # pyup: ignore
djangorestframework # pyup: ignore
#marshmallow # pyup: ignore
toastedmarshmallow # pyup: ignore
attr # pyup: ignore
cattrs # pyup: ignore
+6 -1
View File
@@ -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
]
+101
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
Add benchmarks for `cattrs`
+6 -5
View File
@@ -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