修复 FastAPI(Asyncpg + Docker)中的 “invalid literal for int() with base 10: 'None'” 错误
看起来您只提供了来源链接,而没有贴出需要翻译的正文内容。请把您想要翻译的文本(包括标题、段落、代码块说明等)粘贴在这里,我会按照要求保留源链接并将内容翻译成简体中文。
Problem Overview
在使用 Docker 部署带有 Aiven PostgreSQL 的 FastAPI 应用时,可能会遇到以下回溯信息:
ValueError: invalid literal for int() with base 10: 'None'
SQLAlchemy 的 URL 解析器会把字符串 -None 解释为数据库 URL 中的端口部分,该 URL 看起来像:
postgresql+asyncpg://user:pass@host:None/dbname
原因
SQLAlchemy 的 make_url() 函数尝试将端口部分转换为整数。如果用于构建 URL 的变量的值为 None,f‑string 插值会把它变成字面字符串 "None",从而导致错误。
典型代码模式:
import os
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
ASYNC_DB_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
engine = create_async_engine(ASYNC_DB_URL)
如果容器环境中缺少 DB_PORT(或其他任何组件),它会变为 None,从而生成带有 :None 的错误 URL。
将完整的 DATABASE_URL 与基于组件的构造混合使用的回退逻辑也可能触发此问题:
ASYNC_DB_URL = os.getenv("DATABASE_URL") or f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
当 DATABASE_URL 不存在时,构造的 URL 会继承 None 端口。
解决方案
使用单一的 DATABASE_URL
建议使用包含完整连接字符串的单个环境变量。提前验证其是否存在,并避免从多个组件拼接 URL。
import os
from sqlalchemy.ext.asyncio import create_async_engine
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is not set")
engine = create_async_engine(DATABASE_URL)
为 asyncpg 调整 Scheme
Aiven(以及许多云服务提供商)会提供带有 postgres:// scheme 的 URL。为了兼容 SQLAlchemy 的异步支持,需要将其替换为 postgresql+asyncpg://。
raw_url = os.getenv("DATABASE_URL")
if raw_url and raw_url.startswith("postgres://"):
raw_url = raw_url.replace("postgres://", "postgresql+asyncpg://", 1)
engine = create_async_engine(raw_url)
完整示例(database.py)
import os
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
# 获取并验证完整的 URL
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is required")
# 确保使用 asyncpg 驱动的 scheme
if DATABASE_URL.startswith("postgres://"):
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql+asyncpg://", 1)
# 创建异步引擎
engine = create_async_engine(DATABASE_URL, echo=True)
# 会话工厂
AsyncSessionLocal = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
)
# 模型基类
Base = declarative_base()
Docker 配置
通过 .env 文件或直接在 docker‑compose.yml 中将 DATABASE_URL 传递给容器。
services:
app:
build: .
env_file:
- .env
.env 示例:
DATABASE_URL=postgresql+asyncpg://avnadmin:password@host:port/defaultdb?sslmode=require
最佳实践
- 单一真实来源:将完整的连接字符串存放在一个变量中。
- 提前验证:如果缺少必需的变量,抛出明确的错误。
- 防御式编程:避免从碎片化的组件拼接 URL。
- 一致的方案:在使用 SQLAlchemy 异步时使用
postgresql+asyncpg://。 - 最小化环境变量:降低在不同环境中出现不匹配或缺失值的风险。
通过将配置合并为单一、经过验证的 DATABASE_URL,可以消除 None 端口错误,并简化在 Docker、Kubernetes 或其他平台上的部署。