statezero 0.1.0b17__py3-none-any.whl → 0.1.0b18__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.

@@ -763,6 +763,7 @@ class DjangoORMAdapter(AbstractORMProvider):
763
763
  def get_fields(self, model: models.Model) -> Set[str]:
764
764
  """
765
765
  Return a set of the model fields.
766
+ Includes both database fields and additional_fields (computed fields).
766
767
  """
767
768
  model_config = registry.get_config(model)
768
769
  if model_config.fields and "__all__" != model_config.fields:
@@ -775,6 +776,14 @@ class DjangoORMAdapter(AbstractORMProvider):
775
776
  resolved_fields = resolved_fields.union(additional_fields)
776
777
  return resolved_fields
777
778
 
779
+ def get_db_fields(self, model: models.Model) -> Set[str]:
780
+ """
781
+ Return only actual database fields for the model.
782
+ Excludes read-only additional_fields (computed fields).
783
+ Used for deserialization - hooks can write to any DB field.
784
+ """
785
+ return set(field.name for field in model._meta.get_fields())
786
+
778
787
  def build_model_graph(
779
788
  self, model: Type[models.Model], model_graph: nx.DiGraph = None
780
789
  ) -> nx.DiGraph:
@@ -41,9 +41,9 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
41
41
  all_field_names: Set[str] = set()
42
42
  db_field_names: Set[str] = set()
43
43
 
44
- if model_config.frontend_fields != "__all__":
44
+ if model_config.fields != "__all__":
45
45
  all_fields = [
46
- field for field in all_fields if field.name in model_config.frontend_fields
46
+ field for field in all_fields if field.name in model_config.fields
47
47
  ]
48
48
 
49
49
  for field in all_fields:
@@ -6,7 +6,7 @@ from rest_framework import serializers
6
6
  import contextvars
7
7
  from contextlib import contextmanager
8
8
  import logging
9
- from cytoolz import pluck
9
+ from cytoolz import pluck, keyfilter
10
10
  from zen_queries import queries_disabled
11
11
 
12
12
  from statezero.adaptors.django.config import config, registry
@@ -484,33 +484,42 @@ class DRFDynamicSerializer(AbstractDataSerializer):
484
484
  # Serious security issue if fields_map is None
485
485
  assert fields_map is not None, "fields_map is required and cannot be None"
486
486
 
487
- # Use the context manager for the duration of deserialization
488
- with fields_map_context(fields_map):
489
- # Create serializer class
490
- serializer_class = DynamicModelSerializer.for_model(model)
491
- available_fields = set(serializer_class().fields.keys())
487
+ # Get model name and allowed fields from fields_map
488
+ model_name = config.orm_provider.get_model_name(model)
489
+ allowed_fields = fields_map.get(model_name, set())
492
490
 
493
- try:
494
- model_config = registry.get_config(model)
495
- if model_config.pre_hooks:
496
- for hook in model_config.pre_hooks:
497
- hook_result = hook(data, request=request)
498
- if settings.DEBUG:
499
- data = _check_pre_hook_result(
500
- original_data=data,
501
- result_data=hook_result,
502
- model=model,
503
- serializer_fields=available_fields
504
- )
505
- else:
506
- data = hook_result or data
507
- except ValueError:
508
- # No model config available
509
- model_config = None
491
+ # Filter user input to only allowed fields (security boundary)
492
+ data = dict(keyfilter(lambda k: k in allowed_fields, data))
493
+
494
+ try:
495
+ model_config = registry.get_config(model)
496
+ except ValueError:
497
+ # No model config available
498
+ model_config = None
499
+
500
+ # Run pre-hooks on filtered data (hooks can add any DB fields)
501
+ if model_config and model_config.pre_hooks:
502
+ for hook in model_config.pre_hooks:
503
+ hook_result = hook(data, request=request)
504
+ if settings.DEBUG:
505
+ # Note: available_fields check removed since hooks can add any DB field
506
+ data = hook_result or data
507
+ else:
508
+ data = hook_result or data
509
+
510
+ # Expand fields_map to all DB fields for serializer validation
511
+ # This allows hooks to add fields that aren't in the original fields_map
512
+ all_db_fields = config.orm_provider.get_db_fields(model)
513
+ expanded_fields_map = {model_name: all_db_fields}
514
+
515
+ # Use the context manager with expanded fields map
516
+ with fields_map_context(expanded_fields_map):
517
+ # Create serializer class with all DB fields available
518
+ serializer_class = DynamicModelSerializer.for_model(model)
510
519
 
511
520
  # Create serializer
512
521
  serializer = serializer_class(
513
- data=data,
522
+ data=data,
514
523
  partial=partial,
515
524
  request=request
516
525
  )
@@ -570,11 +570,11 @@ class FieldPermissionsView(APIView):
570
570
  # For read operations, default "__all__" to frontend_fields
571
571
  if operation_type == "read":
572
572
  # If frontend_fields is also "__all__", then return all fields
573
- if model_config.frontend_fields == "__all__":
573
+ if model_config.fields == "__all__":
574
574
  return all_fields
575
575
  # Otherwise, use frontend_fields as the default for "__all__"
576
576
  else:
577
- fields = model_config.frontend_fields
577
+ fields = model_config.fields
578
578
  fields &= all_fields # Ensure fields actually exist
579
579
  allowed_fields |= fields
580
580
  else:
@@ -386,19 +386,8 @@ class ASTParser:
386
386
 
387
387
  # If any permission allows all fields
388
388
  if fields == "__all__":
389
- # NEW: For read operations, default "__all__" to frontend_fields
390
- if operation_type == "read":
391
- # If frontend_fields is also "__all__", then return all fields
392
- if model_config.frontend_fields == "__all__":
393
- return all_fields
394
- # Otherwise, use frontend_fields as the default for "__all__"
395
- else:
396
- fields = model_config.frontend_fields
397
- fields &= all_fields # Ensure fields actually exist
398
- allowed_fields |= fields
399
- else:
400
- # For create/update operations, "__all__" means truly all fields
401
- return all_fields
389
+ return all_fields
390
+
402
391
  # Add allowed fields from this permission
403
392
  else: # Ensure we're not operating on the string "__all__"
404
393
  fields &= all_fields # Ensure fields actually exist
statezero/core/config.py CHANGED
@@ -181,7 +181,6 @@ class ModelConfig:
181
181
  searchable_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
182
182
  ordering_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
183
183
  fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
184
- frontend_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
185
184
  display: Optional[Any] = None,
186
185
  DEBUG: bool = False,
187
186
  ):
@@ -196,7 +195,6 @@ class ModelConfig:
196
195
  self.searchable_fields = searchable_fields or set()
197
196
  self.ordering_fields = ordering_fields or set()
198
197
  self.fields = fields or "__all__"
199
- self.frontend_fields = frontend_fields or self.fields
200
198
  self.display = display
201
199
  self.DEBUG = DEBUG or False
202
200
 
@@ -70,6 +70,16 @@ class AbstractORMProvider(ABC):
70
70
  def get_fields(self, model: ORMModel) -> Set[str]:
71
71
  """
72
72
  Get all of the model fields - doesn't apply permissions check.
73
+ Includes both database fields and additional_fields (computed fields).
74
+ """
75
+ pass
76
+
77
+ @abstractmethod
78
+ def get_db_fields(self, model: ORMModel) -> Set[str]:
79
+ """
80
+ Get only the actual database fields for a model.
81
+ Excludes read-only additional_fields (computed fields).
82
+ Used for deserialization - hooks can write to any DB field.
73
83
  """
74
84
  pass
75
85
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statezero
3
- Version: 0.1.0b17
3
+ Version: 0.1.0b18
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=SHIg-A1hiMql1pb026DxkUijUU
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=Z62XheCvuKIpKOoIIiLLOwrpJ5jPhv-BGxg-pqPgNaU,40757
13
+ statezero/adaptors/django/orm.py,sha256=NjxZQDI0r5Ccl5bPxFcPxnao2oJlN0k5_RdGs4kCWAo,41191
14
14
  statezero/adaptors/django/permissions.py,sha256=fU2c4bKK0zX2uuVB0UazZHTI-5OkiI5-BtPNcPEWmW0,9525
15
15
  statezero/adaptors/django/query_optimizer.py,sha256=-iAh5kyE8WNZdjb_qBbNag_nxKzejroUYPBdwG_uVaQ,41462
16
- statezero/adaptors/django/schemas.py,sha256=YpmXd3XMZpkjPiHB_3OMnDgc1thgOj3RgFx-YXhScqs,14058
17
- statezero/adaptors/django/serializers.py,sha256=YFFDu6bzoWkSEOVH5Wmc4yJ8SaOkUA6HbXXYt6djlfc,23296
16
+ statezero/adaptors/django/schemas.py,sha256=cnDzfUWS_oH16kEc0cy6h7tNmBSPpNp_Rz3sH0vU2Ak,14040
17
+ statezero/adaptors/django/serializers.py,sha256=lvcO4Vn9UM8TD28pse1XxrQugkFlzSeL8rYDHRU5bpk,23722
18
18
  statezero/adaptors/django/urls.py,sha256=bLn_kL5a5VBQfhl2-UCpLmguSenjJ7bouPoKMKNTX5M,1054
19
- statezero/adaptors/django/views.py,sha256=RKReFV3AiMT_5jL5fVbPDnSnKfhpBtVt5FlzL4psWhw,24201
19
+ statezero/adaptors/django/views.py,sha256=rALVs8McRzgf-YhLoq7GqisjKiLxaq2k0WUrcGhmRvA,24183
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
@@ -29,20 +29,20 @@ statezero/adaptors/django/search_providers/basic_search.py,sha256=5_GJ1r_B6JdIYX
29
29
  statezero/adaptors/django/search_providers/postgres_search.py,sha256=IMoHxzfi-Y3hAxPND4Xc6GatrPs1eXAqpmcfwt5Zr14,2459
30
30
  statezero/core/__init__.py,sha256=Z6RTutAAElLMEjBFphVVmpySPdJBA55j-Uo0BtR7c5E,1040
31
31
  statezero/core/actions.py,sha256=eq4zuDhK1h-nZ24jUQhiWL6BcMqq-W4BhdVNdYegymw,2969
32
- statezero/core/ast_parser.py,sha256=QTFRDwanN2jJJOs4SeRdRPiVZWlK-tkmwZBlQNByew0,39321
32
+ statezero/core/ast_parser.py,sha256=QHpwWlVQTrxE1xen4xMZcALDn7eEl4CL5PoVfGQg_c4,38539
33
33
  statezero/core/ast_validator.py,sha256=YZAflPyba0kXWBNhd1Z_XeEk-_zUzM6MkY9zSlX1PMs,11582
34
34
  statezero/core/classes.py,sha256=TlJYUhiYniTJqZCSVo_-85mgJ4muhCPpJWxlgG-Vph8,6996
35
- statezero/core/config.py,sha256=kOcQPzBA06d8EliP2bVY0daFlt8bm-8ZOsqb5x7-8JA,11822
35
+ statezero/core/config.py,sha256=RNRujZg393a2B_uj4isUUIhwEUZFAfSlafKHu_O3EAs,11679
36
36
  statezero/core/context_storage.py,sha256=DVx525ZMRorj41kg5K0N6pPdGkQ5_XEJcBucpH5ChxQ,162
37
37
  statezero/core/event_bus.py,sha256=2IFLBHSkLzpm1AX0MfSXSmF2X-lXK-gOoODZCtB2Jdw,6284
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=kVkNWyh52tUlzD02CRheLJof3DyQoVcPuvX33fL6sn8,20544
41
+ statezero/core/interfaces.py,sha256=YgONfBl8PY0sLLILMMmBAJ84xJS-rNCVjhXtsCzoOAE,20938
42
42
  statezero/core/process_request.py,sha256=dwIeBEVOE8zA-oE1h65XNOGiVqFbbXA7SzTAguLNgZk,8060
43
43
  statezero/core/types.py,sha256=mMtqK3fGhEM6LtzUgQrxlyP-V0VgVqc-1eVKgRjTzp0,913
44
- statezero-0.1.0b17.dist-info/licenses/license.md,sha256=0uKjybTt9K_YbEmYgf25JN292qjjJ-BPofvIZ3wdtX4,7411
45
- statezero-0.1.0b17.dist-info/METADATA,sha256=OTnEueJ01hbh4EwG6LzpGxgdF4HzCg0sxXb4QpXfeKM,6704
46
- statezero-0.1.0b17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- statezero-0.1.0b17.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
48
- statezero-0.1.0b17.dist-info/RECORD,,
44
+ statezero-0.1.0b18.dist-info/licenses/license.md,sha256=0uKjybTt9K_YbEmYgf25JN292qjjJ-BPofvIZ3wdtX4,7411
45
+ statezero-0.1.0b18.dist-info/METADATA,sha256=XIu8HuwWqs4D4Iq-nCRr8mIPG-hlSxAa_2E9w2zCmvE,6704
46
+ statezero-0.1.0b18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ statezero-0.1.0b18.dist-info/top_level.txt,sha256=UAuZYPKczradU1kcMQxsGjUzEW0qdgsqzhXyscrcLpw,10
48
+ statezero-0.1.0b18.dist-info/RECORD,,