Skip to content

Commit

Permalink
refactor: mv allauth.tests, extract (de)serialization into modelkit
Browse files Browse the repository at this point in the history
  • Loading branch information
pennersr committed Feb 5, 2025
1 parent fc29147 commit 62b7013
Show file tree
Hide file tree
Showing 133 changed files with 653 additions and 443 deletions.
2 changes: 1 addition & 1 deletion allauth/account/tests/test_auth_backends.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.test.utils import override_settings

import pytest

from allauth.account import app_settings
from allauth.account.auth_backends import AuthenticationBackend
from allauth.tests import TestCase


class AuthenticationBackendTests(TestCase):
Expand Down
2 changes: 1 addition & 1 deletion allauth/account/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import NoReverseMatch, reverse

Expand All @@ -13,7 +14,6 @@
from allauth.account.authentication import AUTHENTICATION_METHODS_SESSION_KEY
from allauth.account.forms import LoginForm
from allauth.account.models import EmailAddress
from allauth.tests import TestCase


@override_settings(
Expand Down
3 changes: 2 additions & 1 deletion allauth/account/tests/test_logout.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from django.contrib.auth import get_user_model
from django.core import validators
from django.test import TestCase
from django.test.client import Client
from django.test.utils import override_settings
from django.urls import reverse

from allauth.account import app_settings
from allauth.account.signals import user_logged_out
from allauth.tests import Mock, TestCase
from allauth.tests import Mock


test_username_validators = [
Expand Down
2 changes: 1 addition & 1 deletion allauth/account/tests/test_reset_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core import mail
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse, reverse_lazy
from django.utils.http import urlencode
Expand All @@ -14,7 +15,6 @@
from allauth.account.forms import ResetPasswordForm, default_token_generator
from allauth.account.models import EmailAddress
from allauth.account.utils import user_pk_to_url_str
from allauth.tests import TestCase


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion allauth/account/tests/test_signup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail
from django.test import TestCase
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
Expand All @@ -16,7 +17,6 @@
from allauth.account.forms import BaseSignupForm, SignupForm
from allauth.account.models import EmailAddress
from allauth.core import context
from allauth.tests import TestCase
from allauth.utils import get_username_max_length


Expand Down
2 changes: 1 addition & 1 deletion allauth/account/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.core import mail, validators
from django.core.exceptions import ValidationError
from django.template import Context, Template
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
Expand All @@ -23,7 +24,6 @@
user_username,
)
from allauth.core import context
from allauth.tests import TestCase

from .test_models import UUIDUser

Expand Down
93 changes: 93 additions & 0 deletions allauth/core/internal/modelkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import base64
import json

from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.files.base import ContentFile
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import FileField
from django.db.models.fields import (
BinaryField,
DateField,
DateTimeField,
TimeField,
)
from django.utils import dateparse
from django.utils.encoding import force_bytes, force_str


SERIALIZED_DB_FIELD_PREFIX = "_db_"


def serialize_instance(instance):
"""
Since Django 1.6 items added to the session are no longer pickled,
but JSON encoded by default. We are storing partially complete models
in the session (user, account, token, ...). We cannot use standard
Django serialization, as these are models are not "complete" yet.
Serialization will start complaining about missing relations et al.
"""
data = {}
for k, v in instance.__dict__.items():
if k.startswith("_") or callable(v):
continue
try:
field = instance._meta.get_field(k)
if isinstance(field, BinaryField):
if v is not None:
v = force_str(base64.b64encode(v))
elif isinstance(field, FileField):
if v and not isinstance(v, str):
v = {
"name": v.name,
"content": base64.b64encode(v.read()).decode("ascii"),
}
# Check if the field is serializable. If not, we'll fall back
# to serializing the DB values which should cover most use cases.
try:
json.dumps(v, cls=DjangoJSONEncoder)
except TypeError:
v = field.get_prep_value(v)
k = SERIALIZED_DB_FIELD_PREFIX + k
except FieldDoesNotExist:
pass
data[k] = v
return json.loads(json.dumps(data, cls=DjangoJSONEncoder))


def deserialize_instance(model, data):
ret = model()
for k, v in data.items():
is_db_value = False
if k.startswith(SERIALIZED_DB_FIELD_PREFIX):
k = k[len(SERIALIZED_DB_FIELD_PREFIX) :]
is_db_value = True
if v is not None:
try:
f = model._meta.get_field(k)
if isinstance(f, DateTimeField):
v = dateparse.parse_datetime(v)
elif isinstance(f, TimeField):
v = dateparse.parse_time(v)
elif isinstance(f, DateField):
v = dateparse.parse_date(v)
elif isinstance(f, BinaryField):
v = force_bytes(base64.b64decode(force_bytes(v)))
elif isinstance(f, FileField):
if isinstance(v, dict):
v = ContentFile(base64.b64decode(v["content"]), name=v["name"])
elif is_db_value:
try:
# This is quite an ugly hack, but will cover most
# use cases...
# The signature of `from_db_value` changed in Django 3
# https://docs.djangoproject.com/en/3.0/releases/3.0/#features-removed-in-3-0
v = f.from_db_value(v, None, None)
except Exception:
raise ImproperlyConfigured(
"Unable to auto serialize field '{}', custom"
" serialization override required".format(k)
)
except FieldDoesNotExist:
pass
setattr(ret, k, v)
return ret
87 changes: 87 additions & 0 deletions allauth/core/internal/tests/test_modelkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from datetime import date, datetime

from django.core.files.base import ContentFile
from django.db import models

from allauth.core.internal import modelkit


def test_serializer():
class SomeValue:
pass

some_value = SomeValue()

class SomeField(models.Field):
def get_prep_value(self, value):
return "somevalue"

def from_db_value(self, value, expression, connection):
return some_value

class SomeModel(models.Model):
dt = models.DateTimeField()
t = models.TimeField()
d = models.DateField()
img1 = models.ImageField()
img2 = models.ImageField()
img3 = models.ImageField()
something = SomeField()

def method(self):
pass

instance = SomeModel(
dt=datetime.now(),
d=date.today(),
something=some_value,
t=datetime.now().time(),
)
instance.img1 = ContentFile(b"%PDF", name="foo.pdf")
instance.img2 = ContentFile(
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00"
b"\x00\x00\x007n\xf9$\x00\x00\x00\nIDATx\x9cc`\x00\x00\x00\x02\x00\x01H\xaf"
b"\xa4q\x00\x00\x00\x00IEND\xaeB`\x82",
name="foo.png",
)
# make sure serializer doesn't fail if a method is attached to
# the instance
instance.method = method
instance.nonfield = "hello"
data = modelkit.serialize_instance(instance)
instance2 = modelkit.deserialize_instance(SomeModel, data)
assert getattr(instance, "method", None) == method
assert getattr(instance2, "method", None) is None
assert instance2.something == some_value
assert instance2.img1.name == "foo.pdf"
assert instance2.img2.name == "foo.png"
assert instance2.img3.name == ""
assert instance.nonfield == instance2.nonfield
assert instance.d == instance2.d
assert instance.dt.date() == instance2.dt.date()
for t1, t2 in [
(instance.t, instance2.t),
(instance.dt.time(), instance2.dt.time()),
]:
assert t1.hour == t2.hour
assert t1.minute == t2.minute
assert t1.second == t2.second
# AssertionError: datetime.time(10, 6, 28, 705776)
# != datetime.time(10, 6, 28, 705000)
assert int(t1.microsecond / 1000) == int(t2.microsecond / 1000)


def test_serializer_binary_field():
class SomeBinaryModel(models.Model):
bb = models.BinaryField()
bb_empty = models.BinaryField()

instance = SomeBinaryModel(bb=b"some binary data")

serialized = modelkit.serialize_instance(instance)
deserialized = modelkit.deserialize_instance(SomeBinaryModel, serialized)

assert serialized["bb"] == "c29tZSBiaW5hcnkgZGF0YQ=="
assert serialized["bb_empty"] == ""
assert deserialized.bb == b"some binary data"
assert deserialized.bb_empty == b""
5 changes: 2 additions & 3 deletions allauth/socialaccount/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
from allauth.account.adapter import get_adapter as get_account_adapter
from allauth.account.utils import user_email, user_field, user_username
from allauth.core.internal.adapter import BaseAdapter
from allauth.utils import (
from allauth.core.internal.modelkit import (
deserialize_instance,
import_attribute,
serialize_instance,
valid_email_or_none,
)
from allauth.utils import import_attribute, valid_email_or_none

from . import app_settings

Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/agave/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AgaveProvider

Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/amazon/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AmazonProvider

Expand Down
4 changes: 2 additions & 2 deletions allauth/socialaccount/providers/amazon_cognito/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json

from django.test import override_settings
from django.test import TestCase, override_settings

import pytest

Expand All @@ -16,7 +16,7 @@
AmazonCognitoOAuth2Adapter,
)
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse


def _get_mocked_claims():
Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/angellist/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AngelListProvider

Expand Down
3 changes: 2 additions & 1 deletion allauth/socialaccount/providers/apple/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from urllib.parse import parse_qs, urlparse

from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.http import urlencode

import jwt

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase, mocked_response
from allauth.tests import MockedResponse, mocked_response

from .apple_session import APPLE_SESSION_COOKIE_NAME
from .client import jwt_encode
Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/asana/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AsanaProvider

Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/atlassian/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AtlassianProvider

Expand Down
4 changes: 3 additions & 1 deletion allauth/socialaccount/providers/auth0/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.test import TestCase

from allauth.socialaccount.providers.auth0.provider import Auth0Provider
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse


class Auth0Tests(OAuth2TestsMixin, TestCase):
Expand Down
3 changes: 2 additions & 1 deletion allauth/socialaccount/providers/authentiq/tests.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json

from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings

from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from allauth.tests import MockedResponse

from .provider import AuthentiqProvider
from .views import AuthentiqOAuth2Adapter
Expand Down
Loading

0 comments on commit 62b7013

Please sign in to comment.