statezero 0.1.0b7__py3-none-any.whl → 0.1.0b9__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.

@@ -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>/", ModelView.as_view(), name="model_view"),
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 = [permission_class]
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)
@@ -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.0b7
3
+ Version: 0.1.0b9
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=-9tWH9hW2Ta5AipXysoWy-em7R-ybHKmPLtBX89nYQc,37118
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=_Ylta5Bba0eI6pDvO7XddMt9ffEutx3JmZS2mSSi5DQ,828
19
- statezero/adaptors/django/views.py,sha256=RTBuGc5iFnt6fjdatA-mNttzAZ3-aNA3Brf5f0ODyFI,17974
18
+ statezero/adaptors/django/urls.py,sha256=OrGQ60vj_wrbiREAKmYDZTwohpKmgjH9n0fdOw1qPaY,924
19
+ statezero/adaptors/django/views.py,sha256=2bJDbXuRGoG2_7zyapWzmRzpSVUHkCpcI58wsrXN1jc,19947
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=uUWSq6k_hXqryhUILvBQP3L2bGMEpFKXglxIT-Tom7U,19818
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.0b7.dist-info/licenses/license.md,sha256=0uKjybTt9K_YbEmYgf25JN292qjjJ-BPofvIZ3wdtX4,7411
45
- statezero-0.1.0b7.dist-info/METADATA,sha256=GzBRo7i-WnqLOPVX_HYJ7CfzbeEpkrlDD3jOZ4Iz57M,7145
46
- statezero-0.1.0b7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- statezero-0.1.0b7.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
48
- statezero-0.1.0b7.dist-info/RECORD,,
44
+ statezero-0.1.0b9.dist-info/licenses/license.md,sha256=0uKjybTt9K_YbEmYgf25JN292qjjJ-BPofvIZ3wdtX4,7411
45
+ statezero-0.1.0b9.dist-info/METADATA,sha256=ZNcWD_BkudIjJRmahGRZ4ftQ9M9HyWQYg2dUcdavAIg,7145
46
+ statezero-0.1.0b9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ statezero-0.1.0b9.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
48
+ statezero-0.1.0b9.dist-info/RECORD,,