nlbone 0.6.15__py3-none-any.whl → 0.6.17__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.
@@ -11,20 +11,33 @@ def assemble_response(
11
11
  obj: Any,
12
12
  reg: ResourceRegistry,
13
13
  selected_rules: Dict[str, FieldRule],
14
- base_schema: type[BaseModel] | None,
15
14
  session,
15
+ base_schema: type[BaseModel] | None,
16
+ scope_map: dict[str, set[str]] = None,
16
17
  ) -> Dict[str, Any]:
17
18
  base = {f: getattr(obj, f, None) for f in reg.default_fields - set(reg.rules.keys())}
18
19
  if base_schema:
19
20
  base = base_schema.model_validate(base).model_dump()
20
21
 
21
- ctx = {"file_service": Container.file_service(), "entity": obj, "db": session}
22
- for name, rule in selected_rules.items():
22
+ ctx = {
23
+ "file_service": Container.file_service(),
24
+ "entity": obj,
25
+ "db": session,
26
+ "pricing_service": Container.pricing_service(),
27
+ }
28
+ roots = {name.split(".", 1)[0] for name in selected_rules.keys()}
29
+ for root in roots:
30
+ rule = reg.rules.get(root)
31
+ if not rule:
32
+ continue
33
+
23
34
  if rule.loader:
24
- value = inject_dependencies(rule.loader, dependencies=ctx)
35
+ dependencies = ctx | {"scope": scope_map.get(root, {""})} if scope_map else ctx
36
+ value = inject_dependencies(rule.loader, dependencies=dependencies)
25
37
  else:
26
- value = _get_nested_attr(obj, name)
27
- _put_nested_key(base, name, value)
38
+ value = _get_nested_attr(obj, root)
39
+
40
+ _put_nested_key(base, root, value)
28
41
 
29
42
  return base
30
43
 
@@ -15,6 +15,10 @@ def _schema_fields(schema: Type[BaseModel], by_alias: bool = True) -> Set[str]:
15
15
  return names
16
16
 
17
17
 
18
+ def _prefix_name(prefix: str, name: str) -> str:
19
+ return f"{prefix}.{name}" if name else prefix
20
+
21
+
18
22
  @dataclass(frozen=True)
19
23
  class FieldRule:
20
24
  """
@@ -51,6 +55,7 @@ class ResourceRegistry:
51
55
  rules: Dict[str, FieldRule] = field(default_factory=dict)
52
56
  bundles: Dict[str, Set[str]] = field(default_factory=dict)
53
57
  _schema_fields_cache: Set[str] = field(default_factory=set, repr=False)
58
+ mounts: Dict[str, "ResourceRegistry"] = field(default_factory=dict)
54
59
 
55
60
  def add_rule(self, rule: FieldRule) -> "ResourceRegistry":
56
61
  self.rules[rule.name] = rule
@@ -103,3 +108,41 @@ class ResourceRegistry:
103
108
 
104
109
  self.default_fields = final
105
110
  return self
111
+
112
+ def mount(
113
+ self,
114
+ child: "ResourceRegistry",
115
+ *,
116
+ at: str,
117
+ include_child_defaults: bool = False,
118
+ include_child_bundles: bool = True,
119
+ ) -> "ResourceRegistry":
120
+ # 1) rules
121
+ for r in child.rules.values():
122
+ prefixed = FieldRule(
123
+ name=_prefix_name(at, r.name),
124
+ default=(include_child_defaults and r.default),
125
+ permission=r.permission,
126
+ deps=tuple(_prefix_name(at, d) for d in r.deps),
127
+ columns=r.columns,
128
+ join_paths=r.join_paths,
129
+ loader=None, # load by parent
130
+ )
131
+ self.add_rule(prefixed)
132
+
133
+ if include_child_defaults:
134
+ self.default_fields |= {_prefix_name(at, f) for f in child.default_fields}
135
+
136
+ if include_child_bundles:
137
+ for bname, items in child.bundles.items():
138
+ pb = _prefix_name(at, bname)
139
+ prefixed_items = set()
140
+ for it in items:
141
+ if it.startswith("@"):
142
+ prefixed_items.add(_prefix_name(at, it))
143
+ else:
144
+ prefixed_items.add(_prefix_name(at, it))
145
+ self.add_bundle(pb, prefixed_items)
146
+
147
+ self.mounts[at] = child
148
+ return self
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from typing import Dict, List, Set, Tuple
2
3
 
3
4
  from nlbone.interfaces.api.additional_filed.field_registry import (
@@ -89,6 +90,18 @@ def resolve_requested_fields(
89
90
  for f in list(selected_rules.keys()):
90
91
  add_deps(f)
91
92
 
93
+ parents_to_add = set()
94
+ for f in list(final):
95
+ if "." in f:
96
+ parent = f.split(".", 1)[0]
97
+ if parent in reg.rules:
98
+ parents_to_add.add(parent)
99
+
100
+ final |= parents_to_add
101
+ for p in parents_to_add:
102
+ if p not in selected_rules and p in reg.rules:
103
+ selected_rules[p] = reg.rules[p]
104
+
92
105
  # validate deps too
93
106
  unknown_deps = {d for d in final if (d not in reg.default_fields and d not in reg.rules)}
94
107
  if unknown_deps:
@@ -130,3 +143,20 @@ def build_query_plan(
130
143
  columns.extend(r.columns or [])
131
144
  joins.extend(r.join_paths or [])
132
145
  return columns, joins
146
+
147
+
148
+ def build_field_scope(requested_fields: Set[str]) -> dict[str, set[str]]:
149
+ """
150
+ {'variants', 'variants.cost', 'supplier.address.city'} →
151
+ {
152
+ 'variants': {'', 'cost'},
153
+ 'supplier': {'address.city'}
154
+ }
155
+ """
156
+ scope: dict[str, set[str]] = defaultdict(set)
157
+ for f in requested_fields:
158
+ parts = f.split(".", 1)
159
+ root = parts[0]
160
+ suffix = parts[1] if len(parts) == 2 else "" # '' یعنی خود root
161
+ scope[root].add(suffix)
162
+ return scope
@@ -78,10 +78,10 @@ def client_or_user_has_access_func(permissions=None, client_permissions=None):
78
78
  if not token:
79
79
  raise UnauthorizedException()
80
80
  needed = client_permissions or permissions
81
- if client_has_access_func(permissions=needed):
82
- return
83
- if user_has_access_func(permissions=needed):
84
- return
81
+ try:
82
+ client_has_access_func(permissions=needed)
83
+ except Exception:
84
+ user_has_access_func(permissions=needed)
85
85
 
86
86
 
87
87
  def client_or_user_has_access(*, permissions=None, client_permissions=None):
@@ -78,6 +78,9 @@ class PaginateRequest:
78
78
  filters_dict[key] = value_cast
79
79
  return filters_dict
80
80
 
81
+ def remove_deleted(self, deleted_at_field: str = "deleted_at"):
82
+ self.filters = self.filters | {"deleted_at": None}
83
+
81
84
 
82
85
  class PaginateResponse:
83
86
  """
@@ -1,4 +1,3 @@
1
-
2
1
  from pydantic import BaseModel, ConfigDict, model_serializer
3
2
 
4
3
  EXCLUDE_NONE = "exclude_none"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.6.15
3
+ Version: 0.6.17
4
4
  Summary: Backbone package for interfaces and infrastructure in Python projects
5
5
  Author-email: Amir Hosein Kahkbazzadeh <a.khakbazzadeh@gmail.com>
6
6
  License: MIT
@@ -61,14 +61,14 @@ nlbone/interfaces/api/exceptions.py,sha256=mvpfTf_CUy1V3Y1iGLMGJh_yWfNlJs_ubrYtJ
61
61
  nlbone/interfaces/api/routers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  nlbone/interfaces/api/schemas.py,sha256=NIEKeTdJtwwIkIxL7WURNZF8g34I4TlRAqs-x1Uq7YI,108
63
63
  nlbone/interfaces/api/additional_filed/__init__.py,sha256=BWemliLSQV9iq1vdUaF733q0FOSipSWBOQk9eYj732Q,318
64
- nlbone/interfaces/api/additional_filed/assembler.py,sha256=MyB6YimAAzMe5WUZtHei7a4ITsOpYhrDWIWNAKq7tY8,1599
65
- nlbone/interfaces/api/additional_filed/field_registry.py,sha256=ScpMsjDBkuUdh_FaBmk1EXBeydERcNthyom1tcJK3BM,3820
66
- nlbone/interfaces/api/additional_filed/resolver.py,sha256=eVBPj6WsUkZbM3wGPyexwA6d5GThCSgbwNzzHImaOyU,3767
64
+ nlbone/interfaces/api/additional_filed/assembler.py,sha256=80sFuNiquqdLPOcx7MoQ_ud9fAJtA327M77Z9V47pIU,1956
65
+ nlbone/interfaces/api/additional_filed/field_registry.py,sha256=xrA-2pMqX6JA4vy7LliJv5btQdy-6AKctFxZZCpWVOI,5321
66
+ nlbone/interfaces/api/additional_filed/resolver.py,sha256=jv1TIBBHN4LBIMwHGipcy4iq0uP0r6udyaqvhRzb8Bk,4655
67
67
  nlbone/interfaces/api/additional_filed/default_field_rules/__init__.py,sha256=LUSAOO3xRUt5ptlraIx7H-7dSkdr1D-WprmnqXRB16g,48
68
68
  nlbone/interfaces/api/additional_filed/default_field_rules/image_field_rules.py,sha256=ecKqPeXZ-YiF14RK9PmK7ln3PCzpCUc18S5zm5IF3fw,339
69
69
  nlbone/interfaces/api/dependencies/__init__.py,sha256=rnYRrFVZCfICQrp_PVFlzNg3BeC57yM08wn2DbOHCfk,359
70
70
  nlbone/interfaces/api/dependencies/async_auth.py,sha256=bfxgBXhp29WqevjTG4jrdPNR-75APm4jKyHdOOtxnp4,1825
71
- nlbone/interfaces/api/dependencies/auth.py,sha256=SVqdH78ek65MdT-81_tJo9QiEbnERAh9dR10pRdTDb8,2833
71
+ nlbone/interfaces/api/dependencies/auth.py,sha256=uJONw74kBh_r-ycH6ra4huBQEE8aia95CjbULPJhyQ8,2834
72
72
  nlbone/interfaces/api/dependencies/db.py,sha256=-UD39J_86UU7ZJs2ZncpdND0yhAG0NeeeALrgSDuuFw,466
73
73
  nlbone/interfaces/api/dependencies/uow.py,sha256=QfLEvLYLNWZJQN1k-0q0hBVtUld3D75P4j39q_RjcnE,1181
74
74
  nlbone/interfaces/api/middleware/__init__.py,sha256=zbX2vaEAfxRMIYwO2MVY_2O6bqG5H9o7HqGpX14U3Is,158
@@ -76,10 +76,10 @@ nlbone/interfaces/api/middleware/access_log.py,sha256=vIkxxxfy2HcjqqKb8XCfGCcSri
76
76
  nlbone/interfaces/api/middleware/add_request_context.py,sha256=av-qs0biOYuF9R6RJOo2eYsFqDL9WRYWcjVakFhbt-w,1834
77
77
  nlbone/interfaces/api/middleware/authentication.py,sha256=ze7vCm492QsX9nPL6A-PqZCmC1C5ZgUE-OWI6fCLpsU,1809
78
78
  nlbone/interfaces/api/pagination/__init__.py,sha256=pA1uC4rK6eqDI5IkLVxmgO2B6lExnOm8Pje2-hifJZw,431
79
- nlbone/interfaces/api/pagination/offset_base.py,sha256=iipO09sAUb0U9ctMMujBHQo1IgMRbasvwnHJFtA9ZeA,3812
79
+ nlbone/interfaces/api/pagination/offset_base.py,sha256=65UgQZ70IlWUbP9Usd--IxVYW_V0hQLoyXHVffnLFZ0,3940
80
80
  nlbone/interfaces/api/schema/__init__.py,sha256=LAqgynfupeqOQ6u0I5ucrcYnojRMZUg9yW8IjKSQTNI,119
81
81
  nlbone/interfaces/api/schema/adaptive_schema.py,sha256=bdWBNpP2NfOJ_in4btXn0lrZOK70x-OqfmZ-NpIJdoQ,3347
82
- nlbone/interfaces/api/schema/base_response_model.py,sha256=Y-mqNzVheTaGkM4m2DsybK-GLhfODMhBUNyTRoaf_Zc,600
82
+ nlbone/interfaces/api/schema/base_response_model.py,sha256=lkBs7k0IcQiSQdJ3KvqDQPr_zwqKNbwaQjcwAE_chnU,599
83
83
  nlbone/interfaces/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
84
  nlbone/interfaces/cli/init_db.py,sha256=C67n2MsJ1vzkJxC8zfUYOxFdd6mEB_vT9agxN6jWoG8,790
85
85
  nlbone/interfaces/cli/main.py,sha256=pNldsTgplVyXa-Hx96dySO2I9gFRi49nDXv7J_dO73s,686
@@ -93,8 +93,8 @@ nlbone/utils/context.py,sha256=MmclJ24BG2uvSTg1IK7J-Da9BhVFDQ5ag4Ggs2FF1_w,1600
93
93
  nlbone/utils/http.py,sha256=UXUoXgQdTRNT08ho8zl-C5ekfDsD8uf-JiMQ323ooqw,872
94
94
  nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
95
95
  nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
96
- nlbone-0.6.15.dist-info/METADATA,sha256=t9hCCQwgtmGzXZOwsvXsc5hinAX5ogPmN36zOQPdyfk,2228
97
- nlbone-0.6.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
- nlbone-0.6.15.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
99
- nlbone-0.6.15.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
- nlbone-0.6.15.dist-info/RECORD,,
96
+ nlbone-0.6.17.dist-info/METADATA,sha256=gL9qwFoS4iJt_JFXECIEa-9auQwxciKpUwvno7GZSx0,2228
97
+ nlbone-0.6.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
+ nlbone-0.6.17.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
99
+ nlbone-0.6.17.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
+ nlbone-0.6.17.dist-info/RECORD,,