statezero 0.1.0b7__py3-none-any.whl → 0.1.0b8__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.
Potentially problematic release.
This version of statezero might be problematic. Click here for more details.
- statezero/adaptors/django/orm.py +100 -1
- statezero/adaptors/django/urls.py +3 -2
- statezero/adaptors/django/views.py +50 -1
- statezero/core/interfaces.py +32 -0
- {statezero-0.1.0b7.dist-info → statezero-0.1.0b8.dist-info}/METADATA +1 -1
- {statezero-0.1.0b7.dist-info → statezero-0.1.0b8.dist-info}/RECORD +9 -9
- {statezero-0.1.0b7.dist-info → statezero-0.1.0b8.dist-info}/WHEEL +0 -0
- {statezero-0.1.0b7.dist-info → statezero-0.1.0b8.dist-info}/licenses/license.md +0 -0
- {statezero-0.1.0b7.dist-info → statezero-0.1.0b8.dist-info}/top_level.txt +0 -0
statezero/adaptors/django/orm.py
CHANGED
|
@@ -962,4 +962,103 @@ class DjangoORMAdapter(AbstractORMProvider):
|
|
|
962
962
|
|
|
963
963
|
def get_user(self, request):
|
|
964
964
|
"""Return the user from the request."""
|
|
965
|
-
return request.user
|
|
965
|
+
return request.user
|
|
966
|
+
|
|
967
|
+
def validate(
|
|
968
|
+
self,
|
|
969
|
+
model: Type[models.Model],
|
|
970
|
+
data: Dict[str, Any],
|
|
971
|
+
validate_type: str,
|
|
972
|
+
partial: bool,
|
|
973
|
+
request: RequestType,
|
|
974
|
+
permissions: List[Type[AbstractPermission]],
|
|
975
|
+
serializer,
|
|
976
|
+
) -> bool:
|
|
977
|
+
"""
|
|
978
|
+
Fast validation without database queries.
|
|
979
|
+
Only checks model-level permissions and serializer validation.
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
model: Django model class
|
|
983
|
+
data: Data to validate
|
|
984
|
+
validate_type: 'create' or 'update'
|
|
985
|
+
partial: Whether to allow partial validation (only validate provided fields)
|
|
986
|
+
request: Request object
|
|
987
|
+
permissions: Permission classes
|
|
988
|
+
serializer: Serializer instance
|
|
989
|
+
|
|
990
|
+
Returns:
|
|
991
|
+
bool: True if validation passes
|
|
992
|
+
|
|
993
|
+
Raises:
|
|
994
|
+
ValidationError: For serializer validation failures
|
|
995
|
+
PermissionDenied: For permission failures
|
|
996
|
+
"""
|
|
997
|
+
# Basic model-level permission check (no DB query)
|
|
998
|
+
required_action = (
|
|
999
|
+
ActionType.CREATE if validate_type == "create" else ActionType.UPDATE
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
has_permission = False
|
|
1003
|
+
for permission_class in permissions:
|
|
1004
|
+
perm_instance = permission_class()
|
|
1005
|
+
allowed_actions = perm_instance.allowed_actions(request, model)
|
|
1006
|
+
if required_action in allowed_actions:
|
|
1007
|
+
has_permission = True
|
|
1008
|
+
break
|
|
1009
|
+
|
|
1010
|
+
if not has_permission:
|
|
1011
|
+
# Let StateZero exception handling deal with this
|
|
1012
|
+
raise PermissionDenied(f"{validate_type.title()} not allowed")
|
|
1013
|
+
|
|
1014
|
+
# Get field permissions
|
|
1015
|
+
allowed_fields = self._get_allowed_fields(
|
|
1016
|
+
model, permissions, request, validate_type
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
# Filter data to only allowed fields
|
|
1020
|
+
filtered_data = {k: v for k, v in data.items() if k in allowed_fields}
|
|
1021
|
+
|
|
1022
|
+
# Create minimal fields map for serializer
|
|
1023
|
+
model_name = config.orm_provider.get_model_name(model)
|
|
1024
|
+
fields_map = {model_name: allowed_fields}
|
|
1025
|
+
|
|
1026
|
+
# Validate using serializer with partial flag - let ValidationError bubble up naturally
|
|
1027
|
+
serializer.deserialize(
|
|
1028
|
+
model=model,
|
|
1029
|
+
data=filtered_data,
|
|
1030
|
+
partial=partial,
|
|
1031
|
+
request=request,
|
|
1032
|
+
fields_map=fields_map,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
# Only return success case - exceptions handle failures
|
|
1036
|
+
return True
|
|
1037
|
+
|
|
1038
|
+
def _get_allowed_fields(
|
|
1039
|
+
self,
|
|
1040
|
+
model: Type[models.Model],
|
|
1041
|
+
permissions: List[Type[AbstractPermission]],
|
|
1042
|
+
request: RequestType,
|
|
1043
|
+
validate_type: str,
|
|
1044
|
+
) -> Set[str]:
|
|
1045
|
+
"""Helper to get allowed fields based on validate_type."""
|
|
1046
|
+
allowed_fields = set()
|
|
1047
|
+
|
|
1048
|
+
for permission_class in permissions:
|
|
1049
|
+
perm_instance = permission_class()
|
|
1050
|
+
|
|
1051
|
+
if validate_type == "create":
|
|
1052
|
+
create_fields = perm_instance.create_fields(request, model)
|
|
1053
|
+
if create_fields == "__all__":
|
|
1054
|
+
return config.orm_provider.get_fields(model)
|
|
1055
|
+
elif isinstance(create_fields, set):
|
|
1056
|
+
allowed_fields.update(create_fields)
|
|
1057
|
+
else: # update
|
|
1058
|
+
editable_fields = perm_instance.editable_fields(request, model)
|
|
1059
|
+
if editable_fields == "__all__":
|
|
1060
|
+
return config.orm_provider.get_fields(model)
|
|
1061
|
+
elif isinstance(editable_fields, set):
|
|
1062
|
+
allowed_fields.update(editable_fields)
|
|
1063
|
+
|
|
1064
|
+
return allowed_fields
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.urls import path
|
|
2
2
|
|
|
3
|
-
from .views import EventsAuthView, ModelListView, ModelView, SchemaView, FileUploadView, FastUploadView, ActionSchemaView, ActionView
|
|
3
|
+
from .views import EventsAuthView, ModelListView, ModelView, SchemaView, FileUploadView, FastUploadView, ActionSchemaView, ActionView, ValidateView
|
|
4
4
|
|
|
5
5
|
app_name = "statezero"
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ urlpatterns = [
|
|
|
11
11
|
path("files/fast-upload/", FastUploadView.as_view(), name="fast_file_upload"),
|
|
12
12
|
path("actions/<str:action_name>/", ActionView.as_view(), name="action"),
|
|
13
13
|
path("actions-schema/", ActionSchemaView.as_view(), name="actions_schema"),
|
|
14
|
-
path("<str:model_name>/",
|
|
14
|
+
path("<str:model_name>/validate/", ValidateView.as_view(), name="validate"),
|
|
15
15
|
path("<str:model_name>/get-schema/", SchemaView.as_view(), name="schema_view"),
|
|
16
|
+
path("<str:model_name>/", ModelView.as_view(), name="model_view"),
|
|
16
17
|
]
|
|
@@ -432,7 +432,56 @@ class ActionView(APIView):
|
|
|
432
432
|
class ActionSchemaView(APIView):
|
|
433
433
|
"""Django view to provide action schema information for frontend generation"""
|
|
434
434
|
permission_classes = [ORMBridgeViewAccessGate]
|
|
435
|
-
|
|
435
|
+
|
|
436
436
|
def get(self, request):
|
|
437
437
|
"""Return schema information for all registered actions"""
|
|
438
438
|
return DjangoActionSchemaGenerator.generate_actions_schema()
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class ValidateView(APIView):
|
|
442
|
+
"""
|
|
443
|
+
Fast validation endpoint that bypasses the AST system for performance.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
permission_classes = [ORMBridgeViewAccessGate]
|
|
447
|
+
|
|
448
|
+
def post(self, request, model_name):
|
|
449
|
+
"""Validate model data without saving."""
|
|
450
|
+
try:
|
|
451
|
+
# Create processor following the same pattern as other views
|
|
452
|
+
processor = RequestProcessor(config=config, registry=registry)
|
|
453
|
+
|
|
454
|
+
# Get model and config using the processor's ORM provider
|
|
455
|
+
model = processor.orm_provider.get_model_by_name(model_name)
|
|
456
|
+
if not model:
|
|
457
|
+
return Response({"error": f"Model {model_name} not found"}, status=404)
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
model_config = processor.registry.get_config(model)
|
|
461
|
+
except ValueError:
|
|
462
|
+
return Response(
|
|
463
|
+
{"error": f"Model {model_name} not registered"}, status=404
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Extract request data
|
|
467
|
+
data = request.data.get("data", {})
|
|
468
|
+
validate_type = request.data.get("validate_type", "create")
|
|
469
|
+
partial = request.data.get("partial", False) # Default to False
|
|
470
|
+
|
|
471
|
+
# Call validate - let exceptions bubble up to explicit_exception_handler
|
|
472
|
+
result = processor.orm_provider.validate(
|
|
473
|
+
model=model,
|
|
474
|
+
data=data,
|
|
475
|
+
validate_type=validate_type,
|
|
476
|
+
partial=partial,
|
|
477
|
+
request=request,
|
|
478
|
+
permissions=model_config.permissions,
|
|
479
|
+
serializer=processor.data_serializer,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Only success case returns here - return simple boolean
|
|
483
|
+
return Response({"valid": True}, status=200)
|
|
484
|
+
|
|
485
|
+
except Exception as original_exception:
|
|
486
|
+
# Let StateZero's exception handler deal with ValidationError, PermissionDenied, etc.
|
|
487
|
+
return explicit_exception_handler(original_exception)
|
statezero/core/interfaces.py
CHANGED
|
@@ -37,6 +37,38 @@ class AbstractORMProvider(ABC):
|
|
|
37
37
|
|
|
38
38
|
# === Query Engine Methods (UPDATED: Now stateless) ===
|
|
39
39
|
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def validate(
|
|
42
|
+
self,
|
|
43
|
+
model: Type,
|
|
44
|
+
data: Dict[str, Any],
|
|
45
|
+
validate_type: str,
|
|
46
|
+
partial: bool,
|
|
47
|
+
request: Any,
|
|
48
|
+
permissions: List[Type],
|
|
49
|
+
serializer: Any,
|
|
50
|
+
) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Validate model data without saving to database.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
model: The model class to validate against
|
|
56
|
+
data: Data to validate
|
|
57
|
+
validate_type: 'create' or 'update'
|
|
58
|
+
partial: Whether to allow partial validation (only validate provided fields)
|
|
59
|
+
request: Request object for permission context
|
|
60
|
+
permissions: List of permission classes
|
|
61
|
+
serializer: Serializer instance for validation
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
bool: True if validation passes
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ValidationError: For serializer validation failures
|
|
68
|
+
PermissionDenied: For permission failures
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
40
72
|
@abstractmethod
|
|
41
73
|
def get_fields(self, model: ORMModel) -> Set[str]:
|
|
42
74
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b8
|
|
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
|
|
@@ -10,13 +10,13 @@ statezero/adaptors/django/exception_handler.py,sha256=cQF1Fm5IjH91ydB54TK9sqXiAO
|
|
|
10
10
|
statezero/adaptors/django/f_handler.py,sha256=yvITFj9UAnz8-r-aLEcWoz48tBhZ08-VMq9Fsm2uiN8,12305
|
|
11
11
|
statezero/adaptors/django/helpers.py,sha256=0Dyq5vboDuTUaH-KpS3oVDjastA9yv6xI6XpBuvRM3I,5974
|
|
12
12
|
statezero/adaptors/django/middleware.py,sha256=YVr8fkqCk51xJQM-ovtrUiB9Kt9H81cLd9xv4cM9YlM,410
|
|
13
|
-
statezero/adaptors/django/orm.py,sha256
|
|
13
|
+
statezero/adaptors/django/orm.py,sha256=Z62XheCvuKIpKOoIIiLLOwrpJ5jPhv-BGxg-pqPgNaU,40757
|
|
14
14
|
statezero/adaptors/django/permissions.py,sha256=fU2c4bKK0zX2uuVB0UazZHTI-5OkiI5-BtPNcPEWmW0,9525
|
|
15
15
|
statezero/adaptors/django/query_optimizer.py,sha256=-GNqL7Xn8WP8OsLEAAxXpIszSyEwm-l6WjgdkEFzxUM,38541
|
|
16
16
|
statezero/adaptors/django/schemas.py,sha256=shq8ed9qHCnbCfYVsRxVE7V3R3GhGIKeRRj7dI3r1IU,12728
|
|
17
17
|
statezero/adaptors/django/serializers.py,sha256=YFFDu6bzoWkSEOVH5Wmc4yJ8SaOkUA6HbXXYt6djlfc,23296
|
|
18
|
-
statezero/adaptors/django/urls.py,sha256=
|
|
19
|
-
statezero/adaptors/django/views.py,sha256=
|
|
18
|
+
statezero/adaptors/django/urls.py,sha256=OrGQ60vj_wrbiREAKmYDZTwohpKmgjH9n0fdOw1qPaY,924
|
|
19
|
+
statezero/adaptors/django/views.py,sha256=HliMoUqMIYkyh_kBxbWtGUVRJaLMU0eprhWeoEEiUqQ,19954
|
|
20
20
|
statezero/adaptors/django/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
statezero/adaptors/django/extensions/custom_field_serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
statezero/adaptors/django/extensions/custom_field_serializers/file_fields.py,sha256=BaOaJPmyzCp-YFwpsTOvGHjHpk6s8UJuZ5JsF-PEGV4,4518
|
|
@@ -38,11 +38,11 @@ statezero/core/event_bus.py,sha256=2IFLBHSkLzpm1AX0MfSXSmF2X-lXK-gOoODZCtB2Jdw,6
|
|
|
38
38
|
statezero/core/event_emitters.py,sha256=qjMbeUmdn4bG7WiVfqTmNdaflEea5amnTEpOn5X0J44,2046
|
|
39
39
|
statezero/core/exceptions.py,sha256=_krMHWW9qBbMXvvqFdWf85a3Kayn7XbJczfC3x3gmBI,3330
|
|
40
40
|
statezero/core/hook_checks.py,sha256=uqtvwRx1qGsF7Vc49elAWdOjMzhuv3RADKY1wiLvhK4,3425
|
|
41
|
-
statezero/core/interfaces.py,sha256=
|
|
41
|
+
statezero/core/interfaces.py,sha256=I0_AYqKN5sBeN5ghkXoSsr1ZHhMTp-IRKeavG_QNb3o,20802
|
|
42
42
|
statezero/core/process_request.py,sha256=dwIeBEVOE8zA-oE1h65XNOGiVqFbbXA7SzTAguLNgZk,8060
|
|
43
43
|
statezero/core/types.py,sha256=K9x9AU5J6yd2AWvqRz27CeAY6UYfuQoQ7xTEwTijrmM,1982
|
|
44
|
-
statezero-0.1.
|
|
45
|
-
statezero-0.1.
|
|
46
|
-
statezero-0.1.
|
|
47
|
-
statezero-0.1.
|
|
48
|
-
statezero-0.1.
|
|
44
|
+
statezero-0.1.0b8.dist-info/licenses/license.md,sha256=0uKjybTt9K_YbEmYgf25JN292qjjJ-BPofvIZ3wdtX4,7411
|
|
45
|
+
statezero-0.1.0b8.dist-info/METADATA,sha256=qxTysj5kbjUJUj4NGDWI-pz0SuMNbwmHifHU2hLV5MA,7145
|
|
46
|
+
statezero-0.1.0b8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
statezero-0.1.0b8.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
|
|
48
|
+
statezero-0.1.0b8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|