From 59659835e837aeb34c587999576047113c0bc448 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 10:00:42 +0200 Subject: [PATCH 01/13] Added a basic benchmark suite for serializers. --- requirements.txt | 1 + runtests.py | 4 +- tests/fields.py | 10 +++ tests/models.py | 60 +++++++++++++++ tests/test_model_serializer.py | 40 +--------- tests/test_serializers_benchmarks.py | 107 +++++++++++++++++++++++++++ tox.ini | 1 + 7 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 tests/fields.py create mode 100644 tests/test_serializers_benchmarks.py diff --git a/requirements.txt b/requirements.txt index 00d973cdf..10ef9790b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ Django>=1.4.11 pytest-django==2.8.0 pytest==2.6.4 pytest-cov==1.6 +pytest-bench==0.3.0 flake8==2.2.2 # Optional packages diff --git a/runtests.py b/runtests.py index 0008bfae5..040adacdc 100755 --- a/runtests.py +++ b/runtests.py @@ -8,8 +8,8 @@ import subprocess PYTEST_ARGS = { - 'default': ['tests', '--tb=short'], - 'fast': ['tests', '--tb=short', '-q'], + 'default': ['tests', '--tb=short', '--bench'], + 'fast': ['tests', '--tb=short', '-q', '--bench'], } FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] diff --git a/tests/fields.py b/tests/fields.py new file mode 100644 index 000000000..dafb21fce --- /dev/null +++ b/tests/fields.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 +from django.db import models + + +class CustomField(models.Field): + """ + A custom model field simply for testing purposes. + """ + pass diff --git a/tests/models.py b/tests/models.py index 456b0a0bb..b07664349 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext_lazy as _ +from .fields import CustomField class RESTFrameworkModel(models.Model): @@ -68,3 +69,62 @@ class NullableOneToOneSource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.OneToOneField(OneToOneTarget, null=True, blank=True, related_name='nullable_source') + + +class RegularFieldsModel(models.Model): + """ + A model class for testing regular flat fields. + """ + auto_field = models.AutoField(primary_key=True) + big_integer_field = models.BigIntegerField() + boolean_field = models.BooleanField(default=False) + char_field = models.CharField(max_length=100) + comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100) + date_field = models.DateField() + datetime_field = models.DateTimeField() + decimal_field = models.DecimalField(max_digits=3, decimal_places=1) + email_field = models.EmailField(max_length=100) + float_field = models.FloatField() + integer_field = models.IntegerField() + null_boolean_field = models.NullBooleanField() + positive_integer_field = models.PositiveIntegerField() + positive_small_integer_field = models.PositiveSmallIntegerField() + slug_field = models.SlugField(max_length=100) + small_integer_field = models.SmallIntegerField() + text_field = models.TextField() + time_field = models.TimeField() + url_field = models.URLField(max_length=100) + custom_field = CustomField() + + def method(self): + return 'method' + + +class RegularFieldsAndFKModel(models.Model): + """ + A model class for testing regular flat fields. + """ + auto_field = models.AutoField(primary_key=True) + big_integer_field = models.BigIntegerField() + boolean_field = models.BooleanField(default=False) + char_field = models.CharField(max_length=100) + comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100) + date_field = models.DateField() + datetime_field = models.DateTimeField() + decimal_field = models.DecimalField(max_digits=3, decimal_places=1) + email_field = models.EmailField(max_length=100) + float_field = models.FloatField() + integer_field = models.IntegerField() + null_boolean_field = models.NullBooleanField() + positive_integer_field = models.PositiveIntegerField() + positive_small_integer_field = models.PositiveSmallIntegerField() + slug_field = models.SlugField(max_length=100) + small_integer_field = models.SmallIntegerField() + text_field = models.TextField() + time_field = models.TimeField() + url_field = models.URLField(max_length=100) + custom_field = CustomField() + fk = models.ForeignKey(RegularFieldsModel) + + def method(self): + return 'method' diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 247b309a1..37b578840 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -6,13 +6,16 @@ These tests deal with ensuring that we correctly map the model fields onto an appropriate set of serializer fields for each case. """ from __future__ import unicode_literals + from django.core.exceptions import ImproperlyConfigured from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator from django.db import models from django.test import TestCase from django.utils import six + from rest_framework import serializers from rest_framework.compat import unicode_repr +from .models import RegularFieldsModel def dedent(blocktext): @@ -22,46 +25,11 @@ def dedent(blocktext): # Tests for regular field mappings. # --------------------------------- -class CustomField(models.Field): - """ - A custom model field simply for testing purposes. - """ - pass - class OneFieldModel(models.Model): char_field = models.CharField(max_length=100) -class RegularFieldsModel(models.Model): - """ - A model class for testing regular flat fields. - """ - auto_field = models.AutoField(primary_key=True) - big_integer_field = models.BigIntegerField() - boolean_field = models.BooleanField(default=False) - char_field = models.CharField(max_length=100) - comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100) - date_field = models.DateField() - datetime_field = models.DateTimeField() - decimal_field = models.DecimalField(max_digits=3, decimal_places=1) - email_field = models.EmailField(max_length=100) - float_field = models.FloatField() - integer_field = models.IntegerField() - null_boolean_field = models.NullBooleanField() - positive_integer_field = models.PositiveIntegerField() - positive_small_integer_field = models.PositiveSmallIntegerField() - slug_field = models.SlugField(max_length=100) - small_integer_field = models.SmallIntegerField() - text_field = models.TextField() - time_field = models.TimeField() - url_field = models.URLField(max_length=100) - custom_field = CustomField() - - def method(self): - return 'method' - - COLOR_CHOICES = (('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')) @@ -125,7 +93,7 @@ class TestRegularFieldMappings(TestCase): text_field = CharField(style={'base_template': 'textarea.html'}) time_field = TimeField() url_field = URLField(max_length=100) - custom_field = ModelField(model_field=) + custom_field = ModelField(model_field=) """) self.assertEqual(unicode_repr(TestSerializer()), expected) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py new file mode 100644 index 000000000..63af030f3 --- /dev/null +++ b/tests/test_serializers_benchmarks.py @@ -0,0 +1,107 @@ +from decimal import Decimal + +from pytest import mark + +from datetime import datetime + +from rest_framework import serializers +from .models import RegularFieldsModel, RegularFieldsAndFKModel + + +data = { + 'big_integer_field': 100000, + 'char_field': 'a', + 'comma_separated_integer_field': '1,2', + 'date_field': datetime.now().date(), + 'datetime_field': datetime.now(), + 'decimal_field': Decimal('1.5'), + 'email_field': 'somewhere@overtherainbow.com', + 'float_field': 0.443, + 'integer_field': 55, + 'null_boolean_field': True, + 'positive_integer_field': 1, + 'positive_small_integer_field': 1, + 'slug_field': 'slug-friendly-text', + 'small_integer_field': 1, + 'text_field': 'lorem ipsum', + 'time_field': datetime.now().time(), + 'url_field': 'https://overtherainbow.com' +} + + +class TestSerializer(serializers.ModelSerializer): + class Meta: + model = RegularFieldsModel + fields = data.keys() + ['method'] + + +class TestNestedSerializer(serializers.ModelSerializer): + fk = TestSerializer() + + class Meta: + model = RegularFieldsAndFKModel + fields = data.keys() + ['method', 'fk'] + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_object_serialization(): + instance = RegularFieldsModel(**data) + serializer = TestSerializer(instance=instance) + + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_nested_object_serialization(): + nested_instance = RegularFieldsModel(**data) + instance = RegularFieldsAndFKModel(fk=nested_instance, **data) + serializer = TestSerializer(instance=instance) + + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_object_list_serialization(): + instance = [RegularFieldsModel(**data) for _ in range(100)] + serializer = TestSerializer(instance=instance, many=True) + + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_object_serialization_with_partial_update(): + instance = RegularFieldsModel(**data) + serializer = TestSerializer(instance=instance, data={'char_field': 'b'}, partial=True) + + assert serializer.is_valid(), serializer.errors + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_object_serialization_with_update(): + instance = RegularFieldsModel(**data) + new_data = data.copy() + new_data['char_field'] = 'b' + serializer = TestSerializer(instance=instance, data=new_data) + + assert serializer.is_valid(), serializer.errors + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_internal_value') +def test_object_deserialization(): + serializer = TestSerializer(data=data) + + assert serializer.is_valid(), serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_internal_value') +def test_object_list_deserialization(): + serializer = TestSerializer(data=[data for _ in range(100)], many=True) + + assert serializer.is_valid(), serializer.errors + + +@mark.bench('serializers.ModelSerializer.__init__') +def test_serializer_initialization(): + TestSerializer(data=data) diff --git a/tox.ini b/tox.ini index 8e0369643..f620faeb7 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ deps = {py26,py27}-django{14,15}: django-oauth2-provider==0.2.3 {py26,py27}-django16: django-oauth2-provider==0.2.4 pytest-django==2.8.0 + pytest-bench==0.3.0 django-filter==0.9.2 defusedxml==0.3 markdown>=2.1.0 From 5f49f97c958261423e7eec12d60582a1ddc6301e Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 10:05:42 +0200 Subject: [PATCH 02/13] Added a benchmark to the serializer's get_fields() method. --- tests/test_serializers_benchmarks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py index 63af030f3..fb04bbba4 100644 --- a/tests/test_serializers_benchmarks.py +++ b/tests/test_serializers_benchmarks.py @@ -43,6 +43,14 @@ class TestNestedSerializer(serializers.ModelSerializer): fields = data.keys() + ['method', 'fk'] +@mark.bench('serializers.ModelSerializer.get_fields') +def test_get_fields(): + instance = RegularFieldsModel(**data) + serializer = TestSerializer(instance=instance) + + assert serializer.get_fields() + + @mark.bench('serializers.ModelSerializer.to_representation') def test_object_serialization(): instance = RegularFieldsModel(**data) From 6118c69530569ec890419ddd33485c04bbac8cdd Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 10:11:11 +0200 Subject: [PATCH 03/13] Deactivated running benchmarks in travis for now. --- runtests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtests.py b/runtests.py index 040adacdc..c8236ff25 100755 --- a/runtests.py +++ b/runtests.py @@ -9,7 +9,7 @@ import subprocess PYTEST_ARGS = { 'default': ['tests', '--tb=short', '--bench'], - 'fast': ['tests', '--tb=short', '-q', '--bench'], + 'fast': ['tests', '--tb=short', '-q'], } FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] From 677362ac67cf3c6cb1f61573c18240505ed2fe02 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 11:08:44 +0200 Subject: [PATCH 04/13] The benchmarks are now Python 3 compatibility. --- tests/test_serializers_benchmarks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py index fb04bbba4..45c1fdd58 100644 --- a/tests/test_serializers_benchmarks.py +++ b/tests/test_serializers_benchmarks.py @@ -32,7 +32,7 @@ data = { class TestSerializer(serializers.ModelSerializer): class Meta: model = RegularFieldsModel - fields = data.keys() + ['method'] + fields = list(data.keys()) + ['method'] class TestNestedSerializer(serializers.ModelSerializer): @@ -40,7 +40,7 @@ class TestNestedSerializer(serializers.ModelSerializer): class Meta: model = RegularFieldsAndFKModel - fields = data.keys() + ['method', 'fk'] + fields = list(data.keys()) + ['method', 'fk'] @mark.bench('serializers.ModelSerializer.get_fields') From acdb4548a8eff5f5741aba376f540d7a0f53c0c7 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 11:11:31 +0200 Subject: [PATCH 05/13] Added a benchmark that will verify that the serializer fields are cached. --- tests/test_serializers_benchmarks.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py index 45c1fdd58..3320ab6cb 100644 --- a/tests/test_serializers_benchmarks.py +++ b/tests/test_serializers_benchmarks.py @@ -51,6 +51,15 @@ def test_get_fields(): assert serializer.get_fields() +@mark.bench('serializers.ModelSerializer.get_fields') +def test_get_fields_twice(): + instance = RegularFieldsModel(**data) + serializer = TestSerializer(instance=instance) + + assert serializer.get_fields() + assert serializer.get_fields() + + @mark.bench('serializers.ModelSerializer.to_representation') def test_object_serialization(): instance = RegularFieldsModel(**data) From 6994cc2bb32f2ebca775221153c1ed1a8cef4492 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 15:15:42 +0200 Subject: [PATCH 06/13] Added a benchmark that measures rendering a dictionary to JSON. --- tests/test_renderers_benchmarks.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_renderers_benchmarks.py diff --git a/tests/test_renderers_benchmarks.py b/tests/test_renderers_benchmarks.py new file mode 100644 index 000000000..e92f9babf --- /dev/null +++ b/tests/test_renderers_benchmarks.py @@ -0,0 +1,33 @@ +from decimal import Decimal +from datetime import datetime + +from pytest import mark + +from rest_framework import renderers + + +data = { + 'big_integer_field': 100000, + 'char_field': 'a', + 'comma_separated_integer_field': '1,2', + 'date_field': datetime.now().date(), + 'datetime_field': datetime.now(), + 'decimal_field': Decimal('1.5'), + 'email_field': 'somewhere@overtherainbow.com', + 'float_field': 0.443, + 'integer_field': 55, + 'null_boolean_field': True, + 'positive_integer_field': 1, + 'positive_small_integer_field': 1, + 'slug_field': 'slug-friendly-text', + 'small_integer_field': 1, + 'text_field': 'lorem ipsum', + 'time_field': datetime.now().time(), + 'url_field': 'https://overtherainbow.com' +} + + +@mark.bench('renderers.JSONRenderer.render') +def test_json_renderer(): + renderer = renderers.JSONRenderer() + renderer.render(data) From 866d41f26399618a3c69098cb3ba18963eab82e9 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 17:24:32 +0200 Subject: [PATCH 07/13] Increased the number of iterations for more accurate results. --- tests/test_renderers_benchmarks.py | 2 +- tests/test_serializers_benchmarks.py | 29 +++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/test_renderers_benchmarks.py b/tests/test_renderers_benchmarks.py index e92f9babf..ea9e91b1f 100644 --- a/tests/test_renderers_benchmarks.py +++ b/tests/test_renderers_benchmarks.py @@ -27,7 +27,7 @@ data = { } -@mark.bench('renderers.JSONRenderer.render') +@mark.bench('renderers.JSONRenderer.render', iterations=10000) def test_json_renderer(): renderer = renderers.JSONRenderer() renderer.render(data) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py index 3320ab6cb..506a658f3 100644 --- a/tests/test_serializers_benchmarks.py +++ b/tests/test_serializers_benchmarks.py @@ -28,6 +28,10 @@ data = { 'url_field': 'https://overtherainbow.com' } +data_list = [data for _ in range(100)] + +instances_list = [RegularFieldsModel(**data) for _ in range(100)] + class TestSerializer(serializers.ModelSerializer): class Meta: @@ -43,7 +47,7 @@ class TestNestedSerializer(serializers.ModelSerializer): fields = list(data.keys()) + ['method', 'fk'] -@mark.bench('serializers.ModelSerializer.get_fields') +@mark.bench('serializers.ModelSerializer.get_fields', iterations=1000) def test_get_fields(): instance = RegularFieldsModel(**data) serializer = TestSerializer(instance=instance) @@ -51,7 +55,7 @@ def test_get_fields(): assert serializer.get_fields() -@mark.bench('serializers.ModelSerializer.get_fields') +@mark.bench('serializers.ModelSerializer.get_fields', iterations=1000) def test_get_fields_twice(): instance = RegularFieldsModel(**data) serializer = TestSerializer(instance=instance) @@ -60,7 +64,7 @@ def test_get_fields_twice(): assert serializer.get_fields() -@mark.bench('serializers.ModelSerializer.to_representation') +@mark.bench('serializers.ModelSerializer.to_representation', iterations=1000) def test_object_serialization(): instance = RegularFieldsModel(**data) serializer = TestSerializer(instance=instance) @@ -68,7 +72,7 @@ def test_object_serialization(): assert serializer.data, serializer.errors -@mark.bench('serializers.ModelSerializer.to_representation') +@mark.bench('serializers.ModelSerializer.to_representation', iterations=1000) def test_nested_object_serialization(): nested_instance = RegularFieldsModel(**data) instance = RegularFieldsAndFKModel(fk=nested_instance, **data) @@ -77,15 +81,14 @@ def test_nested_object_serialization(): assert serializer.data, serializer.errors -@mark.bench('serializers.ModelSerializer.to_representation') +@mark.bench('serializers.ListSerializer.to_representation', iterations=1000) def test_object_list_serialization(): - instance = [RegularFieldsModel(**data) for _ in range(100)] - serializer = TestSerializer(instance=instance, many=True) + serializer = TestSerializer(instance=instances_list, many=True) assert serializer.data, serializer.errors -@mark.bench('serializers.ModelSerializer.to_representation') +@mark.bench('serializers.ModelSerializer.to_representation', iterations=10000) def test_object_serialization_with_partial_update(): instance = RegularFieldsModel(**data) serializer = TestSerializer(instance=instance, data={'char_field': 'b'}, partial=True) @@ -94,7 +97,7 @@ def test_object_serialization_with_partial_update(): assert serializer.data, serializer.errors -@mark.bench('serializers.ModelSerializer.to_representation') +@mark.bench('serializers.ModelSerializer.to_representation', iterations=10000) def test_object_serialization_with_update(): instance = RegularFieldsModel(**data) new_data = data.copy() @@ -105,20 +108,20 @@ def test_object_serialization_with_update(): assert serializer.data, serializer.errors -@mark.bench('serializers.ModelSerializer.to_internal_value') +@mark.bench('serializers.ModelSerializer.to_internal_value', iterations=1000) def test_object_deserialization(): serializer = TestSerializer(data=data) assert serializer.is_valid(), serializer.errors -@mark.bench('serializers.ModelSerializer.to_internal_value') +@mark.bench('serializers.ListSerializer.to_internal_value', iterations=1000) def test_object_list_deserialization(): - serializer = TestSerializer(data=[data for _ in range(100)], many=True) + serializer = TestSerializer(data=data_list, many=True) assert serializer.is_valid(), serializer.errors -@mark.bench('serializers.ModelSerializer.__init__') +@mark.bench('serializers.ModelSerializer.__init__', iterations=10000) def test_serializer_initialization(): TestSerializer(data=data) From 41d93d18a4f667a43320cb6fdde8afa348b5a5c7 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 17:48:46 +0200 Subject: [PATCH 08/13] Increased the number of iterations for more accurate results. --- tests/test_renderers_benchmarks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_renderers_benchmarks.py b/tests/test_renderers_benchmarks.py index ea9e91b1f..f8ccd5eae 100644 --- a/tests/test_renderers_benchmarks.py +++ b/tests/test_renderers_benchmarks.py @@ -27,7 +27,7 @@ data = { } -@mark.bench('renderers.JSONRenderer.render', iterations=10000) +@mark.bench('renderers.JSONRenderer.render', iterations=1000000) def test_json_renderer(): renderer = renderers.JSONRenderer() renderer.render(data) From 7b2192d861062f79e5e056963ea27e7667e87ab7 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 19:12:34 +0200 Subject: [PATCH 09/13] Benchmarking nested serialization with many objects. --- tests/test_serializers_benchmarks.py | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py index 506a658f3..a235fe74c 100644 --- a/tests/test_serializers_benchmarks.py +++ b/tests/test_serializers_benchmarks.py @@ -28,10 +28,35 @@ data = { 'url_field': 'https://overtherainbow.com' } +nested_data = { + 'big_integer_field': 100000, + 'char_field': 'a', + 'comma_separated_integer_field': '1,2', + 'date_field': datetime.now().date(), + 'datetime_field': datetime.now(), + 'decimal_field': Decimal('1.5'), + 'email_field': 'somewhere@overtherainbow.com', + 'float_field': 0.443, + 'integer_field': 55, + 'null_boolean_field': True, + 'positive_integer_field': 1, + 'positive_small_integer_field': 1, + 'slug_field': 'slug-friendly-text', + 'small_integer_field': 1, + 'text_field': 'lorem ipsum', + 'time_field': datetime.now().time(), + 'url_field': 'https://overtherainbow.com', + 'fk': data +} + data_list = [data for _ in range(100)] +data_list_with_nesting = [nested_data for _ in range(100)] + instances_list = [RegularFieldsModel(**data) for _ in range(100)] +instances_with_nesting = [RegularFieldsAndFKModel(fk=nested_instance, **data) for nested_instance in instances_list] + class TestSerializer(serializers.ModelSerializer): class Meta: @@ -88,6 +113,13 @@ def test_object_list_serialization(): assert serializer.data, serializer.errors +@mark.bench('serializers.ListSerializer.to_representation', iterations=1000) +def test_nested_object_list_serialization(): + serializer = TestSerializer(instance=instances_with_nesting, many=True) + + assert serializer.data, serializer.errors + + @mark.bench('serializers.ModelSerializer.to_representation', iterations=10000) def test_object_serialization_with_partial_update(): instance = RegularFieldsModel(**data) @@ -122,6 +154,13 @@ def test_object_list_deserialization(): assert serializer.is_valid(), serializer.errors +@mark.bench('serializers.ListSerializer.to_internal_value', iterations=1000) +def test_nested_object_list_deserialization(): + serializer = TestSerializer(data=data_list_with_nesting, many=True) + + assert serializer.is_valid(), serializer.errors + + @mark.bench('serializers.ModelSerializer.__init__', iterations=10000) def test_serializer_initialization(): TestSerializer(data=data) From 90930ad1f5de9d24bbacbc7e69b90295f734f1b9 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Wed, 4 Feb 2015 09:34:25 +0200 Subject: [PATCH 10/13] Added some full stack benchmarks. Some of them fail and I don't know why yet. --- tests/models.py | 58 +++++++++++++ tests/test_full_stack_benchmarks.py | 129 ++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 tests/test_full_stack_benchmarks.py diff --git a/tests/models.py b/tests/models.py index b07664349..def707962 100644 --- a/tests/models.py +++ b/tests/models.py @@ -100,6 +100,34 @@ class RegularFieldsModel(models.Model): return 'method' +class RegularFieldsModel2(models.Model): + """ + A model class for testing regular flat fields. + """ + auto_field = models.AutoField(primary_key=True) + big_integer_field = models.BigIntegerField() + boolean_field = models.BooleanField(default=False) + char_field = models.CharField(max_length=100) + comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100) + date_field = models.DateField() + datetime_field = models.DateTimeField() + decimal_field = models.DecimalField(max_digits=3, decimal_places=1) + email_field = models.EmailField(max_length=100) + float_field = models.FloatField() + integer_field = models.IntegerField() + null_boolean_field = models.NullBooleanField() + positive_integer_field = models.PositiveIntegerField() + positive_small_integer_field = models.PositiveSmallIntegerField() + slug_field = models.SlugField(max_length=100) + small_integer_field = models.SmallIntegerField() + text_field = models.TextField() + time_field = models.TimeField() + url_field = models.URLField(max_length=100) + + def method(self): + return 'method' + + class RegularFieldsAndFKModel(models.Model): """ A model class for testing regular flat fields. @@ -128,3 +156,33 @@ class RegularFieldsAndFKModel(models.Model): def method(self): return 'method' + + +class RegularFieldsAndFKModel2(models.Model): + """ + A model class for testing regular flat fields. + """ + auto_field = models.AutoField(primary_key=True) + big_integer_field = models.BigIntegerField() + boolean_field = models.BooleanField(default=False) + char_field = models.CharField(max_length=100) + comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100) + date_field = models.DateField() + datetime_field = models.DateTimeField() + decimal_field = models.DecimalField(max_digits=3, decimal_places=1) + email_field = models.EmailField(max_length=100) + float_field = models.FloatField() + integer_field = models.IntegerField() + null_boolean_field = models.NullBooleanField() + positive_integer_field = models.PositiveIntegerField() + positive_small_integer_field = models.PositiveSmallIntegerField() + slug_field = models.SlugField(max_length=100) + small_integer_field = models.SmallIntegerField() + text_field = models.TextField() + time_field = models.TimeField() + url_field = models.URLField(max_length=100) + fk = models.ForeignKey(RegularFieldsModel2) + + def method(self): + return 'method' + diff --git a/tests/test_full_stack_benchmarks.py b/tests/test_full_stack_benchmarks.py new file mode 100644 index 000000000..98ab15258 --- /dev/null +++ b/tests/test_full_stack_benchmarks.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# coding: utf-8 +from pytest import mark +from datetime import datetime +from decimal import Decimal +from rest_framework import viewsets, serializers +from rest_framework.reverse import reverse +from rest_framework.routers import DefaultRouter +from rest_framework.test import APITransactionTestCase +from tests.models import RegularFieldsAndFKModel2, RegularFieldsModel2 + +data = { + 'big_integer_field': 100000, + 'char_field': 'a', + 'comma_separated_integer_field': '1,2', + 'date_field': datetime.now().date(), + 'datetime_field': datetime.now(), + 'decimal_field': Decimal('1.5'), + 'email_field': 'somewhere@overtherainbow.com', + 'float_field': 0.443, + 'integer_field': 55, + 'null_boolean_field': True, + 'positive_integer_field': 1, + 'positive_small_integer_field': 1, + 'slug_field': 'slug-friendly-text', + 'small_integer_field': 1, + 'text_field': 'lorem ipsum', + 'time_field': datetime.now().time(), + 'url_field': 'https://overtherainbow.com' +} + +data_list = [data for _ in range(100)] + + +class TestSerializer(serializers.ModelSerializer): + class Meta: + model = RegularFieldsModel2 + fields = list(data.keys()) + ['method'] + + +class TestNestedSerializer(serializers.ModelSerializer): + fk = TestSerializer() + + class Meta: + model = RegularFieldsAndFKModel2 + fields = list(data.keys()) + ['fk', 'method'] + + +class RegularFieldsAndFKViewSet(viewsets.ModelViewSet): + queryset = RegularFieldsAndFKModel2.objects.all() + serializer_class = TestNestedSerializer + + +router = DefaultRouter() +router.register('benchmark', RegularFieldsAndFKViewSet, base_name='benchmark') + +urlpatterns = router.urls + + +class FullStackBenchmarksTestCase(APITransactionTestCase): + urls = 'tests.test_full_stack_benchmarks' + + def setUp(self): + RegularFieldsModel2.objects.bulk_create([RegularFieldsModel2(**d) for d in data_list]) + + RegularFieldsAndFKModel2.objects.bulk_create( + [RegularFieldsAndFKModel2(fk=o, **data) for o in RegularFieldsModel2.objects.all()]) + + self.first_pk = RegularFieldsAndFKModel2.objects.only('pk').first().pk + self.last_pk = RegularFieldsAndFKModel2.objects.only('pk').last().pk + + @mark.bench('viewsets.ModelViewSet.list', iterations=1000) + def test_viewset_list(self): + url = reverse('benchmark-list') + + response = self.client.get(url) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000) + def test_viewset_retrieve(self): + url = reverse('benchmark-detail', args=[self.first_pk]) + + response = self.client.get(url) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.create', iterations=1000) + def test_viewset_create(self): + url = reverse('benchmark-list') + + new_data = data.copy() + new_data['fk'] = data.copy() + + response = self.client.post(url, data=new_data) + assert response.status_code == 201, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.update', iterations=1000) + def test_viewset_update(self): + url = reverse('benchmark-detail', args=[self.first_pk]) + + new_data = data.copy() + new_fk = RegularFieldsModel2.objects.create(**data) + new_fk_data = data.copy() + new_fk_data['auto_field'] = new_fk.pk + new_data['fk'] = new_fk_data + + response = self.client.put(url, data=new_data) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.partial_update', iterations=1000) + def test_viewset_partial_update(self): + url = reverse('benchmark-detail', args=[self.first_pk]) + + new_fk = RegularFieldsModel2.objects.create(**data) + new_fk_data = data.copy() + new_fk_data['auto_field'] = new_fk.pk + new_data = {'fk': new_fk_data} + + response = self.client.patch(url, data=new_data) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.destroy', iterations=10000) + def test_viewset_delete(self): + new_fk = RegularFieldsModel2.objects.create(**data) + new_obj = RegularFieldsAndFKModel2.objects.create(fk=new_fk, **data) + + url = reverse('benchmark-detail', args=[new_obj.pk]) + + response = self.client.delete(url) + assert response.status_code == 204, (response.rendered_content, url) From 1a08c0ce8a1f1d6d4e852e9c9406119989e864a1 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Fri, 6 Feb 2015 00:08:03 +0200 Subject: [PATCH 11/13] Fixed the failing full stack benchmarks. --- tests/test_full_stack_benchmarks.py | 41 +++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/tests/test_full_stack_benchmarks.py b/tests/test_full_stack_benchmarks.py index 98ab15258..3fa5411a0 100644 --- a/tests/test_full_stack_benchmarks.py +++ b/tests/test_full_stack_benchmarks.py @@ -1,21 +1,24 @@ #!/usr/bin/env python # coding: utf-8 -from pytest import mark -from datetime import datetime from decimal import Decimal + +from pytest import mark + +from datetime import datetime from rest_framework import viewsets, serializers from rest_framework.reverse import reverse from rest_framework.routers import DefaultRouter from rest_framework.test import APITransactionTestCase from tests.models import RegularFieldsAndFKModel2, RegularFieldsModel2 + data = { 'big_integer_field': 100000, 'char_field': 'a', 'comma_separated_integer_field': '1,2', - 'date_field': datetime.now().date(), - 'datetime_field': datetime.now(), - 'decimal_field': Decimal('1.5'), + 'date_field': str(datetime.now().date()), + 'datetime_field': str(datetime.now()), + 'decimal_field': str(Decimal('1.5')), 'email_field': 'somewhere@overtherainbow.com', 'float_field': 0.443, 'integer_field': 55, @@ -25,7 +28,7 @@ data = { 'slug_field': 'slug-friendly-text', 'small_integer_field': 1, 'text_field': 'lorem ipsum', - 'time_field': datetime.now().time(), + 'time_field': str(datetime.now().time()), 'url_field': 'https://overtherainbow.com' } @@ -45,6 +48,28 @@ class TestNestedSerializer(serializers.ModelSerializer): model = RegularFieldsAndFKModel2 fields = list(data.keys()) + ['fk', 'method'] + def create(self, validated_data): + fk = RegularFieldsModel2.objects.create(**validated_data['fk']) + validated_data['fk'] = fk + + return RegularFieldsAndFKModel2.objects.create(**validated_data) + + def update(self, instance, validated_data): + fk_data = validated_data.pop('fk') + fk_pk = fk_data.pop('auto_field', None) + + if not fk_pk: + fk_pk = instance.fk_id + + RegularFieldsModel2.objects.filter(pk=fk_pk).update(**fk_data) + + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.fk_id = fk_pk + instance.save() + + return instance + class RegularFieldsAndFKViewSet(viewsets.ModelViewSet): queryset = RegularFieldsAndFKModel2.objects.all() @@ -90,7 +115,7 @@ class FullStackBenchmarksTestCase(APITransactionTestCase): new_data = data.copy() new_data['fk'] = data.copy() - response = self.client.post(url, data=new_data) + response = self.client.post(url, data=new_data, format='json') assert response.status_code == 201, (response.rendered_content, url) @mark.bench('viewsets.ModelViewSet.update', iterations=1000) @@ -103,7 +128,7 @@ class FullStackBenchmarksTestCase(APITransactionTestCase): new_fk_data['auto_field'] = new_fk.pk new_data['fk'] = new_fk_data - response = self.client.put(url, data=new_data) + response = self.client.put(url, data=new_data, format='json') assert response.status_code == 200, (response.rendered_content, url) @mark.bench('viewsets.ModelViewSet.partial_update', iterations=1000) From a8e7f664db4494e56feb4495c641ec7b5aea052a Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Sat, 7 Feb 2015 13:09:15 +0200 Subject: [PATCH 12/13] Fixed the lint error. --- tests/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/models.py b/tests/models.py index def707962..b5ac1b3cf 100644 --- a/tests/models.py +++ b/tests/models.py @@ -185,4 +185,3 @@ class RegularFieldsAndFKModel2(models.Model): def method(self): return 'method' - From 36cd8663d4c970e35266b968236e390a6a2771b0 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Sat, 7 Feb 2015 13:11:18 +0200 Subject: [PATCH 13/13] Added full stack benchmarks with filters. --- tests/test_full_stack_benchmarks.py | 52 ++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/test_full_stack_benchmarks.py b/tests/test_full_stack_benchmarks.py index 3fa5411a0..0cef52612 100644 --- a/tests/test_full_stack_benchmarks.py +++ b/tests/test_full_stack_benchmarks.py @@ -1,11 +1,13 @@ #!/usr/bin/env python # coding: utf-8 from decimal import Decimal +from datetime import datetime +from django.utils import unittest from pytest import mark -from datetime import datetime from rest_framework import viewsets, serializers +from rest_framework.filters import DjangoFilterBackend from rest_framework.reverse import reverse from rest_framework.routers import DefaultRouter from rest_framework.test import APITransactionTestCase @@ -76,8 +78,14 @@ class RegularFieldsAndFKViewSet(viewsets.ModelViewSet): serializer_class = TestNestedSerializer +class FilteredRegularFieldsAndFKViewSet(RegularFieldsAndFKViewSet): + filter_backends = (DjangoFilterBackend,) + filter_fields = ('big_integer_field',) + + router = DefaultRouter() router.register('benchmark', RegularFieldsAndFKViewSet, base_name='benchmark') +router.register('benchmark2', FilteredRegularFieldsAndFKViewSet, base_name='benchmark-filter') urlpatterns = router.urls @@ -152,3 +160,45 @@ class FullStackBenchmarksTestCase(APITransactionTestCase): response = self.client.delete(url) assert response.status_code == 204, (response.rendered_content, url) + + +class FullStackFilteredBenchmarksTestCase(APITransactionTestCase): + urls = 'tests.test_full_stack_benchmarks' + + def setUp(self): + RegularFieldsModel2.objects.bulk_create([RegularFieldsModel2(**d) for d in data_list]) + + RegularFieldsAndFKModel2.objects.bulk_create( + [RegularFieldsAndFKModel2(fk=o, **data) for o in RegularFieldsModel2.objects.all()]) + + self.first_pk = RegularFieldsAndFKModel2.objects.only('pk').first().pk + self.last_pk = RegularFieldsAndFKModel2.objects.only('pk').last().pk + + @mark.bench('viewsets.ModelViewSet.list', iterations=1000) + def test_viewset_list(self): + url = reverse('benchmark-filter-list') + + response = self.client.get(url, data={'big_integer_field': 100000}) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000) + def test_viewset_retrieve(self): + url = reverse('benchmark-filter-detail', args=[self.first_pk]) + + response = self.client.get(url, data={'big_integer_field': 100000}) + assert response.status_code == 200, (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.list', iterations=1000) + def test_viewset_list_nothing(self): + url = reverse('benchmark-filter-list') + + response = self.client.get(url, data={'big_integer_field': 100001}) + assert response.rendered_content == '[]', (response.rendered_content, url) + + @mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000) + @unittest.skip('pytest-bench cannot benchmark operations that raise exceptions') + def test_viewset_retrieve_nothing(self): + url = reverse('benchmark-filter-detail', args=[self.first_pk]) + + response = self.client.get(url, data={'big_integer_field': 100001}) + assert response.status_code == 404, (response.rendered_content, url)