rb-commons 0.7.14__tar.gz → 0.7.15__tar.gz
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.
- {rb_commons-0.7.14 → rb_commons-0.7.15}/PKG-INFO +1 -1
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/managers.py +115 -16
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons.egg-info/PKG-INFO +1 -1
- {rb_commons-0.7.14 → rb_commons-0.7.15}/setup.py +1 -1
- {rb_commons-0.7.14 → rb_commons-0.7.15}/README.md +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/broker/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/broker/consumer.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/config.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/injections.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/rabbitmq.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/v2/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/configs/v2/config.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/http/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/http/base_api.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/http/consul.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/http/exceptions.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/enum.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/exceptions.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/querysets.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/orm/services.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/permissions/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/permissions/role_permissions.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/schemes/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/schemes/jwt.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/schemes/pagination.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/utils/__init__.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons/utils/media.py +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons.egg-info/SOURCES.txt +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons.egg-info/dependency_links.txt +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons.egg-info/requires.txt +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/rb_commons.egg-info/top_level.txt +0 -0
- {rb_commons-0.7.14 → rb_commons-0.7.15}/setup.cfg +0 -0
@@ -43,6 +43,16 @@ def query_mutator(func: F) -> F:
|
|
43
43
|
return wrapper
|
44
44
|
|
45
45
|
|
46
|
+
AGG_MAP: dict[str, Callable[[Any], Any]] = {
|
47
|
+
"sum": func.sum,
|
48
|
+
"avg": func.avg,
|
49
|
+
"mean": func.avg,
|
50
|
+
"min": func.min,
|
51
|
+
"max": func.max,
|
52
|
+
"count": func.count,
|
53
|
+
"first": lambda c: c,
|
54
|
+
}
|
55
|
+
|
46
56
|
class BaseManager(Generic[ModelType]):
|
47
57
|
model: Type[ModelType]
|
48
58
|
|
@@ -525,27 +535,116 @@ class BaseManager(Generic[ModelType]):
|
|
525
535
|
self._filtered = True
|
526
536
|
return self
|
527
537
|
|
528
|
-
|
529
|
-
|
538
|
+
def _infer_default_agg(self, column) -> str:
|
539
|
+
try:
|
540
|
+
from sqlalchemy import Integer, BigInteger, SmallInteger, Float, Numeric
|
541
|
+
if hasattr(column, "type") and isinstance(column.type, (Integer, BigInteger, SmallInteger, Float, Numeric)):
|
542
|
+
return "sum"
|
543
|
+
except Exception:
|
544
|
+
pass
|
545
|
+
return "max"
|
546
|
+
def _order_expr_for_path(self, token: str):
|
530
547
|
"""
|
531
|
-
|
532
|
-
|
548
|
+
token grammar:
|
549
|
+
[-]<path>[:<agg>][!first|!last]
|
550
|
+
<path> := "field" | "relation__field" (one hop)
|
551
|
+
<agg> := sum|avg|min|max|count|first (required for uselist=True; optional otherwise)
|
552
|
+
Examples:
|
553
|
+
"category__title"
|
554
|
+
"-reviews__rating:avg!last"
|
555
|
+
"stocks__sold:sum"
|
533
556
|
"""
|
534
|
-
self._invalid_sort_tokens = []
|
535
|
-
self._order_by = []
|
536
|
-
model = self.model
|
537
557
|
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
558
|
+
# strip leading '-' (handled by caller), and parse nulls placement
|
559
|
+
core = token.lstrip("-")
|
560
|
+
nulls_placement = None
|
561
|
+
if core.endswith("!first"):
|
562
|
+
core, nulls_placement = core[:-6], "first"
|
563
|
+
elif core.endswith("!last"):
|
564
|
+
core, nulls_placement = core[:-5], "last"
|
565
|
+
|
566
|
+
# split aggregate suffix if present
|
567
|
+
if ":" in core:
|
568
|
+
path, agg_name = core.split(":", 1)
|
569
|
+
agg_name = agg_name.lower().strip()
|
570
|
+
else:
|
571
|
+
path, agg_name = core, None
|
572
|
+
|
573
|
+
# base column on the model (no relation hop)
|
574
|
+
if "__" not in path and "." not in path:
|
575
|
+
col = getattr(self.model, path, None)
|
544
576
|
if col is None:
|
545
|
-
self.
|
546
|
-
|
547
|
-
|
577
|
+
raise ValueError(f"Invalid order_by field '{path}' for {self.model.__name__}")
|
578
|
+
expr = col
|
579
|
+
if nulls_placement == "first":
|
580
|
+
expr = expr.nullsfirst()
|
581
|
+
elif nulls_placement == "last":
|
582
|
+
expr = expr.nullslast()
|
583
|
+
return expr
|
584
|
+
|
585
|
+
# relation hop (exactly one)
|
586
|
+
parts = re.split(r"\.|\_\_", path)
|
587
|
+
if len(parts) != 2:
|
588
|
+
raise ValueError(f"Only one relation hop supported in order_by: {path!r}")
|
589
|
+
|
590
|
+
rel_name, col_name = parts
|
591
|
+
rel_attr = getattr(self.model, rel_name, None)
|
592
|
+
if rel_attr is None or not hasattr(rel_attr, "property"):
|
593
|
+
raise ValueError(f"Invalid relationship '{rel_name}' on {self.model.__name__}")
|
594
|
+
|
595
|
+
target_mapper = rel_attr.property.mapper
|
596
|
+
target_cls = target_mapper.class_
|
597
|
+
target_col = getattr(target_cls, col_name, None)
|
598
|
+
if target_col is None:
|
599
|
+
raise ValueError(f"Invalid column '{col_name}' on related model {target_cls.__name__}")
|
600
|
+
|
601
|
+
primaryjoin = rel_attr.property.primaryjoin
|
602
|
+
uselist = rel_attr.property.uselist
|
603
|
+
|
604
|
+
# One-to-many (or many-to-many via association): require aggregate (or infer)
|
605
|
+
if uselist:
|
606
|
+
agg_name = agg_name or self._infer_default_agg(target_col)
|
607
|
+
agg_fn = AGG_MAP.get(agg_name)
|
608
|
+
if agg_fn is None:
|
609
|
+
raise ValueError(f"Unsupported aggregate '{agg_name}' in order_by for {path!r}")
|
610
|
+
|
611
|
+
# SELECT agg(related.col) WHERE primaryjoin (correlated)
|
612
|
+
subq = (
|
613
|
+
select(agg_fn(target_col))
|
614
|
+
.where(primaryjoin)
|
615
|
+
.correlate(self.model) # tie to outer row
|
616
|
+
.scalar_subquery()
|
617
|
+
)
|
618
|
+
expr = subq
|
548
619
|
|
620
|
+
else:
|
621
|
+
if agg_name and agg_name != "first":
|
622
|
+
agg_fn = AGG_MAP.get(agg_name)
|
623
|
+
if agg_fn is None:
|
624
|
+
raise ValueError(f"Unsupported aggregate '{agg_name}' in order_by for {path!r}")
|
625
|
+
select_expr = agg_fn(target_col)
|
626
|
+
else:
|
627
|
+
select_expr = target_col
|
628
|
+
|
629
|
+
sub = select(select_expr).where(primaryjoin).correlate(self.model)
|
630
|
+
if agg_name == "first":
|
631
|
+
sub = sub.limit(1)
|
632
|
+
expr = sub.scalar_subquery()
|
633
|
+
|
634
|
+
if nulls_placement == "first":
|
635
|
+
expr = expr.nullsfirst()
|
636
|
+
elif nulls_placement == "last":
|
637
|
+
expr = expr.nullslast()
|
638
|
+
|
639
|
+
return expr
|
640
|
+
|
641
|
+
@query_mutator
|
642
|
+
def sort_by(self, tokens):
|
643
|
+
self._order_by = []
|
644
|
+
for tok in tokens or []:
|
645
|
+
direction = desc if tok.startswith("-") else asc
|
646
|
+
name = tok.lstrip("-")
|
647
|
+
self._order_by.append(direction(self._order_expr_for_path(name)))
|
549
648
|
return self
|
550
649
|
|
551
650
|
def model_to_dict(self, instance: ModelType, exclude: set[str] = None) -> dict:
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
5
|
|
6
6
|
setup(
|
7
7
|
name="rb-commons",
|
8
|
-
version="0.7.
|
8
|
+
version="0.7.15",
|
9
9
|
author="Abdulvoris",
|
10
10
|
author_email="erkinovabdulvoris101@gmail.com",
|
11
11
|
description="Commons of project and simplified orm based on sqlalchemy.",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|