Skip to content

Built-in Database Migration Tool For Robyn #1205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

Charlie-BU
Copy link
Contributor

Description

This PR adds a built-in database migration tool to Robyn, powered by Alembic. It introduces a new robyn db CLI command group that provides a full suite of migration capabilities, including init, migrate, upgrade, downgrade, stamp, and more.

Summary

This PR does the following:

  • Adds a fully integrated migration CLI (robyn db) for SQLAlchemy-based projects
  • Wraps Alembic’s command interface with a consistent Robyn-style CLI structure
  • Auto-generates and configures alembic.ini and env.py based on user input or intelligent detection (e.g., database URL, model metadata path)
  • Supports common Alembic commands: init, revision, migrate, upgrade, downgrade, merge, edit, stamp, show, history, etc.
  • Includes support for migration templates (e.g., --template robyn)
  • Handles command-line errors gracefully with user-friendly messages
  • Provides extension class Migrate that can be optionally used inside Robyn apps for future integration

This aims to make database versioning and schema evolution first-class citizens in the Robyn developer workflow, reducing friction for backend developers.

PR Checklist

Please ensure that:

  • The PR contains a descriptive title
  • The PR contains a descriptive summary of the changes
  • You build and test your changes before submitting a PR.
  • You have added relevant documentation
  • You have added relevant tests. We prefer integration tests wherever possible

Pre-Commit Instructions:

Copy link

vercel bot commented Jul 10, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
robyn ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 16, 2025 1:12pm

Copy link

recurseml bot commented Jul 10, 2025

✨ No issues found! Your code is sparkling clean! ✨

⚠️ Only 5 files were analyzed due to processing limits.

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

Copy link

codspeed-hq bot commented Jul 10, 2025

CodSpeed Performance Report

Merging #1205 will not alter performance

Comparing Charlie-BU:main (2c0a4aa) with main (d8339a7)

Summary

✅ 150 untouched benchmarks

@sansyrox
Copy link
Member

acknowledging the PR 😄

Will need to learn a few things before review

Comment on lines +87 to +93
parser.add_argument(
"db",
nargs="?",
default=None,
help="Database migration commands. Use 'robyn db' to see more information.",
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Charlie-BU , this supposed to invoke alembic cli?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, but the alembic cli is being encapsulated in migrate.py. The arguments passed by robyn and that passed by alembic are not exactly the same, so i need to preprocess the robyn cli argument to make it available to alembic. The details are all in migrate.py.

Here, it just adds a positional argument for robyn. Then user would be able to revoke the encapsulated alembic cli using robyn db ..., which being handled here:
image
image

@@ -3,6 +3,7 @@
import subprocess
import sys
import webbrowser
import argparse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need this to define a sub-argument for robyn, which is robyn db .... We might need argparse to process the argument passed after robyn db.
image

robyn/cli.py Outdated
if db_migration == "Y":
print("Installing the latest version of alembic...")
try:
subprocess.run([sys.executable, "-m", "pip", "install", "alembic", "-q"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be a better way here, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering to add alembic as a dependency when people execute pip install robyn. But in view that not everyone needs this database migration tool, I decided to make it an option for people when they do robyn create. If they said yes then alembic is a must in this case.
I modified this self-installing process like this:
image
Pls check if there's something that could be optimized. Thx! :D

Comment on lines +44 to +59
def catch_errors(f):
"""Decorator to catch and handle errors."""

@wraps(f)
def wrapped(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
print(f"ERROR: {str(e)}")
sys.exit(1)

return wrapped
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this pattern tbh. We should have specific exception handling. not a generic catch all at the library level.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me think how to refactor this...😊

Comment on lines +39 to +45
if template is None:
return Path(__file__).parent / "templates" / "robyn"
return Path(template)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path validation might be incorrect.

Copy link
Contributor Author

@Charlie-BU Charlie-BU Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you possibly explain more? I think it works.
image

robyn/migrate.py Outdated
Comment on lines 118 to 129
import_statement = f"sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))\nfrom {module_path} import {class_name}\ntarget_metadata = {class_name}.metadata"

# Replace the import statement
content = content.replace(
"# add your model's MetaData object here\n# for 'autogenerate' support\n# from myapp import mymodel\n# target_metadata = mymodel.Base.metadata",
f"# add your model's MetaData object here\n# for 'autogenerate' support\n{import_statement}",
)

# Replace the target_metadata setting
content = content.replace(
"target_metadata = config.attributes.get('sqlalchemy.metadata', None)",
"# target_metadata = config.attributes.get('sqlalchemy.metadata', None)\n# Already set by the import above",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can be rewritten.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This env.py file and alembic.ini file are automatically created by alembic when handling alembic init alembic, which is robyn db init now. And we have to modify the configuration here to adapt it to the real case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be better to create a db module. This file will be incredible hard to test otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would need some more explanation about this file 😅

@sansyrox
Copy link
Member

Hey @Charlie-BU 👋

Thanks for the PR. This is a good start :D But I have some inline comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants