From: Federico Caselli Date: Thu, 4 Jun 2026 19:33:19 +0000 (+0200) Subject: improve typed column migration docs X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=35e0d85b3343ce5dde441000f5aefe39787ab9b3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git improve typed column migration docs Change-Id: I402580004d1c0d8583ebc9e2852f38d687ba6ae8 --- diff --git a/doc/build/changelog/migration_21.rst b/doc/build/changelog/migration_21.rst index 166f80f9ec..09c67a5b9c 100644 --- a/doc/build/changelog/migration_21.rst +++ b/doc/build/changelog/migration_21.rst @@ -736,16 +736,23 @@ can infer the exact types of columns when accessing them via the Example usage:: - from sqlalchemy import Table, TypedColumns, Column, Integer, MetaData, select + from sqlalchemy import Table, TypedColumns, Column, Integer + from sqlalchemy import MetaData, Named, SmallInteger, select class user_cols(TypedColumns): + # the name will be set to ``id``, type is inferred as Column[int] + # from the Integer SQL type. id = Column(Integer, primary_key=True) - name: Column[str] - age: Column[int] + + # not null String column is generated + name: Named[str] + + # nullable Integer column, the SQL type is manually set SmallInteger + age: Named[int | None] = Column(SmallInteger) # optional, used to infer the select types when selecting the table - __row_pos__: tuple[int, str, int] + __row_pos__: tuple[int, str, int | None] metadata = MetaData() @@ -755,19 +762,20 @@ Example usage:: stmt = select(user.c.id, user.c.name) # Inferred as Select[int, str] # and also when selecting the whole table, when __row_pos__ is present - stmt = select(user) # Inferred as Select[int, str, int] + stmt = select(user) # Inferred as Select[int, str, int | None] The optional :attr:`sqlalchemy.sql._annotated_cols.HasRowPos.__row_pos__` annotation is used to infer the types of a select when selecting the table directly. Columns can be declared in :class:`.TypedColumns` subclasses by instantiating -them directly or by using only a type annotations, that will be inferred when -generating a :class:`_schema.Table`. +them directly, like ``id``, by using only a type annotations, like ``name``, letting +the :class:`_schema.Table` infer SQL type and nullability, or by mixing the two, like ``age``, +to provide explicit column options while inferring nullability and/or SQL type. Other :class:`_sql.FromClause`, like :class:`_sql.Join`, :class:`_sql.CTE`, etc, can be made generic using the :meth:`_sql.FromClause.with_cols` method:: - # using with_cols the ``c`` collection of the cte has typed tables + # using with_cols the ``c`` collection of the cte has typed columns cte = user.select().cte().with_cols(user_cols) ORM Integration diff --git a/lib/sqlalchemy/sql/_annotated_cols.py b/lib/sqlalchemy/sql/_annotated_cols.py index 02c9456092..4264c9a9bc 100644 --- a/lib/sqlalchemy/sql/_annotated_cols.py +++ b/lib/sqlalchemy/sql/_annotated_cols.py @@ -85,8 +85,9 @@ class TypedColumns(ReadOnlyColumnCollection[str, "Column[Any]"]): To resolve the columns, a simplified version of the ORM logic is used, in particular, columns can be declared by: - * directly instantiating them, to declare constraint, custom SQL types and - additional column options; + * directly instantiating :class:`_schema.Column`, to declare constraint, + custom SQL types and additional column options. The annotation is + usually inferred by type checkers from the column instance; * using only a :class:`.Named` or :class:`_schema.Column` type annotation, where nullability and SQL type will be inferred by the python type provided. @@ -94,6 +95,9 @@ class TypedColumns(ReadOnlyColumnCollection[str, "Column[Any]"]): * a mix of both, where the instance can be used to declare constraints and other column options while the annotation will be used to set the SQL type and nullability if not provided by the instance. + The information provided in the instance will take precedence over + the annotation when they are conflicting, for example if setting + nullable=True with an annotation tha does not include ``None``. In all cases the name is inferred from the attribute name, unless explicitly provided. @@ -111,6 +115,7 @@ class TypedColumns(ReadOnlyColumnCollection[str, "Column[Any]"]): class tbl_cols(TypedColumns): # the name will be set to ``id``, type is inferred as Column[int] + # from the Integer SQL type. id = Column(Integer, primary_key=True) # not null String column is generated @@ -119,7 +124,7 @@ class TypedColumns(ReadOnlyColumnCollection[str, "Column[Any]"]): # nullable Double column is generated weight: Named[float | None] - # nullable Integer column, with sql name 'user_age' + # nullable Integer column, with SQL name 'user_age' age: Named[int | None] = Column("user_age") # not null column with type String(42)