I'm refactoring a Python codebase to be modular, with each module defining its own SQLAlchemy models. Because the models are all defined in separate schema files which can be included or excluded based on an envfile, there needs to be a single declarative_base()
which they all use.
The way I've approached this is to have each schema.py
file define a function that takes a Base
and then uses that as the parent for the class inheritance:
# path/to/my/module/schema.py
def schema(Base):
class File(Base):
__tablename__ = "files"
id: Mapped[str] = mapped_column(String(36))
return (File,)
Then, in my higher up application, I can dynamically load it in:
# main.py
Base = declarative_base()
for schema in schemas:
schema(Base)
This works fine. The issue is that because I'm returning a class from a function, it no longer shows up for Pylance static type checking.
For example:
# path/to/my/module/Files.py
from .schema import schema
class Files():
files: List[File] = [] # <-- "File" is not defined
def __init__(self, Base):
(File,) = schema(Base)
self.File = File
some_files: List[File] = [] # <-- Variable not allowed in type expression
It's the "Variable not allowed in type expression" that is the real kicker. It won't allow me to use it because it's not available at runtime. It's no longer a "class" type, but a variable, because it's not static.
The only thing that has worked for both ways is to literally just duplicate the class definitions - once at a static level with a dummy Base
and once returned from the function. Of course that's an awful idea, but it seems mad to not be able to do the equivalent in a normal DRY way.
I asked our robot overlords and they all seemed to hallucinate something to do with modifying the __bases__
property but no dice.
Of course, the base could just be created at a root level and imported backwards from the deeper modules, but then that feels like data is flowing in the wrong direction; the vision being that everything flows one way.
Hopefully the above, highly abridged code explains the slight catch-22 with what I'm trying to achieve. Any ideas welcomed– thanks in advance.