1

I'm in a situation where the data I want to access is using Table Valued Functions in SQL Server.

The app I'm building already interfaces with regular tables using SQLAlchemy ORM and the declarative_base(). Those are then parsed using marshmallow_sqlalchemy.SQLAlchemyAutoSchema

When I try and define a class as below, I get an error that the PK of the table cannot be mapped, and the column collection stored in .c is an empty list.

Specifically the error is KeyError: 'PrimaryKey'
Commenting out the __mapper_args__ results in:

sqlalchemy.exc.ArgumentError: Mapper mapped class TestModel->anon_1 could not assemble any primary key columns for mapped table 'anon_1'
import sqlalchemy
from sqlalchemy import create_engine, MetaData, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

engine = create_engine("MY_CONN_STR")

# Create a locally scoped session object to be used for queries
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db_session = scoped_session(SessionLocal)

class _Base:
    """
    Class defined to modify sqlalchemy declarative_base()
    """
    query = db_session.query_property()

Base = declarative_base(cls=_Base)


class TestModel(Base):
    """
    A Dynamic SQLAlchemy DB Model to use with a TVF
    """
    __table__ = func.mySchema.MyFuncWith2Defaults(None, None).table_valued()
    __table_args__ = {"schema": "mySchema"}
    __mapper_args__ = {"primary_key": func.mySchema.MyFuncWith2Defaults(None, None).table_valued().c["PrimaryKey"]}

    @classmethod
    def return_all_rows(cls) -> List["TestModel"]:
        return cls.query.all()

Ideally the point I would like to get to is to pass a particular tvf function name to a function and have that function create the class for me, something like the below:

def get_function_model(func_name: str, schema: str, pk: str=None) -> "TestModel":
    """
    Returns a SQLAlchemy DB Model of the supplied function name
    """
    # I'm aware the below will not work, but this is how I could do it with a normal Table I think
    attr_dict = {"__table__": metadata.tables[f"{schema}.{func_name}"]}
    if pk is not None:
        attr_dict["__mapper_args__"] = {"primary_key": attr_dict["__table__"].c[pk]}

    return type("TestModel", (TestModel,), attr_dict)

I can query the TVF fine using engine.execute(), and in doing so the column headers pull through as expected on the row objects.

My question is, how can I map a TVF to a SQLAlchemy class

0 Answers0