Skip to content

Instantly share code, notes, and snippets.

@Tobiaqs
Last active March 9, 2022 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tobiaqs/db150ff5307cede06b910fd54c0093f5 to your computer and use it in GitHub Desktop.
Save Tobiaqs/db150ff5307cede06b910fd54c0093f5 to your computer and use it in GitHub Desktop.
Solution for circular Serializer imports in larger Django projects
"""
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
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)
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