Package: redbeanpython
IdeaOne of Python's most significant advantages is that we can make applications fast. Boilerplate is low, but we can focus on business logic, quickly deliver value, and make projects production-ready. But even if, with current powerful libraries, "time-to-market" is low, it can be lowered even more.The idea of RedBeanPython ORM is to decrease the time to market for applications by providing an absolutely zero configuration ORM and, simultaneously, heaving the possibility of zero effort transition to advanced ORM when needed.RedBeanPythonidea is inspired by the maturedRedBeanPHP ORM(since 2009 and still running).RedBeanPython ORM's idea is not to replace the more advanced ORMs (we should not reinvent the wheel) but to cooperate with them. Initially, it can be used as only ORM. When projects evolve, both code bases (RedBeanPython and SQLAlchemy) can coexist and, finally, can be swiftly replaced by SQLAlchemy (if needed).InstallationRedBeanPython is available on PyPI and can be installed with pip:pipinstallredbeanpythonRedBeanPython on PyPIDocumentationDocumentation is available onredbeanpython.org.RequirementsRedBeanPython requiresPython 3.11 or newer.SQLAlchemy 2.0 or newer.alembic 1.7 or newer.Database requirementsForPostgreSQLsupport RedBeanPython requires:psycopg2 2.9.9 or newer.ForMySQL/MariaDBsupport RedBeanPython requires:PyMySQL 1.0.2 or newer.How it works? Let's see an example of a usual project lifecycle.Stage 1.DevelopmentWe are starting a fresh project. We have an Idea, and we want to deliver MVP to production as soon as possible.We have an empty database (or no database at all at that moment).Let's store some data:fromdatetimeimportdatetimefromdecimalimportDecimalfromredbeanpythonimportredbean,Beanredbean.setup(dsn=False)#(1)user=Bean('user')user.name='Adam'user.age=42user.subscription_price=Decimal("12.20")user.subscription_end=datetime.fromisoformat('2024-10-08T16:20:00')redbean.store(user)For a temporary SQLite database callredbean.setup(dsn=False).
For a production database, DSN consistent with SQLAlchemy should be provided.
Seeconnectionfor more details.
For example, you may run a docker container with PostgreSQL:dockerrun-p5432:5432-ePOSTGRES_PASSWORD=pass-dpostgresAnd use it:redbean.setup('postgresql+psycopg://postgres:pass@localhost/postgres')And that's all.No schema is need to be created, no migrations need to be run, no config files to update.Justrun the code, and RedBeanPython willcreate everything automatically.If we take a look into db, we will see that table was created:createtableuser(ageBIGINT,idVARCHAR(255)notnullprimarykey,nameTEXT,subscription_endDATETIME,subscription_priceNUMERIC(30,10));In the background, the SQLAlchemy model was created:fromsqlalchemyimportColumn,BigInteger,String,Text,DateTime,NumericfromredbeanpythonimportModelclassUser(Model):__tablename__='user'__table_args__={'extend_existing':True}age=Column(BigInteger,nullable=True)id=Column(String(255),primary_key=True)name=Column(Text,nullable=True)subscription_end=Column(DateTime,nullable=True)subscription_price=Column(Numeric(precision=30,scale=10),nullable=True)def__json__(self):returnself.as_dict()defas_dict(self):return{'age':self.age,'id':self.id,'name':self.name,'subscription_end':self.subscription_end,'subscription_price':self.subscription_price,}We do not need this information now, but it might be helpful later. (See section:hybrid mode)Let's load it from the database, and as we see, the types are preserved:>>>fromredbeanpythonimportredbean>>>redbean.setup(dsn=False)>>>users=list(redbean.find('user'))>>>user=users[0]>>>print(dict(user)){'age': 42,'id': '1f0e6e46-6e47-404f-9547-9dc3e9741ec6','name': 'Adam','subscription_end': datetime.datetime(2024, 10, 8, 16, 20),'subscription_price': Decimal('12.2000000000')}>>>print(type(user.subscription_end),user.subscription_end)<class 'datetime.datetime'> 2024-10-08 16:20:00Let's go on; development continues.In the early stage of the project, life changes are widespread. Often, we even rewrite the first ideas.With RedBeanPython, this is no problem; we can change the code and run it again. RedBeanPython will adjust everything automatically.For example, we decided that the user should have a birthday. We can add in code:# ...user.birthday=date.fromisoformat('1981-12-01')# ...And rerun it. RedBeanPython will adequately adjust the database schema.The database schema has been adjusted.createtableuser(ageBIGINT,idVARCHAR(255)notnullprimarykey,nameTEXT,subscription_endDATETIME,subscription_priceNUMERIC(30,10),birthdayDATE-- (1));A new column was added.Same as model:classUser(Model):# (...)name=Column(Text,nullable=True)subscription_end=Column(DateTime,nullable=True)# (1)subscription_price=Column(Numeric(precision=30,scale=10),nullable=True)# (...)defas_dict(self):return{# (...)'name':self.name,'subscription_end':self.subscription_end,# (2)'subscription_price':self.subscription_price,}A new property was added.A new property was added.And alembic migration was created automatically:defupgrade()->None:# ### commands auto generated by Alembic - please adjust! ###withop.batch_alter_table('user',schema=None)asbatch_op:batch_op.add_column(sa.Column('birthday',sa.Date(),nullable=True))defdowngrade()->None:# ### commands auto generated by Alembic - please adjust! ###withop.batch_alter_table('user',schema=None)asbatch_op:batch_op.drop_column('birthday'):information: Migrations will be useful later when the project is production-ready.Now, when we have MVP ready, we can go to production.Stage 2:ProductionWhen the project stays production-ready, we would like tofreezeproduction database to avoid potential unexpected changes.
No automatic changes will be made to production since that moment.To freeze the database, we just need to add one parameter toredbean.setup()call:redbean.setup('...',frozen=True)Or useREDBEAN_DB_FROZEN=1environment variable.And, from that time on production, we can usealembicmigrations to manage production database schema changes safely.
We fully control how and when the database schema will be changed.Because alembic migrations are created automatically by RedBeanPython, we can just run:cdredbean/migrationsDB_DSN="sqlite:////tmp/redbean.sqlite"alembicupgradeheadon production to apply all changes.At the same time, everything will work as before in the development environment, and we still have all the benefits from automatic creation.Stage 3:Project growthWhen the project grows, we may need more performance or more sophisticated database features.
As RedBeanPython is not intended to be a replacement for SQLAlchemy, instead of extending RedBeanPython forever, we can switch to SQLAlchemy ORM and take full advantage of the giants.Moreover, we can use both ORMs simultaneously and switch to more advanced ORMs withzero effortfor specific cases only.As SQLAlchemy Models has already been created automatically, we can use it.Let's see how it works.fromredbean.models.customerimportCustomerfromredbean.models.invoiceimportInvoiceredbean.setup(dsn=False)# redbeanpython codecustomer=Bean('customer')customer.name='John'invoice=Bean('invoice')invoice.amount=Decimal("12.20")invoice.customer_id=customer.idredbean.store_many([invoice,customer])# sqlalchemy codesession=redbean.session_maker()query=session.query(Customer,Invoice)query=query.filter(Customer.id==Invoice.customer_id)query=query.filter(Invoice.amount>10)result=query.all()forcustomer,invoiceinresult:print(customer.name,invoice.amount)query=session.query(Invoice.customer_id,func.count('*').label('amount'),func.sum(Invoice.amount).label('amount'),)query=query.group_by(Invoice.customer_id)forcustomer_id,invoices_count,invoices_total,inquery.all():print(f"{customer_id}has:{invoices_count}invoices with a sum:{invoices_total}")# redbeanpython code againprint(customer_id)customer=redbean.load('customer',customer_id)print(dict(customer))We can freely mix both ORMs in the same code base. This is ahybrid mode.Finally, when our project succeeds, it will be on the market for months or years. Development team grows. Codebase enlarges - we can want to switch to SQLAlchemy ORM entirely.Stage 4:MaturityWhen the project matures and we decide to switch entirely to SQLAlchemy ORM, we can say goodbye to RedBeanPython methods and start to use SQLAlchemy only.Starting with previously automatically generated Models, we will continue to extend them and work with them in a standard SQLAlchemy way.And then you can let RedBeanPython go. RedBeanPython did its job. It's time for retirement.It was a beautiful journey!Check more onredbeanpython.org.LicenceLicence: MITIssue trackerIssue tracker is available on GitHub.ContactAdam Puza:adampuza@redbeanpython.org___