堆栈

我正在使用:

  • Python 3.10.十、
  • FastAPI 0.75.x
  • SQLAlchemy 1.4.3x

总结

我正在为几个遗留数据库构建一个统一的FastAPI项目(存储在MariaDB 10.3的后端——一些遗留软件必须保留 struct ).

我的SQLA安装程序使用数据库模块执行以下操作:

/数据库.py

import dotenv
import os

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import .models as models

dotenv.load_dotenv()

engines = {
    'parts': create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/parts", pool_pre_ping=True, pool_recycle=300),
    'shop': create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/shop", pool_pre_ping=True, pool_recycle=300),
    'purchasing': create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/purchasing", pool_pre_ping=True, pool_recycle=300),
    "company": create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/company", pool_pre_ping=True, pool_recycle=300),
    "auth": create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/auth", pool_pre_ping=True, pool_recycle=300),
}

DBSession = sessionmaker(autocommit=False, autoflush=False, binds={
    # Catalogue
    models.Shop.Catalogue: engines["shop"],
    models.Shop.Sections: engines["shop"],
    models.Shop.Orders: engines["shop"],
    # ...
    # Parts
    models.Parts.Part: engines["parts"],
    models.Parts.BinLocations: engines["parts"],

    # ...
    #Purchasing
    models.Purchasing.SupplierOrder: engines["purchasing"],
    models.Purchasing.SupplierOrder: engines["purchasing"],
    # Company Data
    models.Company.Staffmember: engines["company"],
    models.Company.Suppliers: engines["company"],
    # API Auth
    models.Auth.User: engines["auth"],
    models.Auth.Privileges: engines["auth"],
})

# Dependency
def getDb():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

对每一款车型都这样做有点费劲,但确实有效.

因为我有几个数据库,所以我认为创建一个models个模块,每个数据库都有子文件是合乎逻辑的,例如models.Partsmodels.Shopmodels.Purchasemodels.Companymodels.Auth等等.

/模特/init.py


from importlib.metadata import metadata
from sqlalchemy.orm import declarative_base

base = declarative_base()

from . import Auth, Parts, Shop, Catalogue, Purchasing, Shop

我可以通过在models个文件中的__init__.py个文件中导入Base个对象并将其导入到每个子文件中,成功地创建关系.例如:

/模型/验证.py

from . import base as Base

from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Numeric, Date, DateTime, ForeignKey, null, or_, and_

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, nullable=False, primary_key=True)
    username = Column(String(256), nullable=False)
    passhash = Column(String(512), nullable=False)
    email = Column(String, nullable=False)
    enabled = Column(Integer, nullable=True)
    staffmember_id = Column(Integer, nullable=False)

    staffmember = relationship("Company.Staffmember", uselist=False)

/模特/公司.py

from . import base as Base

from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Numeric, Date, DateTime, ForeignKey, null, or_, and_

class Staffmebmer(Base):
    __tablename__ = 'staffmembers'

    id = Column(Integer, ForeignKey("users.staffmember_id"), nullable=False, primary_key=True)
    order = Column(Integer, default=0, nullable=False)
    name = Column(String, nullable=True)
    initial = Column(String, nullable=True)
    email = Column(String, nullable=False)
    enabled = Column(Integer, default=0, nullable=False)

    relationship("Auth.User", back_populates="staffmember")

以下路由很好:

演示.py


from fastapi import Depends

from sqlalchemy.orm import Session

from .. import app, databases, models

@app.get("/api/user/{id}")
async def read_items(id: int, db: Session=Depends(databases.getDb)):
    user = db.query(models.Auth.User).filter(
        models.Auth.User.id == id
    ).first()

    user.staffmember

    return user

访问此URL返回:

{
  "username": "mark",
  "passhash": "<my hash>",
  "enabled": 1,
  "email": "mark@demo.com",
  "id": 1,
  "staffmember_id": 5,
  "staffmember": {
    "order": 20,
    "name": "Mark",
    "email": "mark@demo.com",
    "kStaffmember": 5,
    "initial": "MB",
    "enabled": 1
  }
}

但是,我想使用steffmember的首字母作为可能的用户名,所以当我在OAUTH授权脚本中为用户提问时,我try 使用:


from ..models import Auth, Company

# 'username' is provided by the auth script from the standard username/password OAuth fields

def get_user(db: Session, username: str):
    db_user_data = db.query(Auth.User).join(Company.Staffmember).filter(
        or_(
            Auth.User.username == username,
            Auth.User.email == username,
            Company.Staffmember.initial == username
        )
    ).first()

我有个例外:

(pymysql.err.ProgrammingError) (1146, "Table 'auth.staffmembers' doesn't exist")

我做这件事的方式正确吗?有没有可能解决这个问题?

推荐答案

如果有人无意中发现了这个问题,需要一个合理的答案,我会把它放到SQLAlchemy Git讨论页面上,得到一个非常明智的答案,帮助我解决了这个问题.

https://github.com/sqlalchemy/sqlalchemy/discussions/8027

总之:

  • 对于不同的数据库,不需要多个引擎连接到same MySQL/mariadb server.您只需要在其中一个数据库上启动一个会话——记住它是database的名称,而不是Python SQA代码中的模型或模块.

My new databases.py:

import dotenv
import os

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import .models as models

dotenv.load_dotenv()

DBengine = create_engine("mysql+pymysql://" + os.environ['DB_URL'] + "/parts", pool_pre_ping=True, pool_recycle=300)

DBSession = sessionmaker(bind=DBengine autocommit=False, autoflush=False)

# Dependency
def getDb():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()
  • 如果使用上述样式,则需要更明确地使用ForeignKey()个语句,例如ForeignKey("<db>.<table>.<field>"),以便明确地告诉SQA每个语句要查找哪个数据库和表.

  • 您需要将每个模型的数据库名设置为模式,例如add __table_args__ = { "schema": "<database name>" }——记住这是database的名称,而不是Python SQA代码中的模型或模块.

New /modules/Auth.py

from . import base as Base

from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Numeric, Date, DateTime, ForeignKey, null, or_, and_

class User(Base):
    __tablename__ = 'users' #table is called 'users'
    __table_args__ = { "schema": "auth" } #database is called 'auth'

    id = Column(Integer, nullable=False, primary_key=True)
    username = Column(String(256), nullable=False)
    passhash = Column(String(512), nullable=False)
    email = Column(String, nullable=False)
    enabled = Column(Integer, nullable=True)
    staffmember_id = Column(Integer, nullable=False)

    staffmember = relationship("Company.Staffmember", uselist=False)

New /models/Company.py

from . import base as Base

from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Numeric, Date, DateTime, ForeignKey, null, or_, and_

class Staffmember(Base):
    __tablename__ = 'staffmembers' #table is called 'staffmembers'
    __table_args__ = { "schema": "company" } #database is called 'company'

    id = Column(Integer, ForeignKey("auth.users.staffmember_id"), nullable=False, primary_key=True)
    # ForeignKey now needs to know the database AND table name for the field it refers to
    order = Column(Integer, default=0, nullable=False)
    name = Column(String, nullable=True)
    initial = Column(String, nullable=True)
    email = Column(String, nullable=False)
    enabled = Column(Integer, default=0, nullable=False)

    relationship("Auth.User", back_populates="staffmember")

一旦使用了这个过程,SQA知道在连接和关系中为正确的数据库名称加前缀,一切都应该正常.

Python相关问答推荐

有条件地采样我的大型DF的最有效方法

如何使用Google Gemini API为单个提示生成多个响应?

查找两极rame中组之间的所有差异

PMMLPipeline._ fit()需要2到3个位置参数,但给出了4个位置参数

使用setuptools pyproject.toml和自定义目录树构建PyPi包

我如何使法国在 map 中完全透明的代码?

提取相关行的最快方法—pandas

SQLAlchemy bindparam在mssql上失败(但在mysql上工作)

为什么Django管理页面和我的页面的其他CSS文件和图片都找不到?'

如何在Pyplot表中舍入值

Geopandas未返回正确的缓冲区(单位:米)

Pandas:填充行并删除重复项,但保留不同的值

在二维NumPy数组中,如何 Select 内部数组的第一个和第二个元素?这可以通过索引来实现吗?

Python将一个列值分割成多个列,并保持其余列相同

如何防止html代码出现在quarto gfm报告中的pandas表之上

read_csv分隔符正在创建无关的空列

PYTHON中的pd.wide_to_long比较慢

无法在盐流道中获得柱子

如何在Quarto中的标题页之前创建序言页

Pandas:使列中的列表大小与另一列中的列表大小相同