Skip to content

amsdal_models

v0.8.8 - 2026-06-15

Added

  • Code generation now supports the decimal core type. CoreTypes.DECIMAL maps to Decimal in BASIC_TYPES_MAP, so generated models annotate decimal fields as Decimal. Schema precision/scale are emitted as pydantic Field(max_digits=..., decimal_places=...) constraints, and decimal default values are rendered as Decimal('<value>') rather than bare constants.

Fixed

  • Unconstrained decimals (whose precision/scale are None) no longer emit Field(max_digits=None, decimal_places=None). build_assign_node now skips keyword constraints whose value is None.

v0.8.7 - 2026-06-12

Fixed

  • Historical select_related now projects the FK child's _metadata. The child's _metadata JSON is already computed inside the sr_N child subquery, but _build_nested_only_for_historical previously surfaced only the child's schema fields + range_key, so the decoded child carried no metadata and its _metadata.object_version stayed LATEST. Combined with the select_related child now being is_from_lakehouse=True, accessing metadata on (or comparing) a select_related child issued an extra get_metadata() DB query per object — an N+1 regression. The child _metadata column is now added to the outer projection.

Changed

  • Model.__eq__ is now pk-only (type + primary key). Previously, in sync mode with lakehouse-loaded objects, it additionally compared get_metadata().object_version, which issued a DB query and diverged from async mode (already pk-only). Equality is now consistent across sync/async, matches __hash__ and ExternalModel.__eq__, and performs no I/O. Two instances of the same object at different versions now compare equal via ==.

Added

  • Model.equals_with_version() / Model.aequals_with_version() — opt-in equality that also requires the same object_version (the previous sync __eq__ behavior), available in both sync and async forms.

v0.8.6 - 2026-06-10

Fixed

  • select_related FK targets are now constructed with the loaded ModelState (adding=False, inheriting the parent's is_from_lakehouse) instead of defaulting to adding=True. Previously a select_related target arrived as a nested dict and was built without state, so child.is_new_object was True and a cascade save() on the parent re-INSERTed the FK target, producing a duplicate row. The loaded state is now seeded recursively at every nesting depth (e.g. select_related('profile__company')); Reference and LegacyModel targets are unaffected.

v0.8.5 - 2026-06-08

Fixed

  • Prefetch(lookup, to_attr=...) now rejects a to_attr that collides with an existing relation accessor (forward/reverse foreign-key or many-to-many) on the parent model, raising ValueError during apply_prefetches. Previously the collision validator only checked pydantic model_fields, so a to_attr matching a relation accessor (e.g. Author.book_set, Post.tags) passed validation and then silently shadowed the live descriptor on that instance, breaking the relation read on subsequent access.

v0.8.4 - 2026-06-05

Fixed

  • Historical single-PK FK address-path equality filters (e.g. Model.objects.filter(<fk>___address__object_id='p-1')) now render the FK primary-key leaf with output_type=str, so glue applies the cast(... as TEXT) / text-comparison rule on SQLite- and PostgreSQL-historical backends. Previously the leaf was emitted with output_type=None, leaving the comparison unwrapped and unable to match the stored value. The state backend is unaffected (it resolves output_type to None regardless).

v0.8.3 - 2026-06-04

Fixed

  • ModelClassLoader.load(unload_module=True) (used by ClassSchemaLoader._load_classes() and other schema-introspection paths such as amsdal migrations new) no longer floods stderr with "Late registration of 'X' after freeze()" / "unresolvable deferred refs" warnings. The throwaway model reimport is now wrapped in with allow_late_registration(): so the rebuilt classes bypass the runtime late-registration handler — they are intentional, not user mistakes.
  • StateMigrationExecutor._register_models and its async sibling now snapshot the internal _buffer into a local list and clear it up-front before iterating. Prevents buffer mutation-during-iteration when registration triggers further inserts into _buffer (recursive registration paths).

v0.8.2 - 2026-06-01

Changed

  • Strict-bootstrap lifecycle moved from implicit AmsdalManager peeking into an explicit ModelLoadingState namespace on AmsdalModelsContext. The composition root now drives state transitions via the new public functions freeze(*, strict=False), unfreeze(), and set_strict(value) — all importable from amsdal_models.classes.loading. Removes the cyclic amsdal_modelsamsdal dependency that read AmsdalManager._Singleton__instances (mangled singleton attribute) and amsdal.configs.main.settings.AMSDAL_STRICT_BOOTSTRAP. _on_model_created reads state via current_models_context() and no-ops when no context is active (standalone use or pre-bootstrap). Error message and log text now reference freeze() instead of AmsdalManager.setup().

v0.8.1 - 2026-06-01

Fixed

  • is_partial_namespace rejected pydantic's _ModelNamespaceDict (a dict subclass) when called from the cython-compiled classes/utils wheel due to Cython 3.x strict annotation_typing enforcement on the dict[str, Any] parameter. The annotation is now Mapping[str, Any] (from collections.abc) — Cython does not enforce ABCs as concrete C types, so any dict-like namespace is accepted at runtime; type info is preserved for mypy.

v0.8.0 - 2026-06-01

Added

  • New ModelState lifecycle namespace on every Model instance, exposed as _state (with _state.adding: bool and _state.is_from_lakehouse: bool). The flags are explicitly populated by the QuerySet at execute time, flipped by the create / save flow, and rolled back on post-create exceptions — making instance lifecycle a single source of truth instead of being inferred from metadata content. The legacy _is_new_object and _is_from_lakehouse private slots are removed; the is_new_object and is_from_lakehouse public properties are preserved and now delegate to _state.
  • Forward-FK fields are now class-level descriptors backed by a private pydantic field. <fk> is exposed as ForwardFKDescriptor; the underlying field is stored under _<fk>_ref with alias='<fk>'. Model.model_dump() and model_dump_refs() default to by_alias=True, so output uses the public name <fk>. Consumers that explicitly pass by_alias=False, or call the underlying Pydantic dump directly, will see the internal key _<fk>_ref. populate_by_name=True is set on Model and LegacyModel, so model_validate({'<fk>': ...}) continues to work as before.
  • Reverse-FK accessors are now auto-registered on FK target classes. Given class Book(Model): author: Author, Author gains a book_set attribute that returns a RelatedSet[Book] scoped to author=<author_ref>. Class-level access (Author.book_set) returns a ReverseFKDescriptor for introspection; instance-level access on an unsaved parent returns a RelatedSet with an empty cache (no DB hit). The reverse-name defaults to <reverser_lower>_set; pass related_name='custom' on ReferenceField to override or related_name='+' to disable. Two FKs that resolve to the same default reverse-name on a target raise AmsdalReverseFKConflictError at class build time — add an explicit related_name to one of them. The registry is exposed as Author.__reverse_foreign_keys__: dict[str, ReverseFKDescriptor]. For IDE annotation, the new ReverseRelation['Book'] type alias is available under amsdal_models.classes.relationships.
  • Class-level <Source>.<m2m>_through (e.g. Post.tags_through) now resolves directly to the through-model class (returns type[ThroughModel], not a wrapper). Use .objects on it for through-table queries — active links, historical links, filtering by extra through columns, or working with explicit _address__* / _metadata__* scopes.
  • Accessor filtering on a RelatedSet (post.tags.filter(...), reverse-FK equivalent) now wires a parent-pinned membership EXISTS automatically and returns a target-model QuerySet. On lakehouse it also injects _address__object_version=LATEST + _metadata__is_deleted=False defaults onto the target queryset; user-supplied predicates on either axis replace the corresponding default. Fully chainable end-to-end (post.tags.filter(name='py').order_by('name').count().execute()), state + lakehouse, sync + async.
  • Relation filtering through __ notation now covers the full matrix at the root queryset: membership (filter(tags=t), tags__in=[...], tags___address__object_id=...), attribute spans (tags__name='X', multi-hop tags__category__parent__name='X'), reverse direction via related_name, exclude/isnull, compositional with Q. Each .filter() / .exclude() allocates its own through-table JOIN. No select_related / prefetch_related required at the filter site.
  • Two write-time guards landed on RelatedSet: (1) add / remove / set on a custom-through M2M raise AmsdalError with the hint Cannot {op}() on M2M {name!r} with a custom through-model; create {through_name} rows directly. — write the through-row directly instead. clear() remains allowed. (2) add() rejects a non-LATEST Reference, or a Model whose _metadata.is_latest is False, with ValueError — rebuild a LATEST-pinned reference or re-fetch the model.
  • RelatedSet.add() is now idempotent by object_id — re-adding an already-linked target is a no-op (no AmsdalUniquenessError). Set semantics: add → remove → add collapses to "present". Two Python instances of the same DB row (same PK) collapse to one through-row.
  • Per-hop version/deletion directives on relation chains in historical / lakehouse queries: <rel>___address__object_version=<v>, <rel>___address__class_version=<v>, <rel>___metadata__is_deleted=True|False. Stripped from the predicate at planning time and applied to the corresponding JOIN (not as value filters). Only is_deleted is recognized in the per-hop ___metadata__ form; ___address__class_version=Versions.ALL on a hop raises ValueError — query the through-model (M2M) or target model (reverse-FK) directly for that case.

Changed

  • Reverse-FK (Author.book_set) and M2M (Post.tags) accessors now return RelatedSet[T] instead of QuerySet (lazy) / ManyList (eager). RelatedSet is a list subclass with cache-aware deferred semantics: when prefetch or .add() has populated the cache, reads use it directly; otherwise reads issue optimal SQL (SELECT COUNT(*), SELECT 1 LIMIT 1, SELECT … LIMIT/OFFSET …, full SELECT * for materialization). Full read+write API (iter, len, count, exists, [i], [i:j], in, bool, filter/exclude/order_by → QuerySet, add/remove/set/clear) plus async siblings (acount, aexists, aadd, etc.). Mode contract: named methods strict by mode (sync method in async mode raises with hint to async sibling and vice versa); Python magic methods (__iter__, __len__, __getitem__, __contains__, __bool__) cache-aware (work in any mode when cache populated; raise in async mode when not). Write methods on saved parents are wrapped in @transaction / @async_transaction. Unsaved-parent writes mutate an in-memory buffer; parent.save() flushes the buffer (M2M through-rows, reverse-FK child FK updates) inside the same TX as the parent save — partial failure rolls back the whole save. .add(t, t) deduplicates by object_id (two Python instances of the same DB row collapse to one through-row). Detaching a child via .remove() / .clear() / shrinking .set() on a non-nullable reverse-FK buffers the change and fails at flush with pydantic.ValidationError (the child's FK column rejects None) — reparent the child or delete it instead. Passing a reverse-FK kwarg to a model constructor (e.g. Author(book_set=[...])) now raises AmsdalReverseFKConstructorError instead of being silently ignored. The ManyList class is removed — code that imported it or did isinstance(x, ManyList) must migrate to RelatedSet. The ClassLevelReverseManager and ClassLevelM2MManager classes are also removed — Author.book_set (class-level) returns the descriptor itself for introspection; use Book.objects.filter(...) for class-level queries. ClassLevelThroughAccessor (Post.tags_through) is preserved. Migration: replace author.book_set.execute()list(author.book_set); replace await author.book_set.aexecute()[x async for x in author.book_set]; replace Author.book_set.objects.filter(...)Book.objects.filter(author=X). Iteration (for b in author.book_set), filtering (author.book_set.filter(...).execute()), and constructor M2M assignment (Post(tags=[t1, t2])) continue to work unchanged.
  • RelatedSet (reverse-FK / M2M accessor) now follows a unit-of-work semantics. Writes (add / remove / set / clear) are sync regardless of mode and mutate in-memory pending deltas; DB is touched only when parent.save() / await parent.asave() flushes the deltas. The aadd / aremove / aset / aclear methods are removed — use the sync variants. Attribute access (parent.tags, author.book_set) is now always sync and returns a RelatedSet instance directly — no coroutine wrapping in async mode (fixes the long-standing case where await parent.relation was required for an unsaved parent despite no I/O being needed). Internally the cache state is split: _cache is the snapshot loaded from DB (or None if not loaded); _pending_add / _pending_remove / _pending_clear carry the dirty changes. Reads use _effective_items() = cache ± pending. In async mode, magic methods (len, iter, [i], in, bool) raise on no-cache; named methods (count, exists) are strict by mode (use acount / aexists in async); await rs (or async for x in rs) loads cache and consolidates pending. add() items are validated/coerced via pydantic at call site — invalid input fails fast. Dedup is by object_id — two Python instances of the same DB row collapse to one through-row. Non-nullable reverse-FK detach (remove / clear / shrinking set) is buffered; at parent.save() the flush sets the child's FK to None and fails Pydantic validation with pydantic.ValidationError — reparent or hard-delete the child instead. Prefetch continues to populate cache as before — no user-visible change for the prefetch path. Migration: drop the a prefix and await from any rs.aadd(...) / rs.aremove(...) / rs.aset(...) / rs.aclear(...) calls — they're synchronous now. If you had to await parent.relation because of the descriptor-coroutine issue, you can drop the await — just access the attribute directly.
  • M2M flush at parent.save() / parent.asave() now correctly handles items in rs.add(...) regardless of their state. NP Model items are auto-saved before through-row creation; EP Models are converted to References; explicit References pass through unchanged. The through-row's source-field is always a frozen Reference to parent (capturing the exact version at link-creation time), and target-field is a Reference with object_version=LATEST. Internally: _M2MStrategy.db_add / db_remove / db_clear (and async siblings) now require parent_ref: Reference and items: list[Reference] — all normalization is done by Model._normalize_m2m_items / _anormalize_m2m_items inside _process_m2m_fields. Strategy is now a pure data-path layer with no Model knowledge. Auto-load through for x in rs, len(rs), await rs for M2M now returns target instances directly (via the unified accessor read path), instead of populating cache with raw through-rows. Note: direct Model.objects.bulk_create([...]) still does NOT flush m2m through-rows — m2m persistence happens only via parent.save() (unchanged behavior, consistent with main).
  • M2M through-rows are now snapshotted per Author version in the lakehouse on every Author.save() / Author.asave(), restoring the historical-storage semantics that existed prior to the RelatedSet refactor. Previously, re-saving an Author without touching its m2m UoW state left the new version with zero through-rows, so Author.objects.using('lakehouse').latest().execute()[0].books would return []. The fix captures previous_ref = self.build_reference(is_frozen=True) before _create/_update, then _process_m2m_fields(..., previous_ref=...) / _aprocess_m2m_fields(..., previous_ref=...) invokes a new _M2MStrategy.db_copy_to_new_version(previous_ref, new_ref, exclude_target_refs=...) (and adb_copy_to_new_version) that reads the lakehouse-stored through-rows for the previous Author version and re-emits them with the new frozen source ref via bulk_create(..., using='lakehouse', force_insert=True). Default-DB through-rows are not touched (they were already correct). Edge cases: INSERT (no previous version) and pending_clear are skipped; UoW deletes are honored by passing cached._pending_remove as exclude_target_refs; cache-miss falls back to Model._build_m2m_strategy_from_meta(m2m_name) to materialize a strategy from MANY_TO_MANY_FIELDS metadata.
  • Internal refactor of QuerySet condition tracking: each .filter() / .exclude() call is now stored as a separate Q in QuerySetBase._filter_groups: list[Q] instead of being eagerly AND-combined into a single _conditions slot. get_conditions() keeps its previous public contract by AND-combining the groups on access; a new get_filter_groups() accessor exposes the per-call list for query-builder consumers that need to allocate per-filter JOIN aliases (foundation for upcoming reverse-FK / prefetch_related work). No behavioural change for existing forward-FK queries.
  • Deferred forward-reference resolution in meta/deferred_registry.py no longer depends on typing.ForwardRef._evaluate — a private CPython API whose signature drifts between minor versions (3.9 added positional recursive_guard, 3.10 made it keyword-only, 3.13 added type_params and format, 3.14 may drop the private name entirely under PEP 649). The new _resolve_forward_ref helper uses a two-tier strategy: a fast path that resolves simple identifiers (e.g. ForwardRef('User')) via direct mod_globals dict lookup — no eval, no compile, no typing internals; and a slow path that falls back to eval(compile(arg, '<deferred-ref>', 'eval'), mod_globals, None) for hypothetical compound expressions (e.g. 'list[User]') — currently unused but kept for robustness. Cross-version safe on Python 3.11–3.13 (currently supported), forward-compatible with 3.14: the only ForwardRef attribute we touch is __forward_arg__, which has been stable since 3.5. The slow path is also safer than the prior implementation because the fast path bypasses eval entirely for the 100% of real call sites where __forward_arg__ is a simple identifier (confirmed: all ForwardRef(...) instantiations in the codebase pass a single class name, since resolve_model_type() pre-unwraps Optional/list/Union wrappers before deferred-registry registration).

Fixed

  • Prefetch('a__b__c') dunder-traversal now traverses all hops and populates the relationship cache on each intermediate target instead of silently dropping every segment after the first. Previously the executor only processed the head and users got N+1 queries through ReferenceLoader for the dropped tail. Composition is unchanged: Prefetch('a__b', queryset=A.objects.filter(...)) applies the queryset to hop 1 and prefetches b on the collected targets; Prefetch('a__b', queryset=A.objects.prefetch_related('c')) prefetches both b and c on hop-1 targets at the same level. to_attr attaches to the leaf hop only (Prefetch('books__publisher', to_attr='pub') puts the publisher on each book, not on the author). Forward-FK, reverse-FK, M2M and any mix thereof compose; sync and async paths are at parity. Validation relaxed: Prefetch('books', to_attr='all_books') no longer requires queryset= — defaults to target.objects.all(). Additionally, async forward-FK prefetch no longer silently nulls every target when the parent holds a Reference in _<fk>_ref (the post-.aexecute() materialization state) — the executor now reads the public <fk>_reference accessor directly, bypassing the descriptor's coroutine path.
  • A model instance constructed in memory with a pre-set custom primary key (e.g. Post(id='abc')) is no longer falsely marked as already-saved. Previously the is_new_object flag was inferred from the presence of an object id, which mis-classified pre-set custom-PK instances and caused save() to silently take the update path instead of creating the row. The flag is now driven by an explicit _state.adding lifecycle flag set by the QuerySet at load time, so freshly-constructed instances are always treated as new regardless of PK shape.

v0.7.8 - 2026-05-07

Security

  • PII fields are no longer overwritten with ciphertext on the live model instance after save(). Encryption now produces ciphertext only in the outgoing glue.Data; a per-instance ciphertext cache (__pii_ciphertext_cache__) skips redundant KMS calls when the plaintext has not changed. obj.<pii_field> always returns plaintext on the live instance.
  • filter() / order_by() on a PII field now raises PIIQueryError instead of silently building a query that compares against random-IV ciphertext (which would never match). Detection walks FK / relation paths recursively (profile__email).
  • PII fields cannot be declared in __primary_key__ or any UniqueConstraint. Raises PIIInvalidFieldUsageError at class build time — random-IV ciphertext makes both checks vacuous.
  • New EncryptedStr(str) wrapper masks PII values in repr() / str() / format() so plaintext does not leak into logs or exception messages. Raw ciphertext available via the .ciphertext property.
  • PII marker switched from Field(json_schema_extra={'pii': True}) to a sentinel-based Annotated metadata so the marker no longer surfaces in OpenAPI schemas.
  • Async-only crypto operations (decrypt batch, KMS batch encrypt) fail loudly via @async_mode_only / @sync_mode_only decorators when invoked from the wrong runtime mode.
  • PII encryption consolidated into model_to_data / amodel_to_data; all save paths (single, bulk, manager-level) encrypt at the same audit boundary.

Fixed

  • Async bulk_aupdate / bulk_adelete lock leak: a RuntimeError between acquire and release left the postgres FOR UPDATE lock held until the surrounding transaction rolled back. Lock release now lives in finally.
  • _check_unique issued an N+1 SELECT-by-unique-fields on every save() even in state mode where the underlying RDBMS already enforces UNIQUE at the engine level. The pre-check is skipped in state mode; lakehouse-only mode preserves the previous behaviour (no DB-level UNIQUE there). UNIQUE-constraint violations raised by the connection are translated into AmsdalUniquenessError so the user-visible exception type is unchanged.
  • Failed FK resolution during __getattribute__ now logs a WARNING (amsdal_models.classes.model logger) and returns the original Reference, instead of silently swallowing the error and leaving callers unaware of the failure.

Performance

  • FK access (obj.<fk>) caches the resolved Model on the instance: repeated access fires a single SELECT instead of N. The cache is keyed by field name with the underlying Reference as identity tag, so reassignment (obj.fk = new_ref) auto-invalidates. The original Reference is preserved on the instance — obj.<fk>_reference still returns the user's frozen reference, save flow unaffected.
  • __getattribute__ fast-path: non-FK attribute reads (obj.name, obj.user_id) skip the entire reference-resolution branch via a __foreign_keys__ membership check, removing the per-attribute model_fields lookup + annotation comparison.
  • _process_nested_objects (invoked on every save) skips setattr when _process_nested_field returns the same object (identity check). Avoids Pydantic re-validation for plain fields and unchanged References.
  • After Model→Reference conversion in _process_nested_objects, the original Model is primed into the FK cache so the next obj.<fk> access returns it from memory instead of round-tripping to the DB.
  • Hot-path imports (Iterable, Mapping, Reference, DataApplication, AsyncDataApplication) hoisted to module top; previously re-imported on every _attach_storage / model_validator / __setattr__ call.
  • bulk_aupdate / bulk_adelete no longer await get_latest_schema_version per object inside the loop — hoisted outside as a single await for the whole batch.
  • get_pii_fields(model_class) is now O(1) on hot paths via per-class cache (__pii_fields__ slot); no typing.get_type_hints invocation, tolerates unresolved forward refs in unrelated fields.

Changed

  • New UniqueViolationError re-exported from amsdal_models.classes.errors. Originates in amsdal-glue-core>=0.1.10. Connection-layer UNIQUE violations propagate as this typed exception through the operation pipeline.
  • bulk_create / bulk_acreate / bulk_update / bulk_aupdate failure handling consolidated into a _translate_bulk_failure helper (UniqueViolationError → AmsdalUniquenessError, otherwise BulkOperationError).

v0.7.7 - 2026-05-04

Fixed

  • filter(_address__object_id__in=[(s, c), ...]) on composite-PK models silently dropped results (state) or raised text = ANY(int[]) (historical/postgres). The local-PK address path (fk_chain == ()) now generates per-segment OR-of-AND-blocks instead of falling through to a zip(db_fields, list_of_tuples) mapping that produced malformed conditions. Both <fk>___address__object_id__in=[...] and the local _address__object_id__in=[...] form are covered.

v0.7.6 - 2026-04-30

Fixed

  • select_related alias collision in sibling branches: select_related('a__child', 'b') produced the same sr_2 alias for a.child and b, causing duplicate JOIN aliases in the generated SQL (silently wrong results on some dialects).
  • Lakehouse _process_data accumulated the version-prefix across loop iterations, so when the first checked schema version did not match the row, subsequent versions were tested against junk and the entire FK column silently dropped from the result.
  • Async build_only ignored self._queryset._annotations: the same QuerySet emitted different SQL in sync vs async for queries combining annotate() with no only/select_related.
  • Async _acreate_instance ignored _strict_class_version: sync raised ValidationError on incompatible historical rows while async silently returned LegacyModel wrappers. The same QuerySet produced different object types depending on which path the caller used.
  • Sync and async count() raised bare Exception on failure while query() raised AmsdalQuerySetError. Callers using except AmsdalQuerySetError could not intercept count failures.

Changed

  • New WhereBuilder (query_builders/where/) — backend-agnostic translator from Q trees to glue.Conditions. Backend specifics localised in BackendAdapter Protocol; state and historical builders share the same WHERE pipeline.
  • Sync/async historical query-builder paths unified via shared sync cores plus eager-resolve of async dependencies (build_where, build_only, _process_data, _create_instance / _acreate_instance). ~2000 LOC of byte-identical duplication eliminated.
  • New query_builders/select_related.py module: allocate_aliases (build-side, fixes B4) and resolve_nested_alias (read-side path rewriting) live next to each other; BaseQueryBuilder._extract_select_related delegates to the helper.
  • New query_builders/_dedupe.py with the dedupe_refs generator helper. Replaced O(n²) if x not in result: result.append(x) patterns in query builders with O(n) set-based deduplication.
  • Pre-fetched versions_cache for HistoricalSchemaVersionManager.get_all_schema_properties walks once per build instead of per row × per FK.
  • Reference.object_id_segments() and Address.object_id_segments() / Address.normalize_object_id_to_scalar() hoisted to amsdal_utils (requires amsdal_utils >= 0.6.2); the local helper where.coercers.object_id_as_segments removed.
  • entity_name is now a cached_property; schemas.object_schema.get_model_foreign_keys is @cache-decorated; parse_address_path short-circuits on the hot path.
  • Per-WhereBuilder cache for (model, fk_field) → db_fields; select_related head-index built once per build; FK fields stored as frozenset for membership checks.
  • WhereBuilder.make_condition / build_condition are the single sources of truth for the glue.Condition shape; _latest_version_conditions consolidates the next_version IS NULL OR next_version = '' filter; _legacy_or_native_instance is the shared sync core for _create_instance / _acreate_instance; _extract_count_from_result is the shared core for count().
  • Stringly-typed 'object_id', '_metadata', 'ref', 'object_version', '__ref__object_id' literals replaced with the constants exposed in query_builders/reserved.py.

v0.7.5 - 2026-04-10

Added

  • Added audit data propagation (created_by, updated_by, action, changes) through DeleteData.metadata for proper audit logging on delete operations

v0.7.4 - 2026-03-27

Added

  • Added __ordering__ model attribute for default query ordering

Fixed

  • Fixed transform_count to not include limit and order_by in count queries

v0.7.3 - 2026-03-20

Fixed

  • Fixed order_by with select_related for State, Historical, and Async Historical query builders
  • Fixed resolve_model_type to handle Union types with multiple non-None arguments
  • Fixed _storage attribute check on File-like objects to use getattr instead of hasattr

v0.7.2 - 2026-03-19

Fixed

  • Fixed migration import to use a wrapper module instead of mutating the real one, keeping sys.modules intact for ModelClassLoader

v0.7.1 - 2026-03-17

Fixed

  • Fixed migrations issue with forward reference resolving: added synthetic module registration, deferred PK/FK resolution, and ClassManager integration for compiled migration classes
  • Fixed QuerySet bugs and historical query builders

v0.7.0 - 2026-02-25

Added

  • Added PIIStr field type and get_pii_fields() for PII (Personally Identifiable Information) field detection
  • Added CryptoService protocol and registry (register_crypto_service / get_crypto_service) for field-level encryption
  • Full roundtrip support for PII fields: Model → ObjectSchema → Model preserves PIIStr type annotations, including PIIStr | None (Optional) fields

v0.6.5 - 2026-02-20

Fixed

  • Fix for schemes without foreign_keys specified

v0.6.4 - 2026-02-18

Fixed

  • Fixed select_related for historical query builders to filter by NULL or empty NEXT_VERSION_FIELD, ensuring only the latest version of related objects is returned

v0.6.3 - 2026-02-13

Fixed

  • Fixed pyruff.fix running in isolated mode, ignoring project pyproject.toml config (e.g. force-single-line = true for isort), which caused phantom migrations with merged imports

v0.6.2 - 2026-02-16

Changed

  • Replaced ruff-api with pyruff for code formatting and linting

v0.6.1 - 2026-02-15

Performance

  • Replaced black formatter with ruff-api for code generation (167x faster, eliminates blib2to3 memory leak)
  • Added ClassSourceBuilder._build_class_source cache clearing in ClassManager.teardown()

Fixed

  • Fixed temp file leak in _format_code() — files in /tmp were not cleaned up after ruff check --fix

v0.6.0 - 2026-02-05

Added

  • QuerySet.none()

v0.5.33 - 2026-01-24

Added

  • Added default_manager to model in case of objects override

v0.5.32 - 2026-01-20

Fixed

  • Fix for loading models from different modules

v0.5.31 - 2026-01-16

Fixed

  • Allow to pass None to filter for optional references
  • Added support for storage_metadata attributes in class source generation

v0.5.30 - 2025-12-22

Added

  • Added support for python 3.13

v0.5.29 - 2025-12-21

Change

  • Update pydantic to 2.12

v0.5.28 - 2025-12-18

Fixed

  • Fixed issue with updating FK field

v0.5.27 - 2025-12-16

Added

  • aexecute added to External Connections

v0.5.26 - 2025-12-16

Fixed

  • Fix for external connection

v0.5.25 - 2025-12-11

Fixed

  • Fix for reference filter by string

v0.5.24 - 2025-11-26

Fixed

  • Migration context

v0.5.23 - 2025-11-24

Fixed

  • Fixes for migration

v0.5.22 - 2025-11-14

Fixed

  • Fix for state nested filters

v0.5.21 - 2025-10-30

Fixed

  • Fix for circular import

v0.5.20 - 2025-10-20

Fixed

  • Fix for external postgres connection

v0.5.19 - 2025-10-20

Added

  • External connections support

v0.5.18 - 2025-09-24

Changed

  • Revert "Remove timestamp fields from model_to_object_schema output"

v0.5.17 - 2025-09-24

Changed

  • Remove timestamp fields from model_to_object_schema output

v0.5.16 - 2025-09-19

Fixed

  • Adjusting AsyncFileWrapper

v0.5.15 - 2025-09-15

Fixed

  • Fixes for async migrations

v0.5.14 - 2025-09-11

Fixed

  • Fixes for closed file

v0.5.13 - 2025-09-08

Fixed

  • Fixes for contrib migration

v0.5.12 - 2025-09-05

Fixed

  • Fixed aopen->read for DBStorage

v0.5.11 - 2025-09-04

Fixed

  • Fix aopen for db storage

v0.5.10 - 2025-09-03

Changes

  • File storages

v0.5.9 - 2025-08-15

Changes

  • Changes for plugins to support migration generation

v0.5.8 - 2025-07-31

Fixed

  • Fixed MacOS build

v0.5.7 - 2025-07-29

Fixed

  • Fixed QS.only for lakehouse postgres

v0.5.6 - 2025-07-28

Fixed

  • Fixes for QS.only

v0.5.5 - 2025-07-01

Added

  • Added support for Vector schemas and annotations

v0.5.4 - 2025-06-10

Fixed

  • Fix for postgres and async methods

v0.5.3 - 2025-05-28

Fixed

  • Stubgen fix

v0.5.2 - 2025-05-28

Fixed

  • Fix for Model typing

v0.5.1 - 2025-05-22

Fixed

  • Adjustments and fixes for async mode

v0.5.0 - 2025-05-19

Added

  • StorageMetadata
  • Adjusted migrations to use connection-level transfer data for historical connections
  • Migrations refactoring to decouple logic

v0.4.19 - 2025-05-16

Added

  • Added support for optional References

v0.4.18 - 2025-05-16

Added

  • Support for reference fields

v0.4.17 - 2025-05-16

Added

  • Support for lists of date, datetime and byte objects

v0.4.16 - 2025-05-12

Added

  • Exception for m2m assignment

v0.4.15 - 2025-05-06

Added

  • Handle bytes and date/datetime in TypeModels.

v0.4.14 - 2025-05-06

Added

  • Added support for enum values

v0.4.13 - 2025-05-04

Fixed

  • Fix for TypeModels in lists

v0.4.12 - 2025-04-17

Fixed

  • Fixed load class schemas sorted by dependency order.

v0.4.11 - 2025-04-10

Added

  • Added support for enums in properties

v0.4.10 - 2025-04-07

Changed

  • Suppress ruff error logs for cleaner output

v0.4.9 - 2025-04-04

Fixed

  • Fixed support for pydantic 2.11

v0.4.8 - 2025-04-03

Fixed

  • Fixed getattr for async references

v0.4.7 - 2025-04-03

Added

  • amodel_dump added to model

v0.4.6 - 2025-03-17

Fixed

  • Fixed check unique using lakehouse

v0.4.5 - 2025-03-17

Fixed

  • Fixed check unique using lakehouse

v0.4.4 - 2025-03-11

Fixed

  • Get metadata by class name

v0.4.3 - 2025-03-05

Fixed

  • Add missing migration templates

v0.4.2 - 2025-03-05

Changed

  • Use db_field from PropertyData

v0.4.1 - 2025-02-24

Fixed

  • Fixed stub generations

v0.4.0 - 2025-02-24

Added

  • Support for FKs and M2Ms
  • Python classes

v0.3.9 - 2025-02-06

Added

  • Locking on updates and deletes (locking-in-querysets)

v0.3.8 - 2024-12-13

Changed

  • Model is now awaitable (awaitable-model)
  • Limit usage of managers in different modes (limit-managers-usage)

v0.3.7 - 2024-12-09

Added

  • DependencyModelNames.async_build_from_database method (async-build-from-database)

v0.3.6 - 2024-12-09

Added

  • Add select_related to BaseManager (base-manager-select-related)

Fixed

  • Add object_id when using select_related (object-id-select-related)

v0.3.5 - 2024-12-06

Fixed

  • Added async pre_update hook to File mode (pre-update-file-async-hooks)

v0.3.4 - 2024-12-06

Added

  • Async hooks (async-hooks)

v0.3.3 - 2024-12-04

Added

  • Async support (async-support)

v0.3.2 - 2024-12-02

Fixed

  • Add original_class to LegacyModel (add-original-class-to-legacymodel)

v0.3.1 - 2024-11-28

Fixed

  • Optimized display_name to avoid extra request to lakehouse (optimized-display-name)

v0.3.0 - 2024-11-27

Changed

  • Metadata redesign: moving metadata into historical connection (metadata-redesign)

v0.2.7 - 2024-11-04

Added

  • Nested filtering support (nested-filtering)

v0.2.6 - 2024-10-28

Added

  • Python3.12 support added (python3.12-support)

v0.2.5 - 2024-10-24

Added

  • Added select_related feature (select-related)

Fixed

  • Fix Partial models (partial-models)

v0.2.4 - 2024-10-18

Fixed

  • Fixed ordering of custom code (custom-code-ordering)

v0.2.3 - 2024-10-18

Fixed

  • Handle updates with specific object version (specific-version-update)

v0.2.2 - 2024-10-16

Fixed

  • Fixed prior version (prior-version)

v0.2.1 - 2024-10-15

Fixed

  • Fixed limits setting to 0 by default (fixed-limits)

v0.2.0 - 2024-10-14

Added

  • AMSDAL Glue integration (amsdal-glue)

v0.1.18 - 2024-10-01

Added

  • select_related method added to querysets (select-related)

v0.1.17 - 2024-09-12

Added

  • Added google style docstring (docstring)

v0.1.16 - 2024-07-16

Added

  • Support for ordering in fixtures (support-for-ordering-in-fixtures)

v0.1.15 - 2024-05-28

Fixed

  • Fix for API Schema creation (fix-for-api-schema-creation)

v0.1.14 - 2024-05-23

Added

  • date and datetime field types support (date-datetime-field-types)

v0.1.13 - 2024-05-09

Fixed

  • Fixed imports with aliases in AST builder (fixed-imports-with-aliases-in-ast-builder)

v0.1.12 - 2024-05-06

Added

  • Bulk operations support (bulk-operations-support)

v0.1.11 - 2024-04-24

Changed

  • Left to right union mode during models generation (left-to-right-union-mode-during-models-generation)

v0.1.10 - 2024-04-19

Added

  • Immutable connection support (immutable-connection-support)

v0.1.9 - 2024-04-11

Added

  • {field}_reference dynamic field to avoid reference loading (field-reference-dynamic-field-to-avoid-reference-loading)

Fixed

  • Queryset slicing (queryset-slicing)

v0.1.8 - 2024-04-03

Changed

  • Dissalow save of partial models (dissalow-save-of-partial-models)

v0.1.7 - 2024-04-02

Added

  • Support for partial models (support-for-partial-models)

v0.1.6 - 2024-04-02

Fixed

  • Typo in previous_version and next_version methods (typo-in-previous-version-and-next-version-methods)

v0.1.5 - 2024-04-01

Fixed

  • Fixed previous_version and next_version manager methods (fixed-previous-version-and-next-version-methods)

v0.1.4 - 2024-03-30

Fixed

  • Fix for serialization of recursive models (fix-for-serialization-of-recursive-models)

v0.1.3 - 2024-03-25

Added

  • Check unique fields on save (check-unique-fields-on-save)

v0.1.2 - 2024-03-21

Fixed

  • Fixed mypy build (fixed-mypy-build)

v0.1.1 - 2024-03-21

Changed

  • Change version pins to compatible releases (change-version-pins-to-compatible-releases)

v0.1.0 - 2024-03-20

Changed

  • Upgraded amsdal_utils and amsdal_data dependencies to 0.1.* (upgraded-amsdal-utils-and-amsdal-data-dependencies)