Last active
March 9, 2022 12:23
-
-
Save Tobiaqs/db150ff5307cede06b910fd54c0093f5 to your computer and use it in GitHub Desktop.
Solution for circular Serializer imports in larger Django projects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
DRF circular serializer import helper | |
""" | |
from rest_framework.serializers import ModelSerializer | |
from rest_framework.fields import Field | |
# Add more serializers here | |
RequestSerializer: ModelSerializer = None | |
OrderSerializer: ModelSerializer = None | |
# Keep track of proxy instances | |
proxy_instances = {} | |
# Allow for performance boost once classes are registered | |
name_to_actual_class = {} | |
supported_serializers = [] | |
for serializer_name in [*globals().keys()]: | |
if not serializer_name.endswith('Serializer') or globals()[serializer_name] is not None: | |
continue | |
# For use in __all__ | |
supported_serializers.append(serializer_name) | |
proxy_instances[serializer_name] = [] | |
# Create a new class | |
class ghost_serializer: | |
# keep copy of serializer_name locally | |
_proxy_serializer_name = serializer_name | |
_proxy_actual_instance = None | |
def __new__(cls, *args, **kwargs): | |
# Completely avoid ghost_serializer once the actual class has been provided | |
if cls._proxy_serializer_name in name_to_actual_class: | |
return name_to_actual_class[cls._proxy_serializer_name](*args, **kwargs) | |
return super().__new__(cls) | |
def __init__(self, *args, **kwargs): | |
# To pass as a DRF Field we need to store _args and _kwargs, these are deepcopied | |
# at some point | |
self._args = args | |
self._kwargs = kwargs | |
# Register our existence | |
proxy_instances[self._proxy_serializer_name].append(self) | |
# Pretend being a DRF Field to be accepted by DRF magic | |
self.__class__ = Field | |
def __getattribute__(self, name): | |
if not name.startswith('_proxy') and self._proxy_actual_instance is not None: | |
return getattr(self._proxy_actual_instance, name) | |
else: | |
return super().__getattribute__(name) | |
def __setattr__(self, name, value): | |
if not name.startswith('_proxy') and self._proxy_actual_instance is not None: | |
return setattr(self._proxy_actual_instance, name, value) | |
else: | |
return super().__setattr__(name, value) | |
# Make accessible | |
globals()[serializer_name] = ghost_serializer | |
def register_serializer(name, actual_class): | |
for proxy_instance in proxy_instances[name]: | |
actual_instance = actual_class(*proxy_instance._args, **proxy_instance._kwargs) | |
# Fake __class__, spooky | |
proxy_instance.__class__ = actual_class | |
proxy_instance._proxy_actual_instance = actual_instance | |
# Fast-track future object creations | |
name_to_actual_class[name] = actual_class | |
# Monkey-patch - theoretically this step is unnecessary but it should | |
# give a tiny performance boost | |
globals()[name] = actual_class | |
__all__ = ['register_serializer'] + supported_serializers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from circular_serializers import register_serializer, RequestSerializer | |
... | |
class OrderSerializer(serializers.ModelSerializer): | |
requests = RequestSerializer(many=True, source='request_set') | |
class Meta: | |
model = Order | |
register_serializer('OrderSerializer', OrderSerializer) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from circular_serializers import register_serializer, OrderSerializer | |
... | |
class RequestSerializer(serializers.ModelSerializer): | |
orders = OrderSerializer(many=True, source='order_set') | |
class Meta: | |
model = Request | |
register_serializer('RequestSerializer', RequestSerializer) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment