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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ jobs:
poetry config virtualenvs.in-project true
poetry run pip install -U pip
poetry install
- name: Pull up db
run: make db-up
- name: Run tests
run: make test
- name: Pull down db
run: make db-down
25 changes: 20 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
SHELL:=/usr/bin/env bash

.PHONY: db-up
db-up:
docker compose up -d

.PHONY: db-down
db-down:
docker compose down -v

.PHONY: unit
unit:
poetry run pytest

.PHONY: benchmark
benchmark:
poetry run python3 benchmarks/jsonfield_benchmark.py

.PHONY: typing
typing:
poetry run mypy src

.PHONY: lint
lint:
poetry run ruff check --select I
poetry run ruff format --check
poetry run ruff check --select I src
poetry run ruff format --check src

.PHONY: format
format:
poetry run ruff check --select I --fix
poetry run ruff format
poetry run ruff check --select I --fix src
poetry run ruff format src

.PHONY: test
test: unit
test: unit

.PHONY: all-checks
all-checks: lint typing test
File renamed without changes.
105 changes: 105 additions & 0 deletions benchmarks/jsonfield_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import os
import random
import sys
import timeit

import django
from django.db import connection
from django.test.utils import setup_test_environment, teardown_test_environment
from tabulate import tabulate

project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
django_app_path = os.path.join(project_root, "tests", "django_app")

sys.path.insert(0, project_root)
sys.path.insert(0, django_app_path)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_app.settings")
django.setup()

setup_test_environment()
db_name = connection.creation.create_test_db(verbosity=1, autoclobber=True)

from dict_field.models import ModelForTest

DICT_SIZE = 100_000
BIG_DICT = {str(i): i for i in range(DICT_SIZE)}


def get_big_dict_field(test_model: ModelForTest, field_name: str) -> None:
rand_index = str(random.randint(0, DICT_SIZE - 1))
test_model = ModelForTest.objects.get(pk=test_model.pk)
test_model.__getattribute__(field_name)[rand_index]


def delete_big_dict_field(test_model: ModelForTest, field_name: str) -> None:
rand_index = str(random.randint(0, DICT_SIZE - 1))
val = test_model.__getattribute__(field_name)[rand_index]
del test_model.__getattribute__(field_name)[rand_index]
test_model.save()
# NOTE: Here is overhead because of saving deleted value. Must to figure
# out how to test delete correctly.
test_model.__getattribute__(field_name)[rand_index] = val
test_model.save()


def update_big_dict_field(test_model: ModelForTest, field_name: str) -> None:
rand_index = str(random.randint(0, DICT_SIZE - 1))
test_model.__getattribute__(field_name)[rand_index] = "restored"
test_model.save()


def run_bench():
# NOTE: Can take a while. Be careful.
number = 1000
test_model_jsonfield = ModelForTest.objects.create(
default_json_field=BIG_DICT, default_dict_field={}
)
test_model_dictfield = ModelForTest.objects.create(
default_json_field={}, default_dict_field=BIG_DICT
)

# JSONField
time_to_get = timeit.timeit(
lambda: get_big_dict_field(test_model_jsonfield, "default_json_field"),
number=number,
)
time_to_delete = timeit.timeit(
lambda: delete_big_dict_field(test_model_jsonfield, "default_json_field"),
number=number,
)
time_to_update = timeit.timeit(
lambda: update_big_dict_field(test_model_jsonfield, "default_json_field"),
number=number,
)
json_field_time = ("JSONField", time_to_get, time_to_update, time_to_delete)

# DictField
time_to_get = timeit.timeit(
lambda: get_big_dict_field(test_model_dictfield, "default_dict_field"),
number=number,
)
time_to_delete = timeit.timeit(
lambda: delete_big_dict_field(test_model_dictfield, "default_dict_field"),
number=number,
)
time_to_update = timeit.timeit(
lambda: update_big_dict_field(test_model_dictfield, "default_dict_field"),
number=number,
)
dict_field_time = ("DictField", time_to_get, time_to_update, time_to_delete)

report_table = tabulate(
(json_field_time, dict_field_time),
headers=("Field", "Get", "Delete", "Update"),
tablefmt="grid",
)
print(report_table)


if __name__ == "__main__":
try:
run_bench()
finally:
teardown_test_environment()
connection.creation.destroy_test_db(db_name, verbosity=1)
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
postgres:
image: postgres:15.14-alpine3.21
ports:
- "8888:5432"
environment:
POSTGRES_PASSWORD: postgrespassword
Loading
Loading