python-general-be-lib 0.9.0__py3-none-any.whl → 0.9.1__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.
- general/interface/repository/crud_repository.py +43 -21
- general/interface/repository/handler/statement_manager.py +10 -3
- general/paginator_dto.py +6 -2
- {python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/METADATA +2 -1
- {python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/RECORD +8 -8
- {python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/WHEEL +0 -0
- {python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/top_level.txt +0 -0
|
@@ -317,7 +317,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
317
317
|
return True
|
|
318
318
|
|
|
319
319
|
def find(self, session: Session = None, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, parsing_model: Type[BaseModel] = None, order_by: str = None,
|
|
320
|
-
asc: bool = None, **kwhere) -> list[Entity]:
|
|
320
|
+
asc: bool = None, orders_by: dict[str, bool] = None, **kwhere) -> list[Entity]:
|
|
321
321
|
"""Find the records matching the given criteria.
|
|
322
322
|
|
|
323
323
|
The filters can be passed as a ``where`` dictionary or as keyword arguments. Being a
|
|
@@ -337,6 +337,11 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
337
337
|
``None``, no ordering is applied.
|
|
338
338
|
asc (bool, optional): Ordering direction, ``True`` for ASC and ``False`` for
|
|
339
339
|
DESC. When ``None``, the default ordering is DESC.
|
|
340
|
+
orders_by (dict[str, bool], optional): Ordering on several columns, as
|
|
341
|
+
``{column: ascending}``. The clauses follow the insertion order of the
|
|
342
|
+
dictionary. Takes precedence over ``order_by``/``asc``. The keys are
|
|
343
|
+
database column names, unlike ``columns``, which takes Python attribute
|
|
344
|
+
names.
|
|
340
345
|
**kwhere: Additional filters as keyword arguments.
|
|
341
346
|
|
|
342
347
|
Returns:
|
|
@@ -344,22 +349,26 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
344
349
|
given.
|
|
345
350
|
|
|
346
351
|
Raises:
|
|
347
|
-
HasNoAttributeException: If a column in ``columns``, ``exclude_columns
|
|
348
|
-
``order_by`` does not exist on the entity.
|
|
352
|
+
HasNoAttributeException: If a column in ``columns``, ``exclude_columns``,
|
|
353
|
+
``order_by`` or ``orders_by`` does not exist on the entity.
|
|
349
354
|
"""
|
|
350
355
|
|
|
351
356
|
keep_open = False if session is None else True
|
|
352
357
|
session = self.open_session if session is None else session
|
|
353
|
-
|
|
358
|
+
if order_by is not None and orders_by is None:
|
|
359
|
+
orders_by = {order_by: asc if asc is not None else False}
|
|
360
|
+
entities = self._find(session=session, where=where, columns=columns, exclude_columns=exclude_columns, limit=limit, orders_by=orders_by, **kwhere)
|
|
354
361
|
parsing_model = parsing_model if parsing_model is not None else self._retrieve_parsing_model(columns=columns, exclude_columns=exclude_columns)
|
|
355
362
|
return self._return(session=session, entities=entities, keep_open=keep_open, commit=False, parsing_model=parsing_model)
|
|
356
363
|
|
|
357
|
-
def _find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0,
|
|
358
|
-
asc: bool = None, **kwhere):
|
|
364
|
+
def _find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, orders_by: dict[str, bool] = None, **kwhere):
|
|
359
365
|
if where is None:
|
|
360
366
|
where = kwhere if kwhere else {}
|
|
367
|
+
if orders_by is None:
|
|
368
|
+
orders_by = {}
|
|
361
369
|
stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
|
|
362
|
-
|
|
370
|
+
for order_by, asc in orders_by.items():
|
|
371
|
+
stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
|
|
363
372
|
stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
|
|
364
373
|
|
|
365
374
|
stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
|
|
@@ -626,7 +635,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
626
635
|
return session.execute(stmt).scalar()
|
|
627
636
|
|
|
628
637
|
def paging_find(self, session: Session = None, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, page: int = 1, order_by: str = None, asc: bool = True,
|
|
629
|
-
parsing_model: Type[BaseModel] = None, **kwhere) -> PaginatorResponseModel[Entity]:
|
|
638
|
+
parsing_model: Type[BaseModel] = None, orders_by: dict[str, bool] = None, **kwhere) -> PaginatorResponseModel[Entity]:
|
|
630
639
|
"""Run a paginated search with ordering and offset support.
|
|
631
640
|
|
|
632
641
|
Computes the total number of pages and returns the requested page only. The data
|
|
@@ -650,30 +659,40 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
650
659
|
asc (bool): Ascending order if ``True``. Default: ``True``.
|
|
651
660
|
parsing_model (Type[BaseModel], optional): Alternative model for the
|
|
652
661
|
serialization.
|
|
662
|
+
orders_by (dict[str, bool], optional): Ordering on several columns, as
|
|
663
|
+
``{column: ascending}``, with the clauses following the insertion order of
|
|
664
|
+
the dictionary. Takes precedence over ``order_by``/``asc``. The primary key
|
|
665
|
+
is **not** appended as a tie-breaker, so an ordering on a non-unique column
|
|
666
|
+
does not give a total order and two pages can overlap: pass the key
|
|
667
|
+
explicitly as the last entry when the ordering must be stable.
|
|
653
668
|
**kwhere: Filters as keyword arguments.
|
|
654
669
|
|
|
655
670
|
Returns:
|
|
656
671
|
PaginatorResponseModel[Entity]: Object carrying ``data``, ``tot_pages``,
|
|
657
|
-
``page``, ``limit``, ``order_by`` and ``asc
|
|
672
|
+
``page``, ``limit``, ``orders_by``, plus ``order_by`` and ``asc`` holding
|
|
673
|
+
the first pair of the ordering.
|
|
658
674
|
"""
|
|
659
675
|
keep_open = False if session is None else True
|
|
660
676
|
session = self.open_session if session is None else session
|
|
661
|
-
|
|
677
|
+
orders_by = orders_by if orders_by else {order_by or self.entity.primary_key()[0].key: asc if asc is not None else False}
|
|
678
|
+
paginated = self._paging_find(session=session, where=where, columns=columns, exclude_columns=exclude_columns, limit=limit, page=page, orders_by=orders_by, **kwhere)
|
|
662
679
|
parsing_model = parsing_model if parsing_model is not None else self._retrieve_parsing_model(columns=columns, exclude_columns=exclude_columns)
|
|
663
680
|
if not keep_open:
|
|
664
681
|
paginated.data = _list_adapter(parsing_model).validate_python(paginated.data, from_attributes=True)
|
|
665
682
|
session.close()
|
|
666
683
|
return paginated
|
|
667
684
|
|
|
668
|
-
def _paging_find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, page: int = 1,
|
|
685
|
+
def _paging_find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, page: int = 1, orders_by: dict[str, bool] = None, **kwhere):
|
|
669
686
|
if where is None:
|
|
670
687
|
where = kwhere if kwhere else {}
|
|
688
|
+
if not orders_by:
|
|
689
|
+
orders_by = {self.entity.primary_key()[0].key: True}
|
|
671
690
|
|
|
672
691
|
stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
|
|
673
692
|
stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit, page=page)
|
|
674
693
|
|
|
675
|
-
order_by
|
|
676
|
-
|
|
694
|
+
for order_by, asc in orders_by.items():
|
|
695
|
+
stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
|
|
677
696
|
|
|
678
697
|
conditions = self.stmt_manager.conditions(where=where)
|
|
679
698
|
if conditions:
|
|
@@ -690,20 +709,22 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
690
709
|
else:
|
|
691
710
|
count_ = self._preconf_count(session=session, where=conditions)
|
|
692
711
|
|
|
693
|
-
return self.parse_pagination(limit=limit, page=page,
|
|
712
|
+
return self.parse_pagination(limit=limit, page=page, orders_by=orders_by, count=count_, data=entities)
|
|
694
713
|
|
|
695
|
-
def parse_pagination(self, limit: int, page: int,
|
|
714
|
+
def parse_pagination(self, limit: int, page: int, orders_by: dict[str, bool], count: int, data: Sequence[Row] | Sequence[Row | RowMapping] | Sequence[RowMapping]) -> PaginatorResponseModel:
|
|
696
715
|
"""Build the ``PaginatorResponseModel`` from the pagination data.
|
|
697
716
|
|
|
698
717
|
Computes the total number of pages. When ``limit`` is 0, meaning no limit, the
|
|
699
|
-
length of the data is used as the effective limit
|
|
700
|
-
|
|
718
|
+
length of the data is used as the effective limit. The response carries the whole
|
|
719
|
+
``orders_by`` plus its first pair as ``order_by``/``asc``, which are the fields the
|
|
720
|
+
clients read as the main ordering criterion.
|
|
701
721
|
|
|
702
722
|
Args:
|
|
703
723
|
limit (int): Number of items per page.
|
|
704
724
|
page (int): Current page.
|
|
705
|
-
|
|
706
|
-
|
|
725
|
+
orders_by (dict[str, bool]): Ordering applied, as ``{column: ascending}``. Its
|
|
726
|
+
first pair fills ``order_by``/``asc``; when empty both stay ``None``,
|
|
727
|
+
because no ordering was applied.
|
|
707
728
|
count (int): Total number of filtered records.
|
|
708
729
|
data (Sequence): Entities of the current page.
|
|
709
730
|
|
|
@@ -711,10 +732,11 @@ class CrudRepository[Entity: Base | Type[Base]]:
|
|
|
711
732
|
PaginatorResponseModel: Complete paginated response.
|
|
712
733
|
"""
|
|
713
734
|
limit = limit if limit else len(data)
|
|
714
|
-
order_by =
|
|
735
|
+
order_by = next(iter(orders_by), None) if orders_by else None
|
|
736
|
+
asc = orders_by.get(order_by) if order_by is not None else None
|
|
715
737
|
tot_pages = math.ceil(count / limit) if count and limit > 0 else 1
|
|
716
738
|
|
|
717
|
-
return PaginatorResponseModel(limit=limit, page=page, order_by=order_by, asc=asc, tot_pages=tot_pages, data=data)
|
|
739
|
+
return PaginatorResponseModel(limit=limit, page=page, order_by=order_by, asc=asc, tot_pages=tot_pages, orders_by=orders_by, data=data)
|
|
718
740
|
|
|
719
741
|
def distinct_pairs(self, key: str, value: str, session: Session = None, where: dict[str, Any] = None, **kwhere) -> list[Row] | None:
|
|
720
742
|
"""Return the distinct ``(key, value)`` pairs of the records matching ``where``.
|
|
@@ -113,12 +113,19 @@ class StatementManager:
|
|
|
113
113
|
return stmt
|
|
114
114
|
|
|
115
115
|
def order_by_condition(self, stmt: Select, order_by: str, asc: bool):
|
|
116
|
-
"""Add
|
|
116
|
+
"""Add one ORDER BY clause to the statement.
|
|
117
|
+
|
|
118
|
+
Adds a single criterion and can be called repeatedly to build an ordering on
|
|
119
|
+
several columns: ``Select.order_by`` appends, so the clauses keep the order of the
|
|
120
|
+
calls. The column name is resolved against the table columns, so a name that is
|
|
121
|
+
not a column cannot reach the SQL.
|
|
117
122
|
|
|
118
123
|
Args:
|
|
119
124
|
stmt (Select): Select statement to add the ordering to.
|
|
120
|
-
order_by (str):
|
|
121
|
-
|
|
125
|
+
order_by (str): Database name of the column to order by. Falsy values leave the
|
|
126
|
+
statement untouched.
|
|
127
|
+
asc (bool): If ``True``, ascending order (ASC); if ``False`` or ``None``,
|
|
128
|
+
descending (DESC).
|
|
122
129
|
|
|
123
130
|
Returns:
|
|
124
131
|
Select: Statement with the ORDER BY clause added.
|
general/paginator_dto.py
CHANGED
|
@@ -18,12 +18,12 @@ class PaginatorBaseModel(ExtBaseModel):
|
|
|
18
18
|
page (int): Current page number, 1-based. Alias: ``numeroPagina``.
|
|
19
19
|
limit (int | None): Number of items per page. Alias: ``numeroElementi``.
|
|
20
20
|
order_by (str | None): Field used for the ordering. Alias: ``campoOrdinamento``.
|
|
21
|
-
asc (bool): Ascending order if ``True``. Alias: ``crescente``.
|
|
21
|
+
asc (bool | None): Ascending order if ``True``. Alias: ``crescente``.
|
|
22
22
|
"""
|
|
23
23
|
page: int = Field(alias="numeroPagina")
|
|
24
24
|
limit: Optional[int] = Field(default=None, alias="numeroElementi")
|
|
25
25
|
order_by: Optional[str] = Field(default=None, alias="campoOrdinamento")
|
|
26
|
-
asc: bool = Field(alias="crescente")
|
|
26
|
+
asc: Optional[bool] = Field(default=None, alias="crescente")
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class PaginatorRequestModel(PaginatorBaseModel):
|
|
@@ -45,8 +45,12 @@ class PaginatorResponseModel[ResponseType](PaginatorBaseModel):
|
|
|
45
45
|
Attributes:
|
|
46
46
|
tot_pages (int): Total number of pages, computed by the repository.
|
|
47
47
|
Alias: ``pagineTotali``.
|
|
48
|
+
orders_by (dict[str, bool] | None): Whole ordering applied, as
|
|
49
|
+
``{column: ascending}``. The inherited ``order_by``/``asc`` carry its first
|
|
50
|
+
pair, which is the main criterion. Alias: ``campiOrdinamento``.
|
|
48
51
|
data (list[ResponseType]): Items of the current page.
|
|
49
52
|
"""
|
|
50
53
|
|
|
51
54
|
tot_pages: int = Field(alias="pagineTotali")
|
|
55
|
+
orders_by: Optional[dict[str, bool]] = Field(default=None, alias="campiOrdinamento")
|
|
52
56
|
data: list[ResponseType]
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-general-be-lib
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: General purpose backend library — SQLAlchemy CRUD/Geometry repositories, FastAPI exceptions, Pydantic base models, logger utilities.
|
|
5
5
|
Author-email: Andrea Di Placido <a.diplacido@arpes.it>, "Arpes S.r.l." <it.admin@arpes.it>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Arpes-IS/python-general-be-lib
|
|
8
8
|
Project-URL: Repository, https://github.com/Arpes-IS/python-general-be-lib
|
|
9
|
+
Project-URL: Changelog, https://github.com/Arpes-IS/python-general-be-lib/blob/main/CHANGELOG.md
|
|
9
10
|
Keywords: backend,sqlalchemy,fastapi,pydantic,repository,crud,geoalchemy2
|
|
10
11
|
Classifier: Development Status :: 3 - Alpha
|
|
11
12
|
Classifier: Intended Audience :: Developers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
general/logger.py,sha256=P-7-BLtO1XC2m43wXrnJcTakr2iZBE0gUkMVppJ9exE,4506
|
|
3
|
-
general/paginator_dto.py,sha256=
|
|
3
|
+
general/paginator_dto.py,sha256=ug2KJMPQRgxcwTu8-MvuLi7MwvmkHDG9cHwfp6EzqIg,2267
|
|
4
4
|
general/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
general/exception/__init__.py,sha256=3o5KaoCwA020aG0GIR0jOA4t_ibOBiji0xMxZx4uNug,745
|
|
6
6
|
general/exception/access_exceptions.py,sha256=tNIBjxM0-5iSOXDc7Il01eb36mEk-POQGVaHhCpG278,744
|
|
@@ -14,7 +14,7 @@ general/interface/metadata/__init__.py,sha256=pXKYY4uO92eQI3gtuG4FKZbBi76gu-VJLx
|
|
|
14
14
|
general/interface/metadata/crud_metadata.py,sha256=i6ldkyUqjHPerninf--sQlGyUm18FgzIBmDq55UYz0g,22819
|
|
15
15
|
general/interface/metadata/geom_metadata.py,sha256=thfVc0hyKgkTFIQrRaX6khf_NbEHyEkc-zUA7nO8bI8,11918
|
|
16
16
|
general/interface/repository/__init__.py,sha256=eWoNy35LVu6sDrK5ju7VAeoclknKO0-yTgjVckJpi8s,321
|
|
17
|
-
general/interface/repository/crud_repository.py,sha256=
|
|
17
|
+
general/interface/repository/crud_repository.py,sha256=CH05WG6ZKkDpS5DEB_718AoRqJUS47CMXM65bI-1nCE,40032
|
|
18
18
|
general/interface/repository/geometry_repository.py,sha256=dB1AmZyyi5cWEzl-_4-2eOXXex-rQsa_cu6bl85wtNE,11252
|
|
19
19
|
general/interface/repository/many_to_many_repository.py,sha256=wl-K0n-rwPvIDpa_LfoBq1iHUnC6QvSLm-uwyKA4taw,8574
|
|
20
20
|
general/interface/repository/view_repository.py,sha256=wxDGw7kenuVrDd9nfh7-omw2m9Jex5WzF_lhEiqPoA0,7902
|
|
@@ -26,9 +26,9 @@ general/interface/repository/handler/interval_handler.py,sha256=4TtEcpzUFZeyB-yi
|
|
|
26
26
|
general/interface/repository/handler/json_handler.py,sha256=f_nGemsxTPJ3W0xkU99sCFA0EVo1xa8p803NyjcGJT4,4414
|
|
27
27
|
general/interface/repository/handler/json_ilike_handler.py,sha256=icfA4dO8E_W_KUEG5HB1nLVI1jfjxzkG8ZJ-IaCd4kY,3530
|
|
28
28
|
general/interface/repository/handler/or_handler.py,sha256=UOjIh77H4y-x434YGLYRfVIXKnod1M5XPiMf71V2JTg,3137
|
|
29
|
-
general/interface/repository/handler/statement_manager.py,sha256=
|
|
30
|
-
python_general_be_lib-0.9.
|
|
31
|
-
python_general_be_lib-0.9.
|
|
32
|
-
python_general_be_lib-0.9.
|
|
33
|
-
python_general_be_lib-0.9.
|
|
34
|
-
python_general_be_lib-0.9.
|
|
29
|
+
general/interface/repository/handler/statement_manager.py,sha256=dPG-HZy0jVM3Y4ZnCp2QPY4gLgt-rWSzOvY1_UmotcI,7333
|
|
30
|
+
python_general_be_lib-0.9.1.dist-info/licenses/LICENSE,sha256=iUaO1XZyB9P3Tmog0OILuTisP6vXGe3QKz-4yRTxOFk,1069
|
|
31
|
+
python_general_be_lib-0.9.1.dist-info/METADATA,sha256=J4CIjmhDsD6SN26tAfFUvtbd2eQOggjhucncPn6Sn6c,3409
|
|
32
|
+
python_general_be_lib-0.9.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
33
|
+
python_general_be_lib-0.9.1.dist-info/top_level.txt,sha256=tTZePW8_CNUqSgKFd2SEH72ZbnhS0OYjRsgcv0ikSFY,8
|
|
34
|
+
python_general_be_lib-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
{python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{python_general_be_lib-0.9.0.dist-info → python_general_be_lib-0.9.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|