Autogenerate¶
iceberg_ops.autogen¶
Autogenerate migration files from a schema diff.
This module powers iceberg-ops revision --autogenerate: it compares an
existing Iceberg table against a target schema (pa.Schema,
SchemaContract, or
pyiceberg.schema.Schema) and produces a list of
MigrationOp, which the renderer then turns into runnable
migration source code.
Public API¶
MigrationOpDataclass describing one op (kind + args).
schema_diff_to_ops(*, current, target, rename_hints=None, format_version=None, spec=None, sort_order=None)()Compute the ops that turn
currentintotargetfor column schema (add / rename / delete / type_change).spec_diff_to_ops(*, current, target, schema)()Compute the ops that turn
currentpartition spec intotarget.sort_order_diff_to_ops(*, current, target, schema)()Emit one
set_sort_orderop when the orders differ.properties_diff_to_ops(*, current, target)()Emit
set_property/remove_propertyops.render_migration_body(ops, *, target_table)()Translate a
list[MigrationOp]into(upgrade_body, downgrade_body)Python source strings ready to drop into aBaseMigrationskeleton.render_migration_file(*, version, description, ops, target_table)()Top-level helper that produces the full migration file source.
write_autogenerated_revision(*, version, description, ops, target_table, out_path, overwrite=False)()Render and write the file to disk (UTF-8, LF line endings). Refuses to replace an existing file unless
overwrite=True.
Scope¶
Every name, value and description rendered into a generated file is written as a Python literal (
repr), so a generated file always compiles and cannot run code smuggled in through a column name or a-mmessage.Schema column ops handle primitive Iceberg types. Type changes are emitted only for Iceberg type promotions (
int -> long,float -> double, decimal precision widening at the same scale).Column names containing a dot are refused: the
MigrationContexthelpers read a dot as a nested struct path.Steps that cannot run as generated (incompatible type changes, nested types, dropping a column the partition spec, sort order or identifier fields reference) or cannot be reversed (column drops, type widening, partition-field removal, sort-order replacement, property removal with an unknown old value) make the affected
upgrade/downgraderaiseNotImplementedErrorbefore any step runs. The generated calls are kept as comments so the author can finish the method by hand.Partition specs and sort orders are diffed for programmatic use; the CLI front-end (
revision --autogenerate) is schema-only.Nullability and doc changes on existing columns are not emitted yet.
- class iceberg_ops.autogen.MigrationOp(kind, args=dict())[source]¶
Bases:
objectA single migration operation that the autogen renderer will emit.
- Variables:
- Parameters:
- iceberg_ops.autogen.properties_diff_to_ops(*, current, target)[source]¶
Emit
set_propertyandremove_propertyops to reconcile the table-property dicts.Keys present in
targetbut absent fromcurrent(or with a different value) becomeset_propertyops. Keys present incurrentbut absent fromtargetbecomeremove_propertyops; the old value is preserved in the args so the generated downgrade body can restore it.- Parameters:
- Returns:
remove_propertyops (args{"key", "old_value"}) first, thenset_propertyops (args{"key", "value", "old_value"}), each group sorted by key.- Return type:
- iceberg_ops.autogen.render_migration_body(ops, *, target_table)[source]¶
Translate a list of
MigrationOpintoupgradeanddowngrademethod body source strings.- Parameters:
ops (
list[MigrationOp]) – Output ofschema_diff_to_ops()and friends.target_table (
str) – Table identifier used in the generated calls. Prefer a bare name ("orders"): the migration context qualifies it with the namespace the migration runs against, so one file serves every namespace. A dotted name ("sales.orders") pins the namespace - the migration always modifies that table.
- Returns:
(upgrade_body, downgrade_body) – Each is a
\n-joined block of indented Python source ready to drop into aBaseMigrationmethod.- Return type:
- Raises:
ValueError – If an op has an unknown
kind.
Notes
The downgrade order is the reverse of upgrade - matches the classic schema-migration convention. When any step of a method cannot run as generated (an incompatible or nested type change, or dropping a column the table layout references, on upgrade; a column drop, type widening, partition-field removal, sort-order replacement or property removal without a recorded old value on downgrade), that method raises
NotImplementedErrorbefore any step runs and keeps every generated call as a comment.
- iceberg_ops.autogen.render_migration_file(*, version, description, ops, target_table)[source]¶
Render a complete migration source file from autogen ops.
Combines
render_migration_body()with the autogen-specific docstring (which carries the “Review every line before running” warning) and the imports the ops need.- Parameters:
version (
str) – Four-digit version, e.g."0007".description (
str) – Free-text description (e.g. the-mmessage). Stored verbatim as thedescriptionclass attribute; a whitespace-collapsed copy goes into the module docstring.ops (
list[MigrationOp]) – Ops to render.target_table (
str) – Table identifier for the generated calls; seerender_migration_body()(prefer a bare name).
- Returns:
Full Python source ready to write to disk.
- Return type:
- Raises:
ValueError – If
versionis not four digits or an op has an unknown kind.
- iceberg_ops.autogen.schema_diff_to_ops(*, current, target, rename_hints=None, format_version=None, spec=None, sort_order=None)[source]¶
Compute the list of
MigrationOpthat turnscurrentintotargetfor column schema only (add / rename / delete / type_change).Uses
diff_schemas()under the hood. Renames are disambiguated byrename_hints(a mapping{old_name: new_name}); when no hint covers a pair, a column rename is emitted as drop + add.- Parameters:
current (
Schema) – Schema of the existing table - must carry stable field-IDs from a real Iceberg table (e.g.table.schema()).target (
Schema|Schema|SchemaContract) – Desired schema.IcebergSchemais passed through;pa.SchemaandSchemaContractare normalised by reusingcurrentfield-IDs for fields with the same name (or via rename hints). Columns ofcurrentmissing from the target are dropped, except for a non-strictSchemaContract(strict=False, the default), which only reconciles the columns it lists and keeps the rest. Nested columns of apa.Schematarget are not compared when they already exist, and cannot be added.rename_hints (
dict[str,str] |None, default:None) – Map ofold_column_name->new_column_name. Each entry causes a singlerename_columnop instead of drop + add. Not accepted with anIcebergSchematarget, whose field IDs already identify renames.format_version (
int|None, default:None) – The table’s format version (table.format_version). When given, an added or changed column whose type needs a newer version (timestamp_ns,timestamptz_ns,unknown,geometryandgeographyneed version 3) raisesValueError.None(default) skips the check.spec (
PartitionSpec|None, default:None) – The partition spec in effect when the deletes run (normallytable.spec()). Deleting a column one of its non-void fields is sourced from is rendered as anupgradethat raises.sort_order (
SortOrder|None, default:None) – The sort order in effect when the deletes run (normallytable.sort_order()); deleting a column it sorts by is rendered as anupgradethat raises. The identifier fields ofcurrentare always checked the same way.
- Returns:
Ordered list of ops. Order: removed -> renamed -> type-changed -> added; this keeps downstream renders deterministic.
- Return type:
- Raises:
ValueError – If a rename hint is inconsistent with the two schemas (unknown old or new name, old name kept, new name already present, two hints sharing a new name), if hints are combined with an
IcebergSchematarget, if the target repeats a column name, if a target column type cannot be mapped, if an op names a column containing"."(theMigrationContexthelpers read a dot as a nested struct path), or if format_version is given and an added or changed type needs a newer one.TypeError – If
targetis not one of the supported types.
Notes
Op kinds emitted:
add_column- args{"name", "iceberg_type", "doc"}rename_column- args{"old_name", "new_name"}delete_column- args{"name"}, plus"referenced_by"(list of str) when the identifier fields, spec or sort_order reference the column; the renderer then blocks theupgrade.type_change- args{"name", "old_iceberg_type", "new_iceberg_type"}
The
iceberg_typevalues arepyiceberg.types.IcebergTypeinstances; the renderer converts them to source code. Atype_changethat is not an Iceberg type promotion is still emitted so the diff stays visible; the renderer turns it into anupgradethat raises.Nullability and doc changes on existing columns are not emitted yet (PyIceberg supports them via
UpdateSchema.make_column_optionalandupdate_column(doc=...)). New columns are added as optional, with the target doc when one is set.
- iceberg_ops.autogen.sort_order_diff_to_ops(*, current, target, schema)[source]¶
Emit a single
set_sort_orderop when the orders differ.The renderer translates this to
ctx.replace_sort_order(target_table, sort_fields=[(col, dir), ...])- replacing the entire sort order rather than adding / removing fields, which matches PyIceberg’sreplace_sort_ordersemantics.Two orders are considered identical iff they have the same number of fields, in the same order, with the same
source_id,transform,directionandnull_order.- Parameters:
- Returns:
[]when the orders are identical, otherwise oneset_sort_orderop with args{"sort_fields": [(column, "asc" | "desc"), ...]}.- Return type:
- Raises:
ValueError – If the orders differ and a target field uses a non-identity transform or a non-default null order (
ascmust beNULLS_FIRSTanddescNULLS_LAST), whichMigrationContext.replace_sort_order()cannot express, or a target field’s source id is not in schema.
- iceberg_ops.autogen.spec_diff_to_ops(*, current, target, schema)[source]¶
Compute add / remove ops to evolve
currentpartition spec intotarget.Compares fields by their
name(the spec-level partition name, not the source column). Eachtargetfield absent fromcurrentbecomes anadd_partition_fieldop; eachcurrentfield absent fromtargetbecomes aremove_partition_fieldop.- Parameters:
current (
PartitionSpec) –table.spec()from the existing table.target (
PartitionSpec) – Desired spec.schema (
Schema) – Used to resolve each partition field’s source column name from itssource_idso the generated migration can pass the correct argument toMigrationContext.update_partition_spec(). A nested source is rendered as its full dotted path ("addr.city").
- Returns:
Ops in removal-first then add order.
- Return type:
- Raises:
ValueError – If a partition field keeps its name but changes its source column or transform; autogenerate cannot evolve a field in place, so give the new field a different name in the target. Also if an added field’s source id is not in schema.
Op kinds emitted –
---------------- –
- remove_partition_field - args {"partition_name"} –
- add_partition_field - args –
{"source_column", "transform_source", "partition_name"}, wheretransform_sourceis Python source such as"transforms.DayTransform()".
- iceberg_ops.autogen.write_autogenerated_revision(*, version, description, ops, target_table, out_path, overwrite=False)[source]¶
Render and write an autogenerated migration to out_path.
The file is written as UTF-8 with LF line endings on every platform.
- Parameters:
version (
str) – Four-digit version, e.g."0007".description (
str) – Free-text description of the migration.ops (
list[MigrationOp]) – Ops to render.target_table (
str) – Table identifier for the generated calls; prefer a bare name so the migration follows the namespace it runs against.overwrite (
bool, default:False) – Replace an existing file at out_path. DefaultFalse.
- Returns:
The absolute path of the written file (string for symmetry with
iceberg_ops.scaffolding.create_revision()).- Return type:
- Raises:
FileExistsError – If out_path exists and
overwriteis false.ValueError – If
versionis not four digits or an op has an unknown kind.