on_version_apply is typed as a positional callable, but alembic calls it with keyword arguments

未关闭 适合新手
#1,883 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

  • #1886 来自 @anish-yen —— 已关闭,未合并

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
82/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python, sqlalchemy
领域
backend, databases

调研方向

阅读 alembic/runtime/environment.py 和 alembic/context.pyi 中的 OnVersionApplyFn 定义,然后将它们与 alembic/runtime/migration.py 中使用关键字参数的调用进行比较。更新可调用类型,使其符合文档中说明的关键字参数,并检查相关的类型检查覆盖情况。完成标准是:类型检查器接受按文档编写的回调,并拒绝仅接受位置参数的回调。

由索引模型根据 Issue 内容生成。

描述

Describe the bug

EnvironmentContext.configure(on_version_apply=...) documents that "Each callable accepts no positional arguments and the following keyword arguments: ctx, step, heads, run_args", and MigrationContext.run_migrations() calls each callable that way (alembic/runtime/migration.py):

callback(
    ctx=self,
    step=step.info,
    heads=set(head_maintainer.heads),
    run_args=kw,
)

But the parameter is typed as a callable of four positional arguments, in alembic/runtime/environment.py and in alembic/context.pyi:

OnVersionApplyFn = Callable[
    [MigrationContext, "MigrationInfo", Collection[Any], Mapping[str, Any]],
    None,
]

So a type checker rejects a callback written as documented, and accepts one that fails at runtime.

Expected behavior

The type of on_version_apply describes the keyword arguments, for example:

class OnVersionApplyFn(Protocol):
    def __call__(
        self,
        *,
        ctx: MigrationContext,
        step: MigrationInfo,
        heads: Collection[Any],
        run_args: Mapping[str, Any],
    ) -> None: ...

To Reproduce

from typing import Any

from alembic import context
from alembic.runtime.migration import MigrationContext, MigrationInfo


def as_documented(
    *,
    ctx: MigrationContext,
    step: MigrationInfo,
    heads: set[str],
    run_args: dict[str, Any],
) -> None:
    """Takes the arguments by keyword, as documented."""


context.configure(on_version_apply=as_documented)  # rejected by type checkers


def as_typed(
    context_: MigrationContext, step: MigrationInfo, heads: Any, args: Any
) -> None:
    """Takes them by place, as typed."""


context.configure(on_version_apply=as_typed)  # accepted; run_migrations() then fails

Error

pyright 1.1.407:
error: Argument of type "(*, ctx: MigrationContext, step: MigrationInfo, heads: set[str], run_args: dict[str, Any]) -> None" cannot be assigned to parameter "on_version_apply" of type "((MigrationContext, MigrationInfo, Collection[Any], Mapping[str, Any]) -> None) | None" in function "configure" (reportArgumentType)

pyrefly 1.3.2:
ERROR Argument `(*, ctx: MigrationContext, step: MigrationInfo, heads: set[str], run_args: dict[str, Any]) -> None` is not assignable to parameter `on_version_apply` with type `((MigrationContext, MigrationInfo, Collection[Any], Mapping[str, Any]) -> None) | None` in function `alembic.context.configure` [bad-argument-type]

at runtime, with as_typed:
TypeError: as_typed() got an unexpected keyword argument 'ctx'

Versions.

  • OS: Linux, macOS
  • Python: 3.14
  • Alembic: 1.20.0
  • SQLAlchemy: 2.0.54
  • Database: PostgreSQL 17
  • DBAPI: asyncpg

Additional context

Our workaround takes the arguments both ways: def on_apply(ctx, step, heads, run_args) -> None, the parameters named as alembic passes them.

Have a nice day!

主要语言
Python
星标
4.5k
派生
391
PR 合并指标
30 天内没有已合并 PR

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

sqlalchemy/alembic 的其他 Issue

查看 sqlalchemy/alembic 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。