nlbone 0.6.16__py3-none-any.whl → 0.6.18__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.
@@ -340,8 +340,10 @@ def get_paginated_response(
340
340
  with_count: bool = True,
341
341
  output_cls: Optional[Type] = None,
342
342
  eager_options: Optional[Sequence[LoaderOption]] = None,
343
+ query = None
343
344
  ) -> dict:
344
- query = session.query(entity)
345
+ if not query:
346
+ query = session.query(entity)
345
347
  if eager_options:
346
348
  query = query.options(*eager_options)
347
349
 
@@ -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):
@@ -13,20 +13,20 @@ class PaginateRequest:
13
13
  """
14
14
 
15
15
  def __init__(
16
- self,
17
- limit: int = 10,
18
- offset: int = 0,
19
- sort: Optional[str] = None,
20
- filters: Optional[str] = Query(None, description="e.g. title:abc"),
21
- include: Optional[str] = None,
16
+ self,
17
+ limit: int = 10,
18
+ offset: int = 0,
19
+ sort: Optional[str] = None,
20
+ filters: Optional[str] = Query(None, description="e.g. title:abc"),
21
+ include: Optional[str] = None,
22
22
  ) -> None:
23
23
  self.limit = max(0, limit)
24
24
  self.offset = max(0, offset)
25
25
  self.sort = self._parse_sort(sort)
26
26
  self.filters = self._parse_filters(filters or "")
27
27
  self.include_ids: List[int] = ([int(x) for x in include.split(",") if x.strip().isdigit()] if include else [])[
28
- :50
29
- ]
28
+ :50
29
+ ]
30
30
 
31
31
  @staticmethod
32
32
  def _parse_sort(sort_str: Optional[str]) -> list[dict[str, str]]:
@@ -78,8 +78,8 @@ 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}
81
+ def remove_deleted(self, deleted_at_field: str = "deleted_at"):
82
+ self.filters = self.filters | {"deleted_at": None}
83
83
 
84
84
 
85
85
  class PaginateResponse:
@@ -88,12 +88,12 @@ class PaginateResponse:
88
88
  """
89
89
 
90
90
  def __init__(
91
- self,
92
- data: list[Any],
93
- total_count: int | None,
94
- limit: int,
95
- offset: int,
96
- use_data_key: bool = True,
91
+ self,
92
+ data: list[Any],
93
+ total_count: int | None,
94
+ limit: int,
95
+ offset: int,
96
+ use_data_key: bool = True,
97
97
  ) -> None:
98
98
  self.data = data
99
99
  self.total_count = total_count
@@ -1,4 +1,3 @@
1
-
2
1
  from pydantic import BaseModel, ConfigDict, model_serializer
3
2
 
4
3
  EXCLUDE_NONE = "exclude_none"
@@ -0,0 +1,7 @@
1
+ import time
2
+ from nlbone.adapters.messaging.event_bus import OutboxDispatcher
3
+
4
+ def main(dispatcher: OutboxDispatcher, interval_sec: int = 2):
5
+ while True:
6
+ count = dispatcher.run_once()
7
+ time.sleep(interval_sec)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.6.16
3
+ Version: 0.6.18
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
@@ -15,7 +15,7 @@ nlbone/adapters/db/postgres/__init__.py,sha256=6JYJH0xZs3aR-zuyMpRhsdzFugmqz8npr
15
15
  nlbone/adapters/db/postgres/audit.py,sha256=8f5XOuW7_ybJyy_STam1FNzqmZAAVAu7tmMRUkCGJOM,4594
16
16
  nlbone/adapters/db/postgres/base.py,sha256=kha9xmklzhuQAK8QEkNBn-mAHq8dUKbOM-3abaBpWmQ,71
17
17
  nlbone/adapters/db/postgres/engine.py,sha256=UCegauVB1gvo42ThytYnn5VIcQBwR-5xhcXYFApRFNk,3448
18
- nlbone/adapters/db/postgres/query_builder.py,sha256=V-Eb98xi9ajhJaEAW1BMtZot9A3muZa5gO_NRhYR0bQ,12505
18
+ nlbone/adapters/db/postgres/query_builder.py,sha256=wtOIsUJ3iM5-vFxIUfbkM6I13WE4fSw8pJKdVi4jgXY,12544
19
19
  nlbone/adapters/db/postgres/repository.py,sha256=J_DBE73JhHPYCk90c5-O7lQtZbxDgqjjN9OcWy4Omvs,1660
20
20
  nlbone/adapters/db/postgres/schema.py,sha256=NlE7Rr8uXypsw4oWkdZhZwcIBHQEPIpoHLxcUo98i6s,1039
21
21
  nlbone/adapters/db/postgres/uow.py,sha256=nRxNpY-WoWHpym-XeZ8VHm0MYvtB9wuopOeNdV_ebk8,2088
@@ -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,14 +76,15 @@ 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=w44uBX268WW6JWvyV5hBtvTuN3Da8lSfwLDMgSP1CAQ,4044
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
86
86
  nlbone/interfaces/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
+ nlbone/interfaces/jobs/dispatch_outbox.py,sha256=KeCz-fvqBKdD5pqRofNdtisGpkiEi5VWkb-sGD57eIU,227
87
88
  nlbone/interfaces/jobs/sync_tokens.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
89
  nlbone/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
90
  nlbone/utils/cache.py,sha256=hVfkR62o5vllDrE_nY4At10wK0It6qpZ45K1xoj10cQ,5931
@@ -93,8 +94,8 @@ nlbone/utils/context.py,sha256=MmclJ24BG2uvSTg1IK7J-Da9BhVFDQ5ag4Ggs2FF1_w,1600
93
94
  nlbone/utils/http.py,sha256=UXUoXgQdTRNT08ho8zl-C5ekfDsD8uf-JiMQ323ooqw,872
94
95
  nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
95
96
  nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
96
- nlbone-0.6.16.dist-info/METADATA,sha256=z5PdS95EZDyFi5vrYuOlPi15vE-BkjX37l0SD4od30M,2228
97
- nlbone-0.6.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
- nlbone-0.6.16.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
99
- nlbone-0.6.16.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
- nlbone-0.6.16.dist-info/RECORD,,
97
+ nlbone-0.6.18.dist-info/METADATA,sha256=OLYDCTskzO9n0jDia3Sg_vVvLQLY30w_djubvJinxwU,2228
98
+ nlbone-0.6.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
99
+ nlbone-0.6.18.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
100
+ nlbone-0.6.18.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
+ nlbone-0.6.18.dist-info/RECORD,,