amsdal_models
v0.8.8 - 2026-06-15¶
Added¶
- Code generation now supports the
decimalcore type.CoreTypes.DECIMALmaps toDecimalinBASIC_TYPES_MAP, so generated models annotate decimal fields asDecimal. Schemaprecision/scaleare emitted as pydanticField(max_digits=..., decimal_places=...)constraints, and decimal default values are rendered asDecimal('<value>')rather than bare constants.
Fixed¶
- Unconstrained decimals (whose
precision/scaleareNone) no longer emitField(max_digits=None, decimal_places=None).build_assign_nodenow skips keyword constraints whose value isNone.
v0.8.7 - 2026-06-12¶
Fixed¶
- Historical
select_relatednow projects the FK child's_metadata. The child's_metadataJSON is already computed inside thesr_Nchild subquery, but_build_nested_only_for_historicalpreviously surfaced only the child's schema fields +range_key, so the decoded child carried no metadata and its_metadata.object_versionstayedLATEST. Combined with the select_related child now beingis_from_lakehouse=True, accessing metadata on (or comparing) a select_related child issued an extraget_metadata()DB query per object — an N+1 regression. The child_metadatacolumn 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 comparedget_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__andExternalModel.__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 sameobject_version(the previous sync__eq__behavior), available in both sync and async forms.
v0.8.6 - 2026-06-10¶
Fixed¶
select_relatedFK targets are now constructed with the loadedModelState(adding=False, inheriting the parent'sis_from_lakehouse) instead of defaulting toadding=True. Previously a select_related target arrived as a nested dict and was built without state, sochild.is_new_objectwasTrueand a cascadesave()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'));ReferenceandLegacyModeltargets are unaffected.
v0.8.5 - 2026-06-08¶
Fixed¶
Prefetch(lookup, to_attr=...)now rejects ato_attrthat collides with an existing relation accessor (forward/reverse foreign-key or many-to-many) on the parent model, raisingValueErrorduringapply_prefetches. Previously the collision validator only checked pydanticmodel_fields, so ato_attrmatching 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 withoutput_type=str, so glue applies thecast(... as TEXT)/ text-comparison rule on SQLite- and PostgreSQL-historical backends. Previously the leaf was emitted withoutput_type=None, leaving the comparison unwrapped and unable to match the stored value. The state backend is unaffected (it resolvesoutput_typetoNoneregardless).
v0.8.3 - 2026-06-04¶
Fixed¶
ModelClassLoader.load(unload_module=True)(used byClassSchemaLoader._load_classes()and other schema-introspection paths such asamsdal migrations new) no longer floods stderr with "Late registration of 'X' after freeze()" / "unresolvable deferred refs" warnings. The throwaway model reimport is now wrapped inwith allow_late_registration():so the rebuilt classes bypass the runtime late-registration handler — they are intentional, not user mistakes.StateMigrationExecutor._register_modelsand its async sibling now snapshot the internal_bufferinto 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
AmsdalManagerpeeking into an explicitModelLoadingStatenamespace onAmsdalModelsContext. The composition root now drives state transitions via the new public functionsfreeze(*, strict=False),unfreeze(), andset_strict(value)— all importable fromamsdal_models.classes.loading. Removes the cyclicamsdal_models→amsdaldependency that readAmsdalManager._Singleton__instances(mangled singleton attribute) andamsdal.configs.main.settings.AMSDAL_STRICT_BOOTSTRAP._on_model_createdreads state viacurrent_models_context()and no-ops when no context is active (standalone use or pre-bootstrap). Error message and log text now referencefreeze()instead ofAmsdalManager.setup().
v0.8.1 - 2026-06-01¶
Fixed¶
is_partial_namespacerejected pydantic's_ModelNamespaceDict(adictsubclass) when called from the cython-compiledclasses/utilswheel due to Cython 3.x strictannotation_typingenforcement on thedict[str, Any]parameter. The annotation is nowMapping[str, Any](fromcollections.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
ModelStatelifecycle namespace on everyModelinstance, exposed as_state(with_state.adding: booland_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_objectand_is_from_lakehouseprivate slots are removed; theis_new_objectandis_from_lakehousepublic 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 asForwardFKDescriptor; the underlying field is stored under_<fk>_refwithalias='<fk>'.Model.model_dump()andmodel_dump_refs()default toby_alias=True, so output uses the public name<fk>. Consumers that explicitly passby_alias=False, or call the underlying Pydantic dump directly, will see the internal key_<fk>_ref.populate_by_name=Trueis set onModelandLegacyModel, somodel_validate({'<fk>': ...})continues to work as before. - Reverse-FK accessors are now auto-registered on FK target classes. Given
class Book(Model): author: Author,Authorgains abook_setattribute that returns aRelatedSet[Book]scoped toauthor=<author_ref>. Class-level access (Author.book_set) returns aReverseFKDescriptorfor introspection; instance-level access on an unsaved parent returns aRelatedSetwith an empty cache (no DB hit). The reverse-name defaults to<reverser_lower>_set; passrelated_name='custom'onReferenceFieldto override orrelated_name='+'to disable. Two FKs that resolve to the same default reverse-name on a target raiseAmsdalReverseFKConflictErrorat class build time — add an explicitrelated_nameto one of them. The registry is exposed asAuthor.__reverse_foreign_keys__: dict[str, ReverseFKDescriptor]. For IDE annotation, the newReverseRelation['Book']type alias is available underamsdal_models.classes.relationships. - Class-level
<Source>.<m2m>_through(e.g.Post.tags_through) now resolves directly to the through-model class (returnstype[ThroughModel], not a wrapper). Use.objectson 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-modelQuerySet. On lakehouse it also injects_address__object_version=LATEST+_metadata__is_deleted=Falsedefaults 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-hoptags__category__parent__name='X'), reverse direction viarelated_name,exclude/isnull, compositional withQ. Each.filter()/.exclude()allocates its own through-table JOIN. Noselect_related/prefetch_relatedrequired at the filter site. - Two write-time guards landed on
RelatedSet: (1)add/remove/seton a custom-through M2M raiseAmsdalErrorwith the hintCannot {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-LATESTReference, or aModelwhose_metadata.is_latest is False, withValueError— rebuild a LATEST-pinned reference or re-fetch the model. RelatedSet.add()is now idempotent byobject_id— re-adding an already-linked target is a no-op (noAmsdalUniquenessError). Set semantics:add → remove → addcollapses 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). Onlyis_deletedis recognized in the per-hop___metadata__form;___address__class_version=Versions.ALLon a hop raisesValueError— 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 returnRelatedSet[T]instead ofQuerySet(lazy) /ManyList(eager).RelatedSetis alistsubclass 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 …, fullSELECT *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 byobject_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 withpydantic.ValidationError(the child's FK column rejectsNone) — reparent the child or delete it instead. Passing a reverse-FK kwarg to a model constructor (e.g.Author(book_set=[...])) now raisesAmsdalReverseFKConstructorErrorinstead of being silently ignored. TheManyListclass is removed — code that imported it or didisinstance(x, ManyList)must migrate toRelatedSet. TheClassLevelReverseManagerandClassLevelM2MManagerclasses are also removed —Author.book_set(class-level) returns the descriptor itself for introspection; useBook.objects.filter(...)for class-level queries.ClassLevelThroughAccessor(Post.tags_through) is preserved. Migration: replaceauthor.book_set.execute()→list(author.book_set); replaceawait author.book_set.aexecute()→[x async for x in author.book_set]; replaceAuthor.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 whenparent.save()/await parent.asave()flushes the deltas. Theaadd/aremove/aset/aclearmethods are removed — use the sync variants. Attribute access (parent.tags,author.book_set) is now always sync and returns aRelatedSetinstance directly — no coroutine wrapping in async mode (fixes the long-standing case whereawait parent.relationwas required for an unsaved parent despite no I/O being needed). Internally the cache state is split:_cacheis the snapshot loaded from DB (orNoneif not loaded);_pending_add/_pending_remove/_pending_clearcarry 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 (useacount/aexistsin async);await rs(orasync 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 byobject_id— two Python instances of the same DB row collapse to one through-row. Non-nullable reverse-FK detach (remove/clear/ shrinkingset) is buffered; atparent.save()the flush sets the child's FK toNoneand fails Pydantic validation withpydantic.ValidationError— reparent or hard-delete the child instead.Prefetchcontinues to populate cache as before — no user-visible change for the prefetch path. Migration: drop theaprefix andawaitfrom anyrs.aadd(...)/rs.aremove(...)/rs.aset(...)/rs.aclear(...)calls — they're synchronous now. If you had toawait parent.relationbecause of the descriptor-coroutine issue, you can drop theawait— just access the attribute directly.- M2M flush at
parent.save()/parent.asave()now correctly handles items inrs.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 withobject_version=LATEST. Internally:_M2MStrategy.db_add/db_remove/db_clear(and async siblings) now requireparent_ref: Referenceanditems: list[Reference]— all normalization is done byModel._normalize_m2m_items/_anormalize_m2m_itemsinside_process_m2m_fields. Strategy is now a pure data-path layer with no Model knowledge. Auto-load throughfor x in rs,len(rs),await rsfor M2M now returns target instances directly (via the unified accessor read path), instead of populating cache with raw through-rows. Note: directModel.objects.bulk_create([...])still does NOT flush m2m through-rows — m2m persistence happens only viaparent.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, soAuthor.objects.using('lakehouse').latest().execute()[0].bookswould return[]. The fix capturesprevious_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=...)(andadb_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 viabulk_create(..., using='lakehouse', force_insert=True). Default-DB through-rows are not touched (they were already correct). Edge cases: INSERT (no previous version) andpending_clearare skipped; UoW deletes are honored by passingcached._pending_removeasexclude_target_refs; cache-miss falls back toModel._build_m2m_strategy_from_meta(m2m_name)to materialize a strategy fromMANY_TO_MANY_FIELDSmetadata. - Internal refactor of QuerySet condition tracking: each
.filter()/.exclude()call is now stored as a separateQinQuerySetBase._filter_groups: list[Q]instead of being eagerly AND-combined into a single_conditionsslot.get_conditions()keeps its previous public contract by AND-combining the groups on access; a newget_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_relatedwork). No behavioural change for existing forward-FK queries. - Deferred forward-reference resolution in
meta/deferred_registry.pyno longer depends ontyping.ForwardRef._evaluate— a private CPython API whose signature drifts between minor versions (3.9 added positionalrecursive_guard, 3.10 made it keyword-only, 3.13 addedtype_paramsandformat, 3.14 may drop the private name entirely under PEP 649). The new_resolve_forward_refhelper uses a two-tier strategy: a fast path that resolves simple identifiers (e.g.ForwardRef('User')) via directmod_globalsdict lookup — noeval, nocompile, notypinginternals; and a slow path that falls back toeval(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 bypassesevalentirely for the 100% of real call sites where__forward_arg__is a simple identifier (confirmed: allForwardRef(...)instantiations in the codebase pass a single class name, sinceresolve_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 throughReferenceLoaderfor the dropped tail. Composition is unchanged:Prefetch('a__b', queryset=A.objects.filter(...))applies the queryset to hop 1 and prefetchesbon the collected targets;Prefetch('a__b', queryset=A.objects.prefetch_related('c'))prefetches bothbandcon hop-1 targets at the same level.to_attrattaches 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 requiresqueryset=— defaults totarget.objects.all(). Additionally, async forward-FK prefetch no longer silently nulls every target when the parent holds aReferencein_<fk>_ref(the post-.aexecute()materialization state) — the executor now reads the public<fk>_referenceaccessor 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 theis_new_objectflag was inferred from the presence of an object id, which mis-classified pre-set custom-PK instances and causedsave()to silently take the update path instead of creating the row. The flag is now driven by an explicit_state.addinglifecycle 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 outgoingglue.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 raisesPIIQueryErrorinstead 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 anyUniqueConstraint. RaisesPIIInvalidFieldUsageErrorat class build time — random-IV ciphertext makes both checks vacuous. - New
EncryptedStr(str)wrapper masks PII values inrepr()/str()/format()so plaintext does not leak into logs or exception messages. Raw ciphertext available via the.ciphertextproperty. - PII marker switched from
Field(json_schema_extra={'pii': True})to a sentinel-basedAnnotatedmetadata 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_onlydecorators 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_adeletelock leak: aRuntimeErrorbetweenacquireandreleaseleft the postgresFOR UPDATElock held until the surrounding transaction rolled back. Lock release now lives infinally. _check_uniqueissued an N+1 SELECT-by-unique-fields on everysave()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 intoAmsdalUniquenessErrorso the user-visible exception type is unchanged.- Failed FK resolution during
__getattribute__now logs aWARNING(amsdal_models.classes.modellogger) and returns the originalReference, 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 underlyingReferenceas identity tag, so reassignment (obj.fk = new_ref) auto-invalidates. The originalReferenceis preserved on the instance —obj.<fk>_referencestill 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-attributemodel_fieldslookup + annotation comparison._process_nested_objects(invoked on every save) skipssetattrwhen_process_nested_fieldreturns 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 nextobj.<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_adeleteno longerawait get_latest_schema_versionper 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); notyping.get_type_hintsinvocation, tolerates unresolved forward refs in unrelated fields.
Changed¶
- New
UniqueViolationErrorre-exported fromamsdal_models.classes.errors. Originates inamsdal-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_aupdatefailure handling consolidated into a_translate_bulk_failurehelper (UniqueViolationError → AmsdalUniquenessError, otherwiseBulkOperationError).
v0.7.7 - 2026-05-04¶
Fixed¶
filter(_address__object_id__in=[(s, c), ...])on composite-PK models silently dropped results (state) or raisedtext = ANY(int[])(historical/postgres). The local-PK address path (fk_chain == ()) now generates per-segment OR-of-AND-blocks instead of falling through to azip(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_relatedalias collision in sibling branches:select_related('a__child', 'b')produced the samesr_2alias fora.childandb, causing duplicate JOIN aliases in the generated SQL (silently wrong results on some dialects).- Lakehouse
_process_dataaccumulated 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_onlyignoredself._queryset._annotations: the same QuerySet emitted different SQL in sync vs async for queries combiningannotate()with noonly/select_related. - Async
_acreate_instanceignored_strict_class_version: sync raisedValidationErroron incompatible historical rows while async silently returnedLegacyModelwrappers. The same QuerySet produced different object types depending on which path the caller used. - Sync and async
count()raised bareExceptionon failure whilequery()raisedAmsdalQuerySetError. Callers usingexcept AmsdalQuerySetErrorcould not intercept count failures.
Changed¶
- New
WhereBuilder(query_builders/where/) — backend-agnostic translator fromQtrees toglue.Conditions. Backend specifics localised inBackendAdapterProtocol; 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.pymodule:allocate_aliases(build-side, fixes B4) andresolve_nested_alias(read-side path rewriting) live next to each other;BaseQueryBuilder._extract_select_relateddelegates to the helper. - New
query_builders/_dedupe.pywith thededupe_refsgenerator 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_cacheforHistoricalSchemaVersionManager.get_all_schema_propertieswalks once per build instead of per row × per FK. Reference.object_id_segments()andAddress.object_id_segments()/Address.normalize_object_id_to_scalar()hoisted toamsdal_utils(requiresamsdal_utils >= 0.6.2); the local helperwhere.coercers.object_id_as_segmentsremoved.entity_nameis now acached_property;schemas.object_schema.get_model_foreign_keysis@cache-decorated;parse_address_pathshort-circuits on the hot path.- Per-WhereBuilder cache for
(model, fk_field) → db_fields;select_relatedhead-index built once per build; FK fields stored asfrozensetfor membership checks. WhereBuilder.make_condition/build_conditionare the single sources of truth for theglue.Conditionshape;_latest_version_conditionsconsolidates thenext_version IS NULL OR next_version = ''filter;_legacy_or_native_instanceis the shared sync core for_create_instance/_acreate_instance;_extract_count_from_resultis the shared core forcount().- Stringly-typed
'object_id','_metadata','ref','object_version','__ref__object_id'literals replaced with the constants exposed inquery_builders/reserved.py.
v0.7.5 - 2026-04-10¶
Added¶
- Added audit data propagation (created_by, updated_by, action, changes) through
DeleteData.metadatafor proper audit logging on delete operations
v0.7.4 - 2026-03-27¶
Added¶
- Added
__ordering__model attribute for default query ordering
Fixed¶
- Fixed
transform_countto not includelimitandorder_byin count queries
v0.7.3 - 2026-03-20¶
Fixed¶
- Fixed
order_bywithselect_relatedfor State, Historical, and Async Historical query builders - Fixed
resolve_model_typeto handle Union types with multiple non-None arguments - Fixed
_storageattribute check on File-like objects to usegetattrinstead ofhasattr
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
PIIStrfield type andget_pii_fields()for PII (Personally Identifiable Information) field detection - Added
CryptoServiceprotocol and registry (register_crypto_service/get_crypto_service) for field-level encryption - Full roundtrip support for PII fields:
Model → ObjectSchema → ModelpreservesPIIStrtype annotations, includingPIIStr | 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_relatedfor historical query builders to filter by NULL or emptyNEXT_VERSION_FIELD, ensuring only the latest version of related objects is returned
v0.6.3 - 2026-02-13¶
Fixed¶
- Fixed
pyruff.fixrunning in isolated mode, ignoring projectpyproject.tomlconfig (e.g.force-single-line = truefor isort), which caused phantom migrations with merged imports
v0.6.2 - 2026-02-16¶
Changed¶
- Replaced
ruff-apiwithpyrufffor code formatting and linting
v0.6.1 - 2026-02-15¶
Performance¶
- Replaced
blackformatter withruff-apifor code generation (167x faster, eliminates blib2to3 memory leak) - Added
ClassSourceBuilder._build_class_sourcecache clearing inClassManager.teardown()
Fixed¶
- Fixed temp file leak in
_format_code()— files in/tmpwere not cleaned up afterruff 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_schemaoutput"
v0.5.17 - 2025-09-24¶
Changed¶
- Remove timestamp fields from
model_to_object_schemaoutput
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_dumpadded 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
dateanddatetimefield 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}_referencedynamic 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_utilsandamsdal_datadependencies to0.1.*(upgraded-amsdal-utils-and-amsdal-data-dependencies)