Proposal: Database Router hints

Hello,

I would like to propose, to allow to add hints for the database router on all operations in migrations, e.g. migrations.CreateModel(..., hints={"just_analytics_db": True}), because this would sometimes simplify the db router logic and unify the usage to other operations like RunPython().

Here is my example:
I have decided to move the Model ModelX from one database to another. It was first saved only in the database “default” and should be saved after the change only in the database “analytics”. I have written for this a database router, and deleted the old model ModelX and created a new equal one with the new name ModelX2, because the historical model ModelX still exists:

MODEL_LABEL = "analytics.ModelX2"
DB_ALIAS = "analytics"

class AnalyticsRouter:
   ...

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if model := hints.get("model"):
            if model._meta.label == MODEL_LABEL:
                return db == DB_ALIAS
            if db == DB_ALIAS:
                return model._meta.label == MODEL_LABEL
        if "just_analytics_db" in hints:
            return db == DB_ALIAS
        if db == DB_ALIAS:
            return False
        return None

The router routes the model ModelX2 and all operations with the hints={"just_analytics_db": True}to the analytics db. The router uses the name of the model, because there exists more models in the same app, which should stay in the default db. The “just_analytics_db” way exist for operations without model, like:

    migrations.RunPython(
        ..., ..., hints={"just_analytics_db": True}
    ),

If it were possible to add this hints to migrations.CreateModel() I could use again the old name ModelX, but because it is not, I have to use a new name for my model. Also the db router would be easier to implement, it would allow to implement:

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if "just_analytics_db" in hints:
            return db == DB_ALIAS
        if db == DB_ALIAS:
            return False
        return None

For these reasons, I propose that all migrations operations like CreateModel, CreateIndex, … gets the possibility to add hints like the RunPython operation.

What do you think?

Here’s a path to your desired outcome without using any hints:

  1. Have one commit with migration that deletes the old ModelX and creates the new model ModelX2.
  2. After that first migration is shipped, remove all references to the old ModelX from your migration history.
  3. Rename ModelX2 to ModelX.

Would that work for you? It’s okay to modify old migrations, sometimes, especially for bigger refactors like this.

Hello Adam,
this would work. Thanks.
I thought that adding hints would be an improvement anyway, because the logic for the router would be easier. Is there a reason, why RunPython allows hints and CreateModel not?

Yes.

The reason is that the function passed to RunPython is opaque to the migration migration framework so there’s no way for it to know, before calling it, if the operation should be allowed or not to run on a specific database. For this reason it allows hints to be provided to advise its decision.

In the case of CreateModel, and all other operations that pertain to model alterations which is the vast majority of them, the operation instance itself has enough metadata to perform an adequate allow_migrate call.

By the way, relying on hints["model"] in your AnalyticsRouter.allow_migrate is unnecessary as app_label and model_name will always be provided when hints["model"] is.

MODEL_LABEL = ("analytics", "modelx2")
DB_ALIAS = "analytics"

class AnalyticsRouter:
   ...

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if (app_label, model_name) == MODEL_LABEL:
            return db == DB_ALIAS
        if "just_analytics_db" in hints:
            return db == DB_ALIAS
        if db == DB_ALIAS:
            return False
        return None

I’m mentioning it as we’ll likely have to deprecate hints["model"] in the future to avoid rendering models when applying migrations.

1 Like