Reactant: A pre-ChatGPT code generator for Django
Reactant is a Python package that generates code for models, views, and urls based on Python type annotations. When I built Reactant, it was initially to target Django, so the models, views, and urls refer to the Django data models, class-based views, and URLconf respectively. The package eventually expanded to support SQLAlchemy and Peewee models.
It was actively developed during the second half of 2021. I was unemployed and a very junior programmer then. At that time, I already know how to work with Django but I needed to enhance my skills to have an advantage when applying for jobs. FastAPI + Pydantic + SQLModel were very hot in the Python web development space during that time too. So I thought building something that can somehow utilize these shiny things for Django might be a plus.
Playful naming
If you skim the library code (Reactant repository), you’ll notice a playful take on naming the objects. DjangoCombustionChamber, PeeweeCombustionChamber, DjangoCombustor, and more. Yes, it was a novice mistake naming them like that. I even forgot what they were when writing this post. (⌒_⌒;)
How it works
Reactant is built on top of Pydantic. When using Reactant, you write Reactant models like Pydantic models but must inherit from Reactant ORM classes instead: DjangoORM, SQLAlchemyORM, PeeweeORM. These classes themselves inherit from Pydantic BaseModel. For example, the script shown below uses DjangoORM to generate Django-specific files.
# generate.py
from typing import Optional
from reactant import DjangoORM, Field, generate
from datetime import date
class RocketEngine(DjangoORM):
name: str = Field(max_length=32, title="engine_name") # adding additional arguments
manufacturer: Optional[str]
power_cycle: Optional[str] = "gas-generator" # setting a default
thrust_weight_ratio: Optional[int] = None
class LaunchVehicle(DjangoORM):
name: str = Field(max_length=32)
country: str = Field("USA", max_length=32) # setting a default in the Field function
status: str
total_launches: Optional[int]
first_flight: Optional[date]
engine: str = Field(foreign_key="RocketEngine") # specifying a relationship field
# Don't forget this block.
if __name__ == "__main__":
generate()
Now when running the script…
$ reactant generate.py
Running generate.py
Found 2 Django reactants.
Django models.py finished rendering.
Django serializers.py finished rendering.
Django views_class.py finished rendering.
Django urls_class.py finished rendering.
Django views_func.py finished rendering.
Django urls_func.py finished rendering.
Success! Please check "reactant_products/django" directory.
…the code for models, views, and urls will be generated. So what’s happening under the hood?
The generate() function first classifies and gets the subclasses of each Reactant ORM classes. Then it passes them to their corresponding renderer. In the example above, the RocketEngine and LaunchVehicle models will be passed to the DjangoCombustionChamber renderer. A renderer class contains methods for generating the objects and files according to the target framework. A key part of each renderer when generating data models specific to the framework is a parser. In our example, the reactant models are parsed by the DjangoCombustor parser. In a parser class, the field names, types, and arguments are mapped to the target framework’s data model. The code below shows the result of mapping our RocketEngine and LaunchVehicle reactant models to Django models.
from django.db import models
class RocketEngine(models.Model):
name = models.CharField(max_length=32, verbose_name="engine_name")
manufacturer = models.CharField(null=True, max_length=64)
power_cycle = models.CharField(default="gas-generator", null=True, max_length=64)
thrust_weight_ratio = models.IntegerField(default=None, null=True)
class LaunchVehicle(models.Model):
name = models.CharField(max_length=32)
country = models.CharField(default="USA", null=True, max_length=32)
status = models.CharField(max_length=64)
total launches = models.IntegerField(null=True)
first_flight = models.DateField(null=True)
engine = models.ForeignKey(RocketEngine, on_delete=models.CASCADE)
Notice that model relationship is included. This is made possible by having the engine field with a foreign_key argument in the original LaunchVehicle reactant model.
Coding LLMs are here
I enjoyed making Reactant, it was useful to me, and it helped me level up my skills. Now that coding LLMs are here, I’m enjoying a new experience using them as tools for code generation. Most of the time I use Qwen/Qwen2.5-Coder-32B-Instruct on HuggingChat.