Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circular dependency in Django Rest Framework serializers

I'm fighting with circular dependencies within serializers in my web API written using Django Rest Framework 3. Whereas I know that circular dependencies in a project is almost always a sign of bad design, I can't find a decent way of avoiding it without making the app a big monolithic nightmare.

A simple stripped down example pictures well enough what happens in all places I'm having the similar problem.

Let's have two simple models in two apps:

Profiles app

# profiles/models.py  from images.models import Image  class Profile(models.Model):     name = models.CharField(max_length=140)        def recent_images(self):         return Image.objects.recent_images_for_user(self) 

Images app

# images/models.py  class Image(models.Model):     profile = models.ForeignKey('profiles.Profile')     title = models.CharField(max_length=140) 

Following the principle of fat models I often use multiple imports in my models to allow easy retrieval of related objects using methods on Profile, but that rarely causes circular dependencies, since I rarely do the same from the other end.

The problem begins when I try to add serializers to the bunch. To make the API footprint small and limit the amount of necessary calls to the minimum, I want to serialize on both ends some of the related objects in their simplified forms.

I want to be able to retrieve profiles on /profile endpoint that will have simplified info about few recent images created by the user nested. Also, when retrieving images from /images endpoint I'd like to have profile info embedded in the image JSON.

To achieve this and avoid recursive nesting, I have two serializers - one that nests related objects, and one that does not, for both apps.

Profiles app

# profiles/serializers.py  from images.serializers import SimplifiedImageSerializer  class SimplifiedProfileSerializer(serializers.Serializer):     name = serializers.CharField()  class ProfileSerializer(SimplifiedProfileSerializer):     recent_images = SimplifiedImageSerializer(many=True) 

Images app

# images/serializers.py  from profiles.serializers import SimplifiedProfileSerializer  class SimplifiedImageSerializer(serializers.Serializer):     title = serializers.CharField()  class ImageSerializer(SimplifiedImageSerializer):     profile = SimplifiedProfileSerializer() 

The expected behaviour is to get the following JSON results:

Profiles app at /profiles

[{     'name': 'Test profile',     'recent_images': [{         'title': 'Test image 1'     }, {         'title': 'Test image 2'     }] ]] 

Images app at /images

[{     'title': 'Test image 1',     'profile': {         'name': 'Test profile'     } }, {     'title': 'Test image 2',     'profile': {         'name': 'Test profile'     } }] 

but then I hit the wall with circular imports of the serializers.

I feel that joining those two apps into one is definitely not the road to take - after all, images are something completely different from user profiles.

The serializers also in my view should belong to their respective apps.

The only way to go around this problem I found as of now is import in the method as follows:

class ImageSerializer(SimplifiedProfileSerializer):     profile = SerializerMethodField()      def get_profile(self, instance):         from profiles.serializers import SimplifiedProfileSerializer         return SimplifiedProfileSerializer(instance.profile).data 

but that feels like an ugly, ugly, uuuugly hack.

Could you please share your experience with similar problems?

Thanks!

like image 291
Mateusz Papiernik Avatar asked Oct 29 '15 11:10

Mateusz Papiernik


People also ask

Do we need Serializers in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

What is the use of Serializers in Django?

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON , XML or other content types.

What is circular dependency in Django?

Circular dependencies are imports in different Python modules from each other.

What is HyperlinkedModelSerializer?

HyperlinkedModelSerializer is a layer of abstraction over the default serializer that allows to quickly create a serializer for a model in Django. Django REST Framework is a wrapper over default Django Framework, basically used to create APIs of various kinds.


1 Answers

In my opinion your code is fine, because you do not have a logic circular dependency.

Your ImportError is only raised because of the way import() evaluates top level statements of the entire file when called.

However, nothing is impossible in python...

There is a way around it if you positively want your imports on top:

From David Beazleys excellent talk Modules and Packages: Live and Let Die! - PyCon 2015, 1:54:00, here is a way to deal with circular imports in python:

try:     from images.serializers import SimplifiedImageSerializer except ImportError:     import sys     SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer'] 

This tries to import SimplifiedImageSerializer and if ImportError is raised, because it already is imported, it will pull it from the importcache.

PS: You have to read this entire post in David Beazley's voice.

like image 83
Sebastian Wozny Avatar answered Sep 23 '22 08:09

Sebastian Wozny