Repository navigation
test(typed-column): contract the rebuilt column layer and the protocol guarantees - #24
Merged
Merged
Conversation
…module The teardown hook calls release_all after every test, and it imported fcntl at function entry, so a serial run on Windows failed each test with ModuleNotFoundError even though no slot lock had ever been taken: slot locks are only ever taken by _acquire_slot, which is reachable exclusively while pooling_active holds. Return before the import when the worker holds no slot, and cover both the serial-run path and the POSIX-only locked path with regression tests.
DDLSource contract: - rename the DataType accessor call sites (column_type -> column_data_type, columns_type() -> columns_data_type()) in the DDL contract and the derivation tests; - add the declaration-accessor contract: the UseColumnType marker is presented as written (identity and .column_class), a field that declares none answers None, and two declarations on one field fail with TypeError. typed_column contracts: - rewrite the column-expression and protocol contracts for the ColumnTypeSupport tables: an explicit declaration is backend-independent, inference is compared against each backend's own suggested_column_types() answer, and a backend refusal (None) skips instead of failing; - the guarantees now assert completeness of the common entries, class-or- None answers, constructibility, the absence of a value-family label, and the UseColumnType escape hatch past a refusal; - add column_helpers.py with the canonical COMMON_TYPES list and the selection mirror; update the README.
…iour - a field declaring only UseColumnType answers None on column_data_type, and a field declaring only UseSqlType answers None on column_type; - a field declaring both answers both, identity preserved on each side (the new `stored` field on the declaration contract model); - SyncContractModel.status is pinned as answering None on column_type; - an unknown field raises KeyError on the model_fields-based accessor (column_type, beside field_python_type), while the metadata-based accessors answer None -- the split is stated in the test.
…egal) Exercises the operation surface through the field proxy, the entry point a model actually uses: - each family's operations are reachable and produce expressions (string transforms and pattern, string-to-integer feeding integer arithmetic, numeric value + transcendental, JSON accessors, temporal operations + interval arithmetic, boolean predicates, binary comparison/null tests); - operations a family does not offer refuse at construction: a missing attribute is AttributeError (like on an int, upper on a number result, created_at + created_at via the temporal operand gate), while a family that never declared arithmetic is refused by the interpreter (TypeError for str + 1) -- both pinned, with the layer difference stated; - backends whose table answers None for an annotation skip the tests that need it (Firebird's dict is the recorded case), since a refusal is a legitimate answer. Core classes only: no backend wrappers, no rendered SQL text.
The contract used to pin the split: the metadata-based accessors answered None/[] for a field the model does not have while the model_fields-based ones raised KeyError. The ruling unifies it -- a field the model does not have is a mistake in the call, not an answer -- so every field accessor now refuses it with KeyError, and an explicitly named unknown field fails the batch accessor the same way. None (or an empty collection) remains the answer for a real field that declares nothing.
Two independent problems, one branch that carries both: - fcntl: release_all() imported the POSIX locking module even when the caller held no slot, which is what made a Windows serial run fail at teardown. Fixed in 5568e13. - typed-column contracts: the shared expression filler has to hand a FunctionCall to a parameter that wraps one, and has to supply ArrayType's required element_type. Core CI pins this branch, so whichever is missing here is missing from every run that depends on it.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
Rebuilds the shared contract layer so that the suite validates the column layer the way the core now defines it: which operations a column offers is decided by its class rather than by a universal base, and every operation is rendered through exactly one hook instead of being assembled from a function name. The contracts pin those guarantees directly, and the suite's own fixtures migrate to the same doctrine, so a backend that runs this suite exercises the new surface rather than the old one.
Six things arrive together, because they are one design seen from different heights.
The typed-column contracts and the protocol guarantees. The column-expression and protocol contracts are rewritten for the
ColumnTypeSupporttables: an explicit declaration is backend-independent, inference is compared against each backend's ownsuggested_column_types()answer, and a backend refusal (None) skips instead of failing. The guarantees assert completeness of the common entries (column_helpers.pynow carries the canonicalCOMMON_TYPESlist and the selection mirror), class-or-Noneanswers, constructibility, the absence of a value-family label, and theUseColumnTypeescape hatch past a refusal.The sync to the new core accessors. The
DDLSourcecontract and the derivation tests follow the core's rename of the DataType accessors (column_type→column_data_type,columns_type()→columns_data_type()). A new declaration-accessor contract pins that theUseColumnTypemarker is presented as written (identity and.column_class), that a field declaring nothing answersNone, and that two declarations on one field fail withTypeError. The declaration accessors are pinned independent of each other: a field declaring onlyUseColumnTypeanswersNoneoncolumn_data_typeand one declaring onlyUseSqlTypeanswersNoneoncolumn_type, while a field declaring both answers both with identity preserved on each side.The DDL-source unknown-field →
KeyErrorcontract. The contract used to pin a split — the metadata-based accessors answeredNone/[]for a field the model does not have while themodel_fields-based ones raisedKeyError. The core's ruling unifies it: a field the model does not have is a mistake in the call, not an answer. The contract is updated accordingly — every field accessor refuses it withKeyError, and an explicitly named unknown field fails the batch accessor the same way, whileNone(or an empty collection) remains the answer for a real field that declares nothing.Core-level
Model.coperation gates. The operation surface is exercised through the field proxy, the entry point a model actually uses: every family's operations are reachable and produce expressions (string transforms and pattern, string-to-integer feeding integer arithmetic, numeric value plus transcendentals, JSON accessors, temporal operations plus interval arithmetic, boolean predicates, binary comparison and null tests), and operations a family does not offer refuse at construction —AttributeErrorfor a missing attribute (as on anint,upperon a number result,created_at + created_atvia the temporal operand gate) andTypeErrorwhere the interpreter itself refuses a family that never declared arithmetic (str + 1). Both are pinned, with the layer difference stated. Backends whose table answersNonefor an annotation skip the tests that need it (Firebird'sdictis the recorded case), since a refusal is a legitimate answer. Core classes only: no backend wrappers, no rendered SQL text.The
DateTimeColumn→TimestampColumnfollow. The column layer models values, anddatetimeis a vendor spelling, not a different value. The contracts follow the core's rename.The bare-
Columndoctrine migration in the shared fixtures. The shared fixtures and the expression filler move off the doctrine that any operation can be called on any column: expressions are built from objects — aFunctionCallfor a parameter that wraps one, aTableExpressionfor parameters annotated as one,ArrayType's requiredelement_typesupplied — matching what the query layer in core now does.These contracts are the gate every backend runs against. They are not an internal asset: each of the nine backends' CI installs this suite and consumes exactly these contracts, so this repo's release branch is what the backends' CI must install. The nine backends currently install from this branch (their
test.ymltestsuite install lines point at@test/typed-column-contracts); once this merges, the follow-up is to repoint those install lines atrelease/v1.0.0.dev17, so that no release branch keeps depending on a feature branch that will be deleted.Type of change
test: Adding missing tests and correcting existing tests for the new featurefeatfixdocsperfchorecibuildrevertBreaking Change
Impact on Backend Developers — yes. The suite is consumed by backend repositories, so its contracts are their API: the DDL-source accessor call sites move to
column_data_type()/columns_data_type(), inference is validated against each backend's ownColumnTypeSupporttables rather than a fixed list, a backend refusal (None) is a legitimate answer that skips rather than fails, and an unknown field is now required to raiseKeyErroron every accessor. A backend built against the previous contracts will see these tests demand the new behaviour.Related Repositories/Packages
This change cannot be used alone; the coordinated branch in each repo is
feature/typed-columns(testsuite:test/typed-column-contracts).python-activerecordfeature/typed-columnsrelease/v1.0.0.dev30(#126)python-activerecord-{postgres,mysql,mariadb,oracle,sqlserver,firebird,clickhouse,snowflake,bigquery}feature/typed-columnsTesting
Test Plan: these contracts are executed by the backends' CI runs against this branch (core
release/v1.0.0.dev30), which currently consume exactly this state of the suite. Per the project rules this repository has no test suite of its own (python-activerecord-testsuite is a shared test suite library with no self-tests), so local full-test verification does not apply here; the repo's own checkers (tools/check_sync_async_parity.py,check_structure.py,check_naming.py,check_markers.py) are the local gate.Checklist
typed_columnREADME follows the accessor renames and the guarantees).Additional Notes
The merge commit in this branch (
3e5371a) is intentional, not an accidental merge. It merges two independent lines of work that were both needed on this branch at the same time: the Windowsfcntlserial-run fix (5568e13, which is the fix itself) and the typed-column contracts line. Thefcntlfix is worth stating on its own: the pool's teardown hook callsrelease_allafter every test, and it imported the POSIX locking module at function entry, so a serial run on Windows failed each test withModuleNotFoundErroreven though no slot lock had ever been taken — slot locks are only ever taken by_acquire_slot, reachable exclusively whilepooling_activeholds. The fix returns before the import when the worker holds no slot, and covers both the serial-run path and the POSIX-only locked path with regression tests. It is in this branch because CI pins this branch: whichever of the two is missing here is missing from every run that depends on it.On the branch name: the testsuite's coordinated branches carry the
test/*prefix (contract changes are test code), which is why this branch istest/typed-column-contractsrather thanfeature/*.