statezero 0.1.0b62__py3-none-any.whl → 0.1.0b64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- statezero/adaptors/django/middleware.py +4 -3
- statezero/adaptors/django/orm.py +7 -3
- statezero/core/classes.py +4 -4
- statezero/core/context_storage.py +0 -16
- statezero/core/event_bus.py +10 -2
- statezero/core/query_cache.py +6 -0
- {statezero-0.1.0b62.dist-info → statezero-0.1.0b64.dist-info}/METADATA +1 -1
- {statezero-0.1.0b62.dist-info → statezero-0.1.0b64.dist-info}/RECORD +10 -10
- {statezero-0.1.0b62.dist-info → statezero-0.1.0b64.dist-info}/WHEEL +0 -0
- {statezero-0.1.0b62.dist-info → statezero-0.1.0b64.dist-info}/top_level.txt +0 -0
|
@@ -7,9 +7,10 @@ class OperationIDMiddleware(MiddlewareMixin):
|
|
|
7
7
|
def process_request(self, request):
|
|
8
8
|
# The header in Django is available via request.META (HTTP headers are prefixed with HTTP_)
|
|
9
9
|
op_id = request.META.get("HTTP_X_OPERATION_ID")
|
|
10
|
-
|
|
10
|
+
# Always reset to avoid leaking IDs across requests in reused threads.
|
|
11
|
+
current_operation_id.set(op_id if op_id else None)
|
|
11
12
|
|
|
12
13
|
# Also read canonical_id from headers if provided by client
|
|
13
14
|
canonical_id = request.META.get("HTTP_X_CANONICAL_ID")
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# Always reset; only set when explicitly provided.
|
|
16
|
+
current_canonical_id.set(canonical_id if canonical_id else None)
|
statezero/adaptors/django/orm.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
|
|
|
3
3
|
|
|
4
4
|
import networkx as nx
|
|
5
5
|
from django.apps import apps
|
|
6
|
-
from django.db import models
|
|
6
|
+
from django.db import models, transaction
|
|
7
7
|
from django.db.models import Avg, Count, Max, Min, Q, Sum, QuerySet
|
|
8
8
|
from django.db.models.signals import post_delete, post_save, pre_delete, pre_save
|
|
9
9
|
from django.dispatch import receiver
|
|
@@ -977,7 +977,8 @@ class DjangoORMAdapter(AbstractORMProvider):
|
|
|
977
977
|
def post_save_receiver(sender, instance, created, **kwargs):
|
|
978
978
|
action = ActionType.CREATE if created else ActionType.UPDATE
|
|
979
979
|
try:
|
|
980
|
-
|
|
980
|
+
# Emit after commit so clients don't re-fetch stale rows.
|
|
981
|
+
transaction.on_commit(lambda: event_bus.emit_event(action, instance))
|
|
981
982
|
except Exception as e:
|
|
982
983
|
logger.exception(
|
|
983
984
|
"Error emitting event %s for instance %s: %s", action, instance, e
|
|
@@ -994,7 +995,10 @@ class DjangoORMAdapter(AbstractORMProvider):
|
|
|
994
995
|
|
|
995
996
|
def post_delete_receiver(sender, instance, **kwargs):
|
|
996
997
|
try:
|
|
997
|
-
|
|
998
|
+
# Emit after commit so clients don't re-fetch stale rows.
|
|
999
|
+
transaction.on_commit(
|
|
1000
|
+
lambda: event_bus.emit_event(ActionType.DELETE, instance)
|
|
1001
|
+
)
|
|
998
1002
|
except Exception as e:
|
|
999
1003
|
logger.exception(
|
|
1000
1004
|
"Error emitting DELETE event for instance %s: %s", instance, e
|
statezero/core/classes.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Literal, Optional, Set, Type, Union, Annotat
|
|
|
4
4
|
|
|
5
5
|
import jsonschema
|
|
6
6
|
from fastapi.encoders import jsonable_encoder
|
|
7
|
-
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
8
8
|
from pydantic.dataclasses import dataclass
|
|
9
9
|
|
|
10
10
|
from statezero.core.types import ORMField
|
|
@@ -83,7 +83,7 @@ class FieldFormat(str, Enum):
|
|
|
83
83
|
MONEY = "money"
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
@dataclass
|
|
86
|
+
@dataclass(config=ConfigDict(arbitrary_types_allowed=True))
|
|
87
87
|
class AdditionalField:
|
|
88
88
|
"""
|
|
89
89
|
Represents configuration for an additional computed field in the schema.
|
|
@@ -95,7 +95,7 @@ class AdditionalField:
|
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
97
|
name: str # The property/method name to pull from
|
|
98
|
-
field:
|
|
98
|
+
field: ORMField # The instantiated serializer field (e.g. CharField(max_length=255)) #type:ignore
|
|
99
99
|
title: Optional[str] # Optional display name override
|
|
100
100
|
|
|
101
101
|
class SchemaFieldMetadata(BaseModel):
|
|
@@ -259,7 +259,7 @@ class Display:
|
|
|
259
259
|
label: Label to show above the display
|
|
260
260
|
extra: Additional custom metadata passed to the component
|
|
261
261
|
"""
|
|
262
|
-
context_path: str
|
|
262
|
+
context_path: Optional[str] = None
|
|
263
263
|
display_component: str = "text"
|
|
264
264
|
label: Optional[str] = None
|
|
265
265
|
extra: Optional[Dict[str, Any]] = None
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import contextvars
|
|
2
|
-
from uuid import uuid4
|
|
3
2
|
from typing import Any, Optional
|
|
4
3
|
|
|
5
4
|
# This context variable holds the current operation id (from client headers).
|
|
@@ -7,18 +6,3 @@ current_operation_id = contextvars.ContextVar("current_operation_id", default=No
|
|
|
7
6
|
|
|
8
7
|
# This context variable holds the canonical id (server-generated for cache sharing).
|
|
9
8
|
current_canonical_id = contextvars.ContextVar("current_canonical_id", default=None)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_or_create_canonical_id():
|
|
13
|
-
"""
|
|
14
|
-
Get the current canonical_id, or generate a new one if it doesn't exist.
|
|
15
|
-
Canonical IDs are used for cross-client cache sharing.
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
str: The canonical ID for this request context
|
|
19
|
-
"""
|
|
20
|
-
canonical_id = current_canonical_id.get()
|
|
21
|
-
if canonical_id is None:
|
|
22
|
-
canonical_id = str(uuid4())
|
|
23
|
-
current_canonical_id.set(canonical_id)
|
|
24
|
-
return canonical_id
|
statezero/core/event_bus.py
CHANGED
|
@@ -2,6 +2,8 @@ from statezero.core.context_storage import current_operation_id, current_canonic
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Any, List, Type, Union
|
|
4
4
|
from fastapi.encoders import jsonable_encoder
|
|
5
|
+
from django.utils import timezone
|
|
6
|
+
from uuid import uuid4
|
|
5
7
|
|
|
6
8
|
from statezero.core.interfaces import AbstractEventEmitter, AbstractORMProvider
|
|
7
9
|
from statezero.core.types import ActionType, ORMModel, ORMQuerySet
|
|
@@ -72,11 +74,14 @@ class EventBus:
|
|
|
72
74
|
pk_field_name = instance._meta.pk.name
|
|
73
75
|
pk_value = instance.pk
|
|
74
76
|
|
|
77
|
+
now = timezone.now()
|
|
78
|
+
event_canonical_id = str(uuid4())
|
|
75
79
|
data = {
|
|
76
80
|
"event": action_type.value,
|
|
77
81
|
"model": model_name,
|
|
78
82
|
"operation_id": current_operation_id.get(),
|
|
79
|
-
"canonical_id":
|
|
83
|
+
"canonical_id": event_canonical_id,
|
|
84
|
+
"server_ts_ms": int(now.timestamp() * 1000),
|
|
80
85
|
"instances": [pk_value],
|
|
81
86
|
"pk_field_name": pk_field_name,
|
|
82
87
|
}
|
|
@@ -153,11 +158,14 @@ class EventBus:
|
|
|
153
158
|
pk_field_name = first_instance._meta.pk.name
|
|
154
159
|
pks = [instance.pk for instance in instances]
|
|
155
160
|
|
|
161
|
+
now = timezone.now()
|
|
162
|
+
event_canonical_id = str(uuid4())
|
|
156
163
|
data = {
|
|
157
164
|
"event": action_type.value,
|
|
158
165
|
"model": model_name,
|
|
159
166
|
"operation_id": current_operation_id.get(),
|
|
160
|
-
"canonical_id":
|
|
167
|
+
"canonical_id": event_canonical_id,
|
|
168
|
+
"server_ts_ms": int(now.timestamp() * 1000),
|
|
161
169
|
"instances": pks,
|
|
162
170
|
"pk_field_name": pk_field_name,
|
|
163
171
|
}
|
statezero/core/query_cache.py
CHANGED
|
@@ -130,6 +130,12 @@ def get_cached_query_result(queryset, operation_context: Optional[str] = None) -
|
|
|
130
130
|
# Someone else is processing, wait for their result
|
|
131
131
|
logger.debug(f"Query being processed by another request, waiting... | txn {txn_id[:8]}...")
|
|
132
132
|
|
|
133
|
+
# TODO: FIX DESIGN (needs to be done, I didn't have time yet): if the leader
|
|
134
|
+
# query exceeds the wait timeout, followers will stop waiting and execute the
|
|
135
|
+
# same query themselves (automatic fan-out). Replace with hard single-flight
|
|
136
|
+
# (no fan-out) and propagate the leader's error/timeout to all followers
|
|
137
|
+
# (cache error responses).
|
|
138
|
+
|
|
133
139
|
# Calculate wait timeout based on Django's query timeout setting
|
|
134
140
|
# Wait for query_timeout + 1 second to allow the first request to complete
|
|
135
141
|
from django.conf import settings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b64
|
|
4
4
|
Summary: Connect your Python backend to a modern JavaScript SPA frontend with 90% less complexity.
|
|
5
5
|
Author-email: Robert <robert.herring@statezero.dev>
|
|
6
6
|
Project-URL: homepage, https://www.statezero.dev
|
|
@@ -11,8 +11,8 @@ statezero/adaptors/django/event_emitters.py,sha256=RXwTGNEP7CMB_F4AbyksjGxB5yZyn
|
|
|
11
11
|
statezero/adaptors/django/exception_handler.py,sha256=fjNYT3XuhqnToP38YtYix6kpDuXAQ8Da_vGoSdjeD28,3863
|
|
12
12
|
statezero/adaptors/django/f_handler.py,sha256=r-4qwpWZi2sDgj0REpdGT1owemsr5iMJnDGzAxWJvZI,11994
|
|
13
13
|
statezero/adaptors/django/helpers.py,sha256=7bwsQtr_zoO1UuJpyQ8dLbOHkR25Jh5TA7dekYKQOVo,5822
|
|
14
|
-
statezero/adaptors/django/middleware.py,sha256=
|
|
15
|
-
statezero/adaptors/django/orm.py,sha256
|
|
14
|
+
statezero/adaptors/django/middleware.py,sha256=2utpDUVQmAk-KBEP9Zt638IO2H6QN0U7sQ-n9LwP1VU,786
|
|
15
|
+
statezero/adaptors/django/orm.py,sha256=7BKVjCHkHLUHprLF-RN7Hlre2sTDxepqc8rPd_g6tgM,44381
|
|
16
16
|
statezero/adaptors/django/permissions.py,sha256=bwiTRYn0EWcvdd2htw_Gk-_gf-NUGsdrfhWsEg5BVT8,11163
|
|
17
17
|
statezero/adaptors/django/query_optimizer.py,sha256=k9_trhL1tYtUKVUDXsqQXYn88k3gVmLkqqrtw_QiPxo,40653
|
|
18
18
|
statezero/adaptors/django/schemas.py,sha256=SBYPKre7o6RANCXhzDgyZOvhChIO32W609ATVm0NzxM,16625
|
|
@@ -39,19 +39,19 @@ statezero/core/__init__.py,sha256=IaNGa9WGZ-2OopG8UTqmijDZcwpC8tIGrfkl_cvCjBk,10
|
|
|
39
39
|
statezero/core/actions.py,sha256=MO1NN5Vtc-nu1e43x32hHrEdPRXWTYRvbC5sKPZczXQ,5385
|
|
40
40
|
statezero/core/ast_parser.py,sha256=oKDchCrv2FDN2UsnC3mfI-6D3HiqXtlID5JLock6Aow,45382
|
|
41
41
|
statezero/core/ast_validator.py,sha256=130IMTemJnIWd3PQHTLZYLfMFBLMmkTm37ckwHpt-QY,12214
|
|
42
|
-
statezero/core/classes.py,sha256=
|
|
42
|
+
statezero/core/classes.py,sha256=UZugF4ZAqewyOtr_WMu0N9pa0U6bwTfEwYEML8Fq6qs,13272
|
|
43
43
|
statezero/core/config.py,sha256=LdFlGutkJF-rsIViJwbRHVOFKWzt-33Q0Inhcba8PnY,14875
|
|
44
|
-
statezero/core/context_storage.py,sha256=
|
|
45
|
-
statezero/core/event_bus.py,sha256=
|
|
44
|
+
statezero/core/context_storage.py,sha256=19yPpy_m0HciYfWUkYUkXJAONPX01iGvQfX8E3IAAMQ,385
|
|
45
|
+
statezero/core/event_bus.py,sha256=8BCwM66xbVmXDmgbrO2uAodVxYbWgXwS0xt4qVhHXNE,8624
|
|
46
46
|
statezero/core/event_emitters.py,sha256=nGvfQyubJKqW0iLfJHRfn0Me3TszNj9B7PueKEdqM5Q,1986
|
|
47
47
|
statezero/core/exceptions.py,sha256=sVl4brAdoK3Y09C_DDa1iNw0eR_oCVwN68j5SIZ1XD4,3224
|
|
48
48
|
statezero/core/hook_checks.py,sha256=jeD1mjqPnE-sf5lg2eH9L1iY0leQEpGME2Qo0_6IlH4,3340
|
|
49
49
|
statezero/core/interfaces.py,sha256=0nTZGhzznzSwSgCVXEYYorj-i3nW-u7FFsoN_d8rSw4,22132
|
|
50
50
|
statezero/core/process_request.py,sha256=s1yys2ms856KJxV-cIfnVIdMkrOW9nLfzyLor64ogME,13334
|
|
51
|
-
statezero/core/query_cache.py,sha256=
|
|
51
|
+
statezero/core/query_cache.py,sha256=haEBdd4nCm8h8ugj8KmCHUbyiHApU4rv-9PncKJJCys,9528
|
|
52
52
|
statezero/core/telemetry.py,sha256=EV2yLV6WAS-MTYCQSRQadiMgOD_ViJ_qUspgvbD0GqA,7757
|
|
53
53
|
statezero/core/types.py,sha256=An57YP1sdd7u6eppXeKMoSudEn_6-Pb6UoC3IdR5E8w,916
|
|
54
|
-
statezero-0.1.
|
|
55
|
-
statezero-0.1.
|
|
56
|
-
statezero-0.1.
|
|
57
|
-
statezero-0.1.
|
|
54
|
+
statezero-0.1.0b64.dist-info/METADATA,sha256=w9RB4CSkF-uQ5WZSRrYb2D_kHFwtXu-V216Pe229kAM,9872
|
|
55
|
+
statezero-0.1.0b64.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
56
|
+
statezero-0.1.0b64.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
|
|
57
|
+
statezero-0.1.0b64.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|