django-logic 0.11.0
Changed (breaking)
-
django-redisis no longer a core dependency — it moves to the[redis]
extra, which stops being an empty alias (#173). The engine has never imported
django_redis; the state lock goes through Django's cache API
(State.lock→cache.add), so what it requires is a cross-process cache
backend, and Django has shipped
django.core.cache.backends.redis.RedisCachesince 4.0 (our floor is 4.2).Migration: if your settings name
django_redis.cache.RedisCache, install
it explicitly —pip install django-logic[redis]. The failure mode if you
miss it is not at boot:django.setup()succeeds and
InvalidCacheBackendErroris raised at the first cache access. The
celery-mode locmem/dummy guard is backend-agnostic and unchanged.
Changed
-
in_progress_stateno longer has to be unique within a process (#175).
Two transitions in one process tree sharing anin_progress_stateused to
raiseImproperlyConfiguredat class-creation time. The rule now enforced is
the narrower one the engine actually needs: transitions sharing an
in_progress_stateon a given (model, state_field) must recover a
record-less stranded instance identically — samefailed_state, same
failure hooks — and must belong to the same bound process. Where they
agree, sharing is free andrecover_stranded_statespicks any claimant;
where they disagree,django_logic.E001failsmanage.py checkand the
sweep skips the state (#143).The old justification — "the in-progress state alone identifies the
transition that's mid-flight" — stopped being true whenowning_process_class
was added toTransitionMessage(migration 0007): phase-2 restore resolves
(owning process class, action_name)off the row, guarded by
_validate_unique_background_action_names, and never searches by state.This unblocks several actions on one model that all mean "busy" to a client,
sharing one in-progress value and onefailed_state.Three things to know:
- Sharing across two different bound processes stays ambiguous. The
sweep's in-flight check is scoped byprocess_name, so a sibling process's
openTransitionMessageis invisible to it — an instance legitimately
mid-flight there would look record-less and be force-failed into
failed_state. The recovery signature includes the bound process name so
this topology still reportsE001and is still skipped. - Failure hooks are compared by object identity, not equality: claimants
must reference the same callables. Hoist a sharedpartial/lambdato a
module-level name, orE001will report transitions that behave identically
as recovering differently. - A divergent shared state within one tree was previously impossible — it
raised at import — and is now constructible, caught byE001at
manage.py checktime. Celery workers do not run system checks, so make
sure your deploy pipeline does.
- Sharing across two different bound processes stays ambiguous. The
Fixed
-
Late migration note for the 0.10.0 removal of
django_logic.conditions.
0.10.0 removedall_related_in/any_related_in(#168) as "public API with
no callers in any tree". That audit scanned only one consumer; the
django-logic-testvalidation rig imported both, and would have failed at
import on upgrade. Nothing changes in 0.11.0 — the module is still gone — but
the migration the changelog never gave is: copy the two factories into your
own project. They are queryset arithmetic with no engine coupling:def all_related_in(relation, field, states): wanted = set(states) def condition(instance, **kwargs) -> bool: manager = getattr(instance, relation) total = manager.count() if total == 0: return False return manager.filter(**{f'{field}__in': wanted}).count() == total return condition def any_related_in(relation, field, states): wanted = set(states) def condition(instance, **kwargs) -> bool: return getattr(instance, relation).filter( **{f'{field}__in': wanted}).exists() return condition