Vlasy22
Vlasy22

Reputation: 1

How to extend SQLAlchemy generated classes in declarative model outside of the model?

I am using SQLAlchemy + sqlacodegen in my python project. I have succesfully generated declarative model from my database, which looks like this:

DB_Model.py

from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Table
from sqlalchemy.sql.sqltypes import NullType
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
metadata = Base.metadata


class GameMatch(Base):
    __tablename__ = 'GameMatches'

    GameMatch_ID = Column(Integer, primary_key=True)
    MatchDate = Column(DateTime)
    UserCreated = Column(String(100))


class Player(Base):
    __tablename__ = 'Players'

    Player_ID = Column(Integer, primary_key=True)
    Name = Column(String(255))
    RollPerRound = Column(Integer)


class User(Base):
    __tablename__ = 'Users'

    User_ID = Column(Integer, primary_key=True)
    Name = Column(String(255), nullable=False)

Code generating:

import io
import sys
import os
from sqlalchemy import create_engine, MetaData
from sqlacodegen.codegen import CodeGenerator

def generate_model(host, user, password, database, outfile = None):
    engine = create_engine('sqlite:///' + os.getcwd() + "\\PigGameDB.db")
    metadata = MetaData(bind=engine)
    metadata.reflect()
    outfile = io.open(outfile, 'w', encoding='utf-8') if outfile else sys.stdout
    generator = CodeGenerator(metadata)
    generator.render(outfile)

if __name__ == '__main__':
    generate_model('database.example.org', 'dbuser', 'secretpassword', 'mydatabase', 'DB_Model.py')

I want to add functionality for these classes, but I can't add it directly into this model, because I have decided for Database first aproach - I define the database first, and then I use sqlacodegen to update the model. That means, that the model file is always rewritten with new generation.

I am thinking about using inheritance for these classes (wrapper) with desired functionality, or switch approach to code first (which i really not prefer).

What are the possible solutions for this problem? Links to good articles are welcomed too. As far as i know, in Python is no equivalent of partial class like in C#, which is solution for EntityFramework's database first approach.

Also is good approach to use these model classes directly in my project, or should I implement some mapping? For example having class Player and _Player:
Player - class for table in the database, and used only for access to database.
_Player - class for the project itself, like standard python class.

Python 3.11.4 SQLAlchemy 1.4.49 sqlacodegen 2.3.0.post1

I tried searching for partial class C# equivalent for Python, with expectation, that class is divided to 2 sections - generated and written by hand.

Upvotes: 0

Views: 335

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55589

There are a few ways that you might approach this, but the simplest solution is probably to generate tables rather than models (sqlacodegen has a TablesGenerator class would help I think) and then use imperative mapping to map the tables to you classes. So you would have something like:

from sqlalchemy import orm

# Let's assume my_tables contains generated table definitions.
from my_tables import my_table1, my_table2

class MyClass1:
    def method(self):
        ...


class MyClass2:
    def method(self):
        ...

# Create a registry (you'll need to provide the metadata that contains your tables) 
mapper_registry = orm.registry(metadata=my_table1.metadata)

# Map classes to tables.
mapper_registry.map_imperatively(MyClass1, my_table1)
mapper_registry.map_imperatively(MyClass2, my_table2)

Another approach would be to have your methods in mixin classes, and update the models file after it has been generated to inherit from the mixin as well as Base. On a *nix platform you could do something like

sed -i '1i import mymixins' DB_models.py
sed -i 's/^class \([A-Z][a-z]\+\([A-Z][a-z]\+\)*\)(Base):/class \1(mymixins.\1Mixin, Base):/p' DB_Models.py

to get class Foo(mymixins.FooMixin, Base):, for example.

Upvotes: 0

Related Questions