statezero 0.1.0b9__tar.gz → 0.1.0b11__tar.gz
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.
Potentially problematic release.
This version of statezero might be problematic. Click here for more details.
- {statezero-0.1.0b9 → statezero-0.1.0b11}/PKG-INFO +1 -13
- {statezero-0.1.0b9 → statezero-0.1.0b11}/README.md +1 -13
- {statezero-0.1.0b9 → statezero-0.1.0b11}/pyproject.toml +1 -1
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/actions.py +57 -13
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/exception_handler.py +1 -1
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/ast_parser.py +26 -26
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/interfaces.py +3 -6
- statezero-0.1.0b11/statezero/core/types.py +29 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero.egg-info/PKG-INFO +1 -13
- statezero-0.1.0b9/statezero/core/types.py +0 -63
- {statezero-0.1.0b9 → statezero-0.1.0b11}/license.md +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/requirements.txt +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/setup.cfg +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/apps.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/config.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/context_manager.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/event_emitters.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/extensions/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/extensions/custom_field_serializers/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/extensions/custom_field_serializers/file_fields.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/extensions/custom_field_serializers/money_field.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/f_handler.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/helpers.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/middleware.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/migrations/0001_initial.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/migrations/0002_delete_modelviewsubscription.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/migrations/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/orm.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/permissions.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/query_optimizer.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/schemas.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/search_providers/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/search_providers/basic_search.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/search_providers/postgres_search.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/serializers.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/urls.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/views.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/__init__.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/actions.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/ast_validator.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/classes.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/config.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/context_storage.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/event_bus.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/event_emitters.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/exceptions.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/hook_checks.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/core/process_request.py +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero.egg-info/SOURCES.txt +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero.egg-info/dependency_links.txt +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero.egg-info/requires.txt +0 -0
- {statezero-0.1.0b9 → statezero-0.1.0b11}/statezero.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b11
|
|
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
|
|
@@ -240,15 +240,3 @@ npx statezero sync
|
|
|
240
240
|
Check out the docs at [Statezero Docs](https://statezero.dev)
|
|
241
241
|
|
|
242
242
|
Run `pip install statezero` and `npm i @statezero/core` to begin.
|
|
243
|
-
|
|
244
|
-
## Pricing
|
|
245
|
-
|
|
246
|
-
StateZero uses a no-rugpull license model:
|
|
247
|
-
|
|
248
|
-
- **$0/month** for companies with revenue up to $3M
|
|
249
|
-
- **$75/month** for companies with revenue up to $7.5M
|
|
250
|
-
- **$200/month** for companies with revenue up to $20M
|
|
251
|
-
- **$500/month** for companies with revenue up to $100M
|
|
252
|
-
- **$1,000/month** for companies with revenue above $100M
|
|
253
|
-
|
|
254
|
-
Lock in your rate forever by signing up early. We can't change your fee or cancel your license.
|
|
@@ -209,16 +209,4 @@ npx statezero sync
|
|
|
209
209
|
|
|
210
210
|
Check out the docs at [Statezero Docs](https://statezero.dev)
|
|
211
211
|
|
|
212
|
-
Run `pip install statezero` and `npm i @statezero/core` to begin.
|
|
213
|
-
|
|
214
|
-
## Pricing
|
|
215
|
-
|
|
216
|
-
StateZero uses a no-rugpull license model:
|
|
217
|
-
|
|
218
|
-
- **$0/month** for companies with revenue up to $3M
|
|
219
|
-
- **$75/month** for companies with revenue up to $7.5M
|
|
220
|
-
- **$200/month** for companies with revenue up to $20M
|
|
221
|
-
- **$500/month** for companies with revenue up to $100M
|
|
222
|
-
- **$1,000/month** for companies with revenue above $100M
|
|
223
|
-
|
|
224
|
-
Lock in your rate forever by signing up early. We can't change your fee or cancel your license.
|
|
212
|
+
Run `pip install statezero` and `npm i @statezero/core` to begin.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "statezero"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0b11"
|
|
8
8
|
description = "Connect your Python backend to a modern JavaScript SPA frontend with 90% less complexity."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from django.apps import apps
|
|
3
3
|
from rest_framework.response import Response
|
|
4
|
-
from rest_framework import fields
|
|
4
|
+
from rest_framework import fields, serializers
|
|
5
|
+
from django.db import models
|
|
5
6
|
from statezero.core.actions import action_registry
|
|
6
7
|
|
|
7
|
-
|
|
8
8
|
class DjangoActionSchemaGenerator:
|
|
9
9
|
"""Django-specific action schema generator that matches StateZero model schema format"""
|
|
10
10
|
|
|
@@ -41,6 +41,13 @@ class DjangoActionSchemaGenerator:
|
|
|
41
41
|
app_name = found_app.label
|
|
42
42
|
docstring = action_config.get("docstring")
|
|
43
43
|
|
|
44
|
+
input_properties, input_relationships = DjangoActionSchemaGenerator._get_serializer_schema(
|
|
45
|
+
action_config["serializer"]
|
|
46
|
+
)
|
|
47
|
+
response_properties, response_relationships = DjangoActionSchemaGenerator._get_serializer_schema(
|
|
48
|
+
action_config["response_serializer"]
|
|
49
|
+
)
|
|
50
|
+
|
|
44
51
|
schema_info = {
|
|
45
52
|
"action_name": action_name,
|
|
46
53
|
"app": app_name,
|
|
@@ -49,12 +56,9 @@ class DjangoActionSchemaGenerator:
|
|
|
49
56
|
"class_name": "".join(
|
|
50
57
|
word.capitalize() for word in action_name.split("_")
|
|
51
58
|
),
|
|
52
|
-
"input_properties":
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"response_properties": DjangoActionSchemaGenerator._get_serializer_schema(
|
|
56
|
-
action_config["response_serializer"]
|
|
57
|
-
),
|
|
59
|
+
"input_properties": input_properties,
|
|
60
|
+
"response_properties": response_properties,
|
|
61
|
+
"relationships": {**input_relationships, **response_relationships},
|
|
58
62
|
"permissions": [
|
|
59
63
|
perm.__name__ for perm in action_config.get("permissions", [])
|
|
60
64
|
],
|
|
@@ -66,11 +70,16 @@ class DjangoActionSchemaGenerator:
|
|
|
66
70
|
@staticmethod
|
|
67
71
|
def _get_serializer_schema(serializer_class):
|
|
68
72
|
if not serializer_class:
|
|
69
|
-
return {}
|
|
73
|
+
return {}, {}
|
|
70
74
|
try:
|
|
71
75
|
serializer_instance = serializer_class()
|
|
72
76
|
properties = {}
|
|
77
|
+
relationships = {}
|
|
73
78
|
for field_name, field in serializer_instance.fields.items():
|
|
79
|
+
relation_info = DjangoActionSchemaGenerator._get_relation_info(field)
|
|
80
|
+
if relation_info:
|
|
81
|
+
relationships[field_name] = relation_info
|
|
82
|
+
|
|
74
83
|
field_info = {
|
|
75
84
|
"type": DjangoActionSchemaGenerator._get_field_type(field),
|
|
76
85
|
"title": getattr(field, "label")
|
|
@@ -95,12 +104,19 @@ class DjangoActionSchemaGenerator:
|
|
|
95
104
|
if hasattr(field, "min_length") and field.min_length is not None:
|
|
96
105
|
field_info["min_length"] = field.min_length
|
|
97
106
|
properties[field_name] = field_info
|
|
98
|
-
return properties
|
|
107
|
+
return properties, relationships
|
|
99
108
|
except Exception as e:
|
|
100
|
-
|
|
109
|
+
print(f"Could not inspect serializer: {str(e)}")
|
|
110
|
+
raise e
|
|
101
111
|
|
|
102
112
|
@staticmethod
|
|
103
113
|
def _get_field_type(field):
|
|
114
|
+
if isinstance(field, serializers.PrimaryKeyRelatedField):
|
|
115
|
+
pk_field = field.queryset.model._meta.pk
|
|
116
|
+
if isinstance(pk_field, (models.UUIDField, models.CharField)):
|
|
117
|
+
return "string"
|
|
118
|
+
return "integer"
|
|
119
|
+
|
|
104
120
|
type_mapping = {
|
|
105
121
|
fields.BooleanField: "boolean",
|
|
106
122
|
fields.CharField: "string",
|
|
@@ -116,6 +132,7 @@ class DjangoActionSchemaGenerator:
|
|
|
116
132
|
fields.JSONField: "object",
|
|
117
133
|
fields.DictField: "object",
|
|
118
134
|
fields.ListField: "array",
|
|
135
|
+
serializers.ManyRelatedField: "array",
|
|
119
136
|
}
|
|
120
137
|
return type_mapping.get(type(field), "string")
|
|
121
138
|
|
|
@@ -128,6 +145,8 @@ class DjangoActionSchemaGenerator:
|
|
|
128
145
|
fields.DateField: "date",
|
|
129
146
|
fields.DateTimeField: "date-time",
|
|
130
147
|
fields.TimeField: "time",
|
|
148
|
+
serializers.ManyRelatedField: "many-to-many",
|
|
149
|
+
serializers.PrimaryKeyRelatedField: "foreign-key",
|
|
131
150
|
}
|
|
132
151
|
return format_mapping.get(type(field))
|
|
133
152
|
|
|
@@ -138,12 +157,13 @@ class DjangoActionSchemaGenerator:
|
|
|
138
157
|
|
|
139
158
|
# Handle dict format: {'low': 'Low', 'high': 'High'}
|
|
140
159
|
if isinstance(choices, dict):
|
|
141
|
-
return
|
|
160
|
+
return choices
|
|
142
161
|
|
|
143
162
|
# Handle list/tuple format: [('low', 'Low'), ('high', 'High')]
|
|
144
163
|
elif isinstance(choices, (list, tuple)):
|
|
145
164
|
try:
|
|
146
|
-
|
|
165
|
+
# Return dict with value->label mapping (same as model)
|
|
166
|
+
return {str(choice[0]): choice[1] for choice in choices}
|
|
147
167
|
except (IndexError, TypeError) as e:
|
|
148
168
|
raise ValueError(
|
|
149
169
|
f"Invalid choice format for field '{field}'. Expected list of tuples "
|
|
@@ -169,3 +189,27 @@ class DjangoActionSchemaGenerator:
|
|
|
169
189
|
return None
|
|
170
190
|
return default
|
|
171
191
|
return None
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _get_relation_info(field):
|
|
195
|
+
relation_type = DjangoActionSchemaGenerator._get_field_format(field)
|
|
196
|
+
if not relation_type in ["foreign-key", "one-to-one", "many-to-many"]:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
if isinstance(field, serializers.PrimaryKeyRelatedField):
|
|
200
|
+
model = field.queryset.model
|
|
201
|
+
return {
|
|
202
|
+
"type": relation_type,
|
|
203
|
+
"model": f"{model._meta.app_label}.{model._meta.model_name}",
|
|
204
|
+
"class_name": model.__name__,
|
|
205
|
+
"primary_key_field": model._meta.pk.name,
|
|
206
|
+
}
|
|
207
|
+
if isinstance(field, serializers.ManyRelatedField):
|
|
208
|
+
model = field.child_relation.queryset.model
|
|
209
|
+
return {
|
|
210
|
+
"type": relation_type,
|
|
211
|
+
"model": f"{model._meta.app_label}.{model._meta.model_name}",
|
|
212
|
+
"class_name": model.__name__,
|
|
213
|
+
"primary_key_field": model._meta.pk.name,
|
|
214
|
+
}
|
|
215
|
+
return None
|
|
@@ -62,7 +62,7 @@ def map_exception(exc):
|
|
|
62
62
|
def explicit_exception_handler(exc):
|
|
63
63
|
"""
|
|
64
64
|
Extended explicit exception handler that builds a structured JSON response.
|
|
65
|
-
It maps known Django/DRF exceptions to
|
|
65
|
+
It maps known Django/DRF exceptions to StateZero's standard errors and
|
|
66
66
|
uses jsonable_encoder to ensure the output is JSON serializable.
|
|
67
67
|
"""
|
|
68
68
|
traceback.print_exc()
|
|
@@ -416,7 +416,7 @@ class ASTParser:
|
|
|
416
416
|
return handler(ast)
|
|
417
417
|
|
|
418
418
|
def _apply_related(self, ast: Dict[str, Any]) -> None:
|
|
419
|
-
"""
|
|
419
|
+
""" Apply select_related and prefetch_related, updating current queryset."""
|
|
420
420
|
if "selectRelated" in ast and isinstance(ast["selectRelated"], list):
|
|
421
421
|
self.current_queryset = self.engine.select_related(
|
|
422
422
|
self.current_queryset, ast["selectRelated"]
|
|
@@ -427,28 +427,28 @@ class ASTParser:
|
|
|
427
427
|
)
|
|
428
428
|
|
|
429
429
|
def _apply_filter(self, ast: Dict[str, Any]) -> None:
|
|
430
|
-
"""
|
|
430
|
+
""" Apply filter from AST to the queryset, updating current queryset."""
|
|
431
431
|
if "filter" in ast and ast["filter"]:
|
|
432
432
|
self.current_queryset = self.engine.filter_node(
|
|
433
433
|
self.current_queryset, ast["filter"]
|
|
434
434
|
)
|
|
435
435
|
|
|
436
436
|
def _apply_exclude(self, ast: Dict[str, Any]) -> None:
|
|
437
|
-
"""
|
|
437
|
+
""" Apply exclude from AST to the queryset, updating current queryset."""
|
|
438
438
|
if "exclude" in ast and ast["exclude"]:
|
|
439
439
|
self.current_queryset = self.engine.exclude_node(
|
|
440
440
|
self.current_queryset, ast["exclude"]
|
|
441
441
|
)
|
|
442
442
|
|
|
443
443
|
def _apply_ordering(self, ast: Dict[str, Any]) -> None:
|
|
444
|
-
"""
|
|
444
|
+
""" Apply ordering, updating current queryset."""
|
|
445
445
|
if "orderBy" in ast:
|
|
446
446
|
self.current_queryset = self.engine.order_by(
|
|
447
447
|
self.current_queryset, ast["orderBy"]
|
|
448
448
|
)
|
|
449
449
|
|
|
450
450
|
def _apply_field_selection(self, ast: Dict[str, Any]) -> None:
|
|
451
|
-
"""
|
|
451
|
+
""" Apply field selection, updating current queryset."""
|
|
452
452
|
if "fields" in ast and isinstance(ast["fields"], list):
|
|
453
453
|
self.current_queryset = self.engine.select_fields(
|
|
454
454
|
self.current_queryset, ast["fields"]
|
|
@@ -456,7 +456,7 @@ class ASTParser:
|
|
|
456
456
|
|
|
457
457
|
def _apply_search(self, ast: Dict[str, Any]) -> None:
|
|
458
458
|
"""
|
|
459
|
-
|
|
459
|
+
If search properties are present at the top level of the AST,
|
|
460
460
|
apply the search using the adapter's search_node() method.
|
|
461
461
|
|
|
462
462
|
Expects the AST to have a top-level "search" key containing:
|
|
@@ -489,7 +489,7 @@ class ASTParser:
|
|
|
489
489
|
else:
|
|
490
490
|
final_search_fields = config_search_fields
|
|
491
491
|
|
|
492
|
-
#
|
|
492
|
+
# Delegate to the ORM adapter's search_node() method with queryset.
|
|
493
493
|
self.current_queryset = self.engine.search_node(
|
|
494
494
|
self.current_queryset, search_query, final_search_fields
|
|
495
495
|
)
|
|
@@ -497,7 +497,7 @@ class ASTParser:
|
|
|
497
497
|
# --- Operation Handlers with Hard-Coded Response Types ---
|
|
498
498
|
|
|
499
499
|
def _handle_create(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
500
|
-
"""
|
|
500
|
+
""" Pass model explicitly to create method."""
|
|
501
501
|
data = ast.get("data", {})
|
|
502
502
|
validated_data = self.serializer.deserialize(
|
|
503
503
|
model=self.model,
|
|
@@ -526,7 +526,7 @@ class ASTParser:
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
def _handle_update(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
529
|
-
"""
|
|
529
|
+
""" Pass current queryset to update method."""
|
|
530
530
|
data = ast.get("data", {})
|
|
531
531
|
validated_data = self.serializer.deserialize(
|
|
532
532
|
model=self.model,
|
|
@@ -543,7 +543,7 @@ class ASTParser:
|
|
|
543
543
|
# Get the readable fields for this model using our existing method
|
|
544
544
|
readable_fields = self._get_operation_fields(self.model, "read")
|
|
545
545
|
|
|
546
|
-
#
|
|
546
|
+
# Update records and get the count and affected instance IDs
|
|
547
547
|
updated_count, updated_instances = self.engine.update(
|
|
548
548
|
self.current_queryset, # Pass current queryset
|
|
549
549
|
ast,
|
|
@@ -570,7 +570,7 @@ class ASTParser:
|
|
|
570
570
|
}
|
|
571
571
|
|
|
572
572
|
def _handle_delete(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
573
|
-
"""
|
|
573
|
+
""" Pass current queryset to delete method."""
|
|
574
574
|
permissions = self.registry.get_config(self.model).permissions
|
|
575
575
|
deleted_count, rows_deleted = self.engine.delete(
|
|
576
576
|
self.current_queryset, ast, self.request, permissions
|
|
@@ -586,7 +586,7 @@ class ASTParser:
|
|
|
586
586
|
}
|
|
587
587
|
|
|
588
588
|
def _handle_update_instance(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
589
|
-
"""
|
|
589
|
+
""" Pass model explicitly to update_instance method."""
|
|
590
590
|
# Extract and deserialize the data.
|
|
591
591
|
raw_data = ast.get("data", {})
|
|
592
592
|
# Allow partial updates.
|
|
@@ -603,7 +603,7 @@ class ASTParser:
|
|
|
603
603
|
# Retrieve permissions from the self.registry.
|
|
604
604
|
permissions = self.registry.get_config(self.model).permissions
|
|
605
605
|
|
|
606
|
-
#
|
|
606
|
+
# Delegate to the engine's instance-based update method.
|
|
607
607
|
updated_instance = self.engine.update_instance(
|
|
608
608
|
self.model,
|
|
609
609
|
ast,
|
|
@@ -628,7 +628,7 @@ class ASTParser:
|
|
|
628
628
|
|
|
629
629
|
def _handle_delete_instance(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
630
630
|
"""
|
|
631
|
-
|
|
631
|
+
Handles deletion of a single instance.
|
|
632
632
|
Typically, no additional data deserialization is needed beyond the filter,
|
|
633
633
|
so we simply verify that a filter is provided and then delegate to the engine.
|
|
634
634
|
"""
|
|
@@ -643,7 +643,7 @@ class ASTParser:
|
|
|
643
643
|
# Retrieve permissions from the self.registry.
|
|
644
644
|
permissions = self.registry.get_config(self.model).permissions
|
|
645
645
|
|
|
646
|
-
#
|
|
646
|
+
# Delegate to the engine's instance-based delete method.
|
|
647
647
|
deleted_count = self.engine.delete_instance(
|
|
648
648
|
self.model, ast, self.request, permissions
|
|
649
649
|
)
|
|
@@ -654,7 +654,7 @@ class ASTParser:
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
def _handle_get(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
657
|
-
"""
|
|
657
|
+
""" Pass current queryset to get method."""
|
|
658
658
|
# Retrieve permissions from the registry
|
|
659
659
|
permissions = self.registry.get_config(self.model).permissions
|
|
660
660
|
record = self.engine.get(self.current_queryset, ast, self.request, permissions)
|
|
@@ -671,7 +671,7 @@ class ASTParser:
|
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
def _handle_get_or_create(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
674
|
-
"""
|
|
674
|
+
""" Pass current queryset to get_or_create method."""
|
|
675
675
|
# Validate and split lookup/defaults (without extra wrapping)
|
|
676
676
|
validated_lookup, validated_defaults = self._validate_and_split_lookup_defaults(
|
|
677
677
|
ast, partial=True
|
|
@@ -684,7 +684,7 @@ class ASTParser:
|
|
|
684
684
|
# Retrieve permissions from configuration
|
|
685
685
|
permissions = self.registry.get_config(self.model).permissions
|
|
686
686
|
|
|
687
|
-
#
|
|
687
|
+
# Call the ORM layer and pass the serializer and request/permissions
|
|
688
688
|
record, created = self.engine.get_or_create(
|
|
689
689
|
self.current_queryset, # Pass current queryset
|
|
690
690
|
{"lookup": ast.get("lookup", {}), "defaults": ast.get("defaults", {})},
|
|
@@ -710,7 +710,7 @@ class ASTParser:
|
|
|
710
710
|
}
|
|
711
711
|
|
|
712
712
|
def _handle_update_or_create(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
713
|
-
"""
|
|
713
|
+
""" Pass current queryset to update_or_create method."""
|
|
714
714
|
# Validate and split lookup/defaults.
|
|
715
715
|
validated_lookup, validated_defaults = self._validate_and_split_lookup_defaults(
|
|
716
716
|
ast, partial=True
|
|
@@ -723,7 +723,7 @@ class ASTParser:
|
|
|
723
723
|
# Retrieve permissions from configuration.
|
|
724
724
|
permissions = self.registry.get_config(self.model).permissions
|
|
725
725
|
|
|
726
|
-
#
|
|
726
|
+
# Call the ORM update_or_create method, passing the serializer, request, and permissions.
|
|
727
727
|
record, created = self.engine.update_or_create(
|
|
728
728
|
self.current_queryset, # Pass current queryset
|
|
729
729
|
{"lookup": ast.get("lookup", {}), "defaults": ast.get("defaults", {})},
|
|
@@ -750,7 +750,7 @@ class ASTParser:
|
|
|
750
750
|
}
|
|
751
751
|
|
|
752
752
|
def _handle_first(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
753
|
-
"""
|
|
753
|
+
""" Pass current queryset to first method."""
|
|
754
754
|
record = self.engine.first(self.current_queryset)
|
|
755
755
|
serialized = self.serializer.serialize(
|
|
756
756
|
record,
|
|
@@ -765,7 +765,7 @@ class ASTParser:
|
|
|
765
765
|
}
|
|
766
766
|
|
|
767
767
|
def _handle_last(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
768
|
-
"""
|
|
768
|
+
""" Pass current queryset to last method."""
|
|
769
769
|
record = self.engine.last(self.current_queryset)
|
|
770
770
|
serialized = self.serializer.serialize(
|
|
771
771
|
record,
|
|
@@ -780,7 +780,7 @@ class ASTParser:
|
|
|
780
780
|
}
|
|
781
781
|
|
|
782
782
|
def _handle_exists(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
783
|
-
"""
|
|
783
|
+
""" Pass current queryset to exists method."""
|
|
784
784
|
exists_flag = self.engine.exists(self.current_queryset)
|
|
785
785
|
return {
|
|
786
786
|
"data": exists_flag,
|
|
@@ -791,7 +791,7 @@ class ASTParser:
|
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
def _handle_aggregate(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
794
|
-
"""
|
|
794
|
+
""" Pass current queryset to all aggregate methods."""
|
|
795
795
|
op_type = ast.get("type")
|
|
796
796
|
if op_type == "aggregate":
|
|
797
797
|
aggs = ast.get("aggregates", {})
|
|
@@ -859,7 +859,7 @@ class ASTParser:
|
|
|
859
859
|
}
|
|
860
860
|
|
|
861
861
|
def _handle_read(self, ast: Dict[str, Any]) -> Dict[str, Any]:
|
|
862
|
-
"""
|
|
862
|
+
""" Pass current queryset to fetch_list method."""
|
|
863
863
|
offset_raw = self.serializer_options.get("offset", 0)
|
|
864
864
|
limit_raw = self.serializer_options.get("limit", self.config.default_limit)
|
|
865
865
|
offset_val = int(offset_raw) if offset_raw is not None else None
|
|
@@ -868,7 +868,7 @@ class ASTParser:
|
|
|
868
868
|
# Retrieve permissions from configuration
|
|
869
869
|
permissions = self.registry.get_config(self.model).permissions
|
|
870
870
|
|
|
871
|
-
#
|
|
871
|
+
# Fetch list with bulk permission checks
|
|
872
872
|
rows = self.engine.fetch_list(
|
|
873
873
|
self.current_queryset, # Pass current queryset
|
|
874
874
|
offset=offset_val,
|
|
@@ -30,12 +30,9 @@ class AbstractORMProvider(ABC):
|
|
|
30
30
|
A merged ORM engine interface that combines both query building (filtering,
|
|
31
31
|
ordering, aggregation, etc.) and ORM provider responsibilities (queryset assembly,
|
|
32
32
|
event signal registration, model graph construction, etc.).
|
|
33
|
-
|
|
34
|
-
UPDATED: All query manipulation methods now take queryset parameters and return
|
|
35
|
-
new querysets instead of mutating internal state.
|
|
36
33
|
"""
|
|
37
34
|
|
|
38
|
-
# === Query Engine Methods
|
|
35
|
+
# === Query Engine Methods ===
|
|
39
36
|
|
|
40
37
|
@abstractmethod
|
|
41
38
|
def validate(
|
|
@@ -145,7 +142,7 @@ class AbstractORMProvider(ABC):
|
|
|
145
142
|
"""
|
|
146
143
|
pass
|
|
147
144
|
|
|
148
|
-
# === Aggregate Methods
|
|
145
|
+
# === Aggregate Methods ===
|
|
149
146
|
|
|
150
147
|
@abstractmethod
|
|
151
148
|
def aggregate(
|
|
@@ -201,7 +198,7 @@ class AbstractORMProvider(ABC):
|
|
|
201
198
|
"""Return True if the queryset has any results; otherwise False."""
|
|
202
199
|
pass
|
|
203
200
|
|
|
204
|
-
# === CRUD Methods
|
|
201
|
+
# === CRUD Methods ===
|
|
205
202
|
|
|
206
203
|
@abstractmethod
|
|
207
204
|
def create(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
from typing import (Any, Callable, Dict, List, Optional, Set, Type, TypeVar,
|
|
6
|
+
Union)
|
|
7
|
+
|
|
8
|
+
# Django imports
|
|
9
|
+
from django.db.models import Field as DjangoField
|
|
10
|
+
from django.db.models import Model as DjangoModel
|
|
11
|
+
from django.db.models.query import QuerySet as DjangoQuerySet
|
|
12
|
+
from rest_framework.request import Request as DRFRequest
|
|
13
|
+
|
|
14
|
+
# Type definitions, when we add FastAPI support, we wil turn these into unions
|
|
15
|
+
ORMField = DjangoField
|
|
16
|
+
ORMModel = DjangoModel
|
|
17
|
+
ORMQuerySet = DjangoQuerySet
|
|
18
|
+
RequestType = DRFRequest
|
|
19
|
+
|
|
20
|
+
class ActionType(Enum):
|
|
21
|
+
CREATE = "create"
|
|
22
|
+
READ = "read"
|
|
23
|
+
UPDATE = "update"
|
|
24
|
+
DELETE = "delete"
|
|
25
|
+
BULK_UPDATE = "bulk_update"
|
|
26
|
+
BULK_DELETE = "bulk_delete"
|
|
27
|
+
# new pre-operation types
|
|
28
|
+
PRE_UPDATE = "pre_update"
|
|
29
|
+
PRE_DELETE = "pre_delete"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b11
|
|
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
|
|
@@ -240,15 +240,3 @@ npx statezero sync
|
|
|
240
240
|
Check out the docs at [Statezero Docs](https://statezero.dev)
|
|
241
241
|
|
|
242
242
|
Run `pip install statezero` and `npm i @statezero/core` to begin.
|
|
243
|
-
|
|
244
|
-
## Pricing
|
|
245
|
-
|
|
246
|
-
StateZero uses a no-rugpull license model:
|
|
247
|
-
|
|
248
|
-
- **$0/month** for companies with revenue up to $3M
|
|
249
|
-
- **$75/month** for companies with revenue up to $7.5M
|
|
250
|
-
- **$200/month** for companies with revenue up to $20M
|
|
251
|
-
- **$500/month** for companies with revenue up to $100M
|
|
252
|
-
- **$1,000/month** for companies with revenue above $100M
|
|
253
|
-
|
|
254
|
-
Lock in your rate forever by signing up early. We can't change your fee or cancel your license.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from enum import Enum, auto
|
|
5
|
-
from typing import (Any, Callable, Dict, List, Optional, Set, Type, TypeVar,
|
|
6
|
-
Union)
|
|
7
|
-
|
|
8
|
-
# Django imports
|
|
9
|
-
try:
|
|
10
|
-
from django.db.models import Field as DjangoField
|
|
11
|
-
from django.db.models import Model as DjangoModel
|
|
12
|
-
from django.db.models.query import QuerySet as DjangoQuerySet
|
|
13
|
-
from rest_framework.request import Request as DRFRequest
|
|
14
|
-
except ImportError:
|
|
15
|
-
DjangoField = None
|
|
16
|
-
DjangoQuerySet = None
|
|
17
|
-
DjangoModel = None
|
|
18
|
-
DRFRequest = object
|
|
19
|
-
|
|
20
|
-
# SQLAlchemy imports
|
|
21
|
-
try:
|
|
22
|
-
from sqlalchemy.ext.declarative import \
|
|
23
|
-
DeclarativeMeta as SQLAlchemyDeclarativeMeta
|
|
24
|
-
from sqlalchemy.orm.query import Query as SQLAlchemyQuery
|
|
25
|
-
from sqlalchemy.sql.schema import Column as SQLAlchemyColumn # type:ignore
|
|
26
|
-
except ImportError:
|
|
27
|
-
SQLAlchemyColumn = None
|
|
28
|
-
SQLAlchemyQuery = None
|
|
29
|
-
SQLAlchemyDeclarativeMeta = None
|
|
30
|
-
|
|
31
|
-
# FastAPI & Flask imports
|
|
32
|
-
try:
|
|
33
|
-
from fastapi import Request as FastAPIRequest
|
|
34
|
-
except ImportError:
|
|
35
|
-
FastAPIRequest = object
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
from flask import Request as FlaskRequest
|
|
39
|
-
except ImportError:
|
|
40
|
-
FlaskRequest = object
|
|
41
|
-
|
|
42
|
-
# Type definitions
|
|
43
|
-
# Explicitly list all possible types. Including 'object' as a fallback ensures the type remains valid
|
|
44
|
-
ORMField = Union[object, DjangoField, SQLAlchemyColumn]
|
|
45
|
-
ORMModel = Union[object, DjangoModel, SQLAlchemyDeclarativeMeta]
|
|
46
|
-
ORMQuerySet = Union[Any, DjangoQuerySet, SQLAlchemyQuery]
|
|
47
|
-
RequestType = Union[DRFRequest, FastAPIRequest, FlaskRequest]
|
|
48
|
-
|
|
49
|
-
class HotPathActionType(Enum):
|
|
50
|
-
CREATED = "created"
|
|
51
|
-
COMPLETED = "completed"
|
|
52
|
-
REJECTED = "rejected"
|
|
53
|
-
|
|
54
|
-
class ActionType(Enum):
|
|
55
|
-
CREATE = "create"
|
|
56
|
-
READ = "read"
|
|
57
|
-
UPDATE = "update"
|
|
58
|
-
DELETE = "delete"
|
|
59
|
-
BULK_UPDATE = "bulk_update"
|
|
60
|
-
BULK_DELETE = "bulk_delete"
|
|
61
|
-
# new pre-operation types
|
|
62
|
-
PRE_UPDATE = "pre_update"
|
|
63
|
-
PRE_DELETE = "pre_delete"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/search_providers/__init__.py
RENAMED
|
File without changes
|
{statezero-0.1.0b9 → statezero-0.1.0b11}/statezero/adaptors/django/search_providers/basic_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|