python-general-be-lib 0.5.2__tar.gz → 0.5.4__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.
Files changed (36) hide show
  1. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/PKG-INFO +1 -1
  2. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/crud_repository.py +13 -4
  3. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/__init__.py +1 -0
  4. python_general_be_lib-0.5.4/general/interface/repository/handler/json_ilike_handler.py +104 -0
  5. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/statement_manager.py +5 -4
  6. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/pyproject.toml +1 -1
  7. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/python_general_be_lib.egg-info/PKG-INFO +1 -1
  8. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/python_general_be_lib.egg-info/SOURCES.txt +1 -0
  9. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/setup.py +2 -2
  10. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/LICENSE +0 -0
  11. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/README.md +0 -0
  12. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/__init__.py +0 -0
  13. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/exception/__init__.py +0 -0
  14. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/exception/access_exceptions.py +0 -0
  15. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/exception/crud_exceptions.py +0 -0
  16. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/exception/exception_interface.py +0 -0
  17. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/base/__init__.py +0 -0
  18. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/base/base_model.py +0 -0
  19. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/base/declarative_base.py +0 -0
  20. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/metadata/__init__.py +0 -0
  21. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/metadata/crud_metadata.py +0 -0
  22. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/metadata/geom_metadata.py +0 -0
  23. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/__init__.py +0 -0
  24. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/geometry_repository.py +0 -0
  25. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/base_handler.py +0 -0
  26. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/ilike_handler.py +0 -0
  27. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/interval_handler.py +0 -0
  28. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/handler/json_handler.py +0 -0
  29. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/many_to_many_repository.py +0 -0
  30. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/interface/repository/view_repository.py +0 -0
  31. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/logger.py +0 -0
  32. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/general/paginator_dto.py +0 -0
  33. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/python_general_be_lib.egg-info/dependency_links.txt +0 -0
  34. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/python_general_be_lib.egg-info/requires.txt +0 -0
  35. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/python_general_be_lib.egg-info/top_level.txt +0 -0
  36. {python_general_be_lib-0.5.2 → python_general_be_lib-0.5.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-general-be-lib
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -217,12 +217,13 @@ class CrudRepository[Entity: Base | Type[Base]]:
217
217
  else:
218
218
  pass
219
219
 
220
- 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, **kwhere) -> list[Entity]:
220
+ 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,
221
+ asc: bool = None, **kwhere) -> list[Entity]:
221
222
  """Cerca i record che corrispondono ai criteri specificati.
222
223
 
223
224
  I filtri possono essere passati come dizionario ``where`` o come
224
225
  keyword arguments. Supporta caricamento parziale delle colonne,
225
- limit e modelli di parsing alternativi.
226
+ limit, ordinamento e modelli di parsing alternativi.
226
227
 
227
228
  Args:
228
229
  session (Session, optional): Sessione esterna. Se ``None``, ne viene
@@ -234,6 +235,10 @@ class CrudRepository[Entity: Base | Type[Base]]:
234
235
  limit (int): Numero massimo di risultati. 0 = nessun limite. Default: 0.
235
236
  parsing_model (Type[BaseModel], optional): Modello Pydantic alternativo
236
237
  per la serializzazione del risultato.
238
+ order_by (str, optional): Nome della colonna per l'ordinamento dei risultati.
239
+ Se ``None``, non viene applicato nessun ordinamento.
240
+ asc (bool, optional): Direzione dell'ordinamento. ``True`` per ASC,
241
+ ``False`` per DESC. Se ``None``, l'ordinamento di default è DESC.
237
242
  **kwhere: Filtri aggiuntivi come keyword arguments.
238
243
 
239
244
  Returns:
@@ -243,17 +248,21 @@ class CrudRepository[Entity: Base | Type[Base]]:
243
248
  Example:
244
249
  >>> repo.find(active=True, limit=10)
245
250
  >>> repo.find(where={"role": ["admin", "editor"]})
251
+ >>> repo.find(active=True, order_by="name", asc=True)
246
252
  """
253
+
247
254
  keep_open = False if session is None else True
248
255
  session = self.open_session if session is None else session
249
- entities = self._find(session=session, where=where, columns=columns, exclude_columns=exclude_columns, limit=limit, **kwhere)
256
+ entities = self._find(session=session, where=where, columns=columns, exclude_columns=exclude_columns, limit=limit, order_by=order_by, asc=asc, **kwhere)
250
257
  return self._return(session=session, entities=entities, keep_open=keep_open, commit=False, parsing_model=parsing_model)
251
258
 
252
- def _find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, **kwhere):
259
+ def _find(self, session: Session, where: dict[str, Any] = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 0, order_by: str = None,
260
+ asc: bool = None, **kwhere):
253
261
  if where is None:
254
262
  where = kwhere if kwhere else {}
255
263
  stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
256
264
  stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
265
+ stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
257
266
  stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
258
267
  return self.execute(session, stmt)
259
268
 
@@ -2,4 +2,5 @@ from .base_handler import *
2
2
  from .ilike_handler import *
3
3
  from .interval_handler import *
4
4
  from .json_handler import *
5
+ from .json_ilike_handler import *
5
6
  from .statement_manager import *
@@ -0,0 +1,104 @@
1
+ __all__ = ["JsonILikeHandler"]
2
+
3
+ from typing import Any
4
+
5
+ from general.interface.repository.handler import BaseHandler
6
+ from sqlalchemy import Column, Select, Update, Delete, or_
7
+ from sqlalchemy.sql.base import ReadOnlyColumnCollection
8
+
9
+
10
+ class JsonILikeHandler(BaseHandler):
11
+ """Handler per ricerche case-insensitive (ILIKE) su più colonne tramite un alias.
12
+
13
+ Permette di esporre un singolo parametro di ricerca testuale (es. ``"search"``)
14
+ che viene applicato come ``ILIKE '%value%'`` su un insieme di colonne con ``OR``.
15
+
16
+ Attributes:
17
+ ilike_aliases (list[str]): Chiavi nel dizionario ``where`` che attivano
18
+ la ricerca ILIKE (es. ``["search", "q"]``).
19
+ ilike_attributes (list[str]): Nomi delle colonne su cui applicare ILIKE
20
+ (es. ``["name", "surname", "email"]``).
21
+
22
+ Example:
23
+ >>> handler = JsonILikeHandler(
24
+ ... columns=User.columns(),
25
+ ... ilike_aliases=["search"],
26
+ ... ilike_attributes=["name", "surname"]
27
+ ... )
28
+ >>> repo.find(where={"search": "mario"})
29
+ # WHERE name ILIKE '%mario%' OR surname ILIKE '%mario%'
30
+ """
31
+ __slots__ = "ilike_aliases", "ilike_attributes", "json_column", "json_ilike_keys"
32
+ ilike_aliases: list[str]
33
+ ilike_attributes: list[str]
34
+ json_ilike_keys: list[str]
35
+ json_column: str
36
+
37
+ def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]], ilike_aliases: list[str] = None, ilike_attributes: list[str] = None):
38
+ """Inizializza l'handler validando che le colonne ILIKE esistano sull'entità.
39
+
40
+ Args:
41
+ columns (ReadOnlyColumnCollection): Colonne della tabella.
42
+ ilike_aliases (list[str], optional): Chiavi alias che attivano la ricerca.
43
+ Default: lista vuota.
44
+ ilike_attributes (list[str], optional): Colonne su cui applicare ILIKE.
45
+ Default: lista vuota.
46
+
47
+ Raises:
48
+ ValueError: Se uno degli ``ilike_attributes`` non è presente tra le colonne.
49
+ """
50
+ self.columns = columns
51
+ self.ilike_aliases = ilike_aliases if ilike_aliases is not None else []
52
+ self.ilike_attributes = []
53
+ self.json_ilike_keys = []
54
+
55
+ if ilike_attributes:
56
+ for ilike_attribute in ilike_attributes:
57
+ if ilike_attribute not in self.columns:
58
+ self.json_ilike_keys.append(ilike_attribute)
59
+ else:
60
+ self.ilike_attributes.append(ilike_attribute)
61
+
62
+ for key, column in columns.items():
63
+ try:
64
+ if column.type.python_type == dict:
65
+ self.json_column = key
66
+ break
67
+ except NotImplementedError:
68
+ continue
69
+
70
+ def handle(self, stmt: Select | Update | Delete, where: dict[str, Any] = None, **kwargs):
71
+ """Applica i filtri ILIKE allo statement per ogni alias trovato in ``where``.
72
+
73
+ Per ogni alias presente in ``ilike_aliases`` e trovato in ``where``,
74
+ estrae il valore, lo rimuove dal dizionario e aggiunge la condizione ILIKE
75
+ su tutte le colonne in ``ilike_attributes`` con ``OR``.
76
+
77
+ Args:
78
+ stmt (Select | Update | Delete): Statement su cui applicare i filtri.
79
+ where (dict[str, Any]): Dizionario dei filtri. Gli alias ILIKE vengono
80
+ estratti e rimossi in-place.
81
+ **kwargs: Ignorati (compatibilità con l'interfaccia base).
82
+
83
+ Returns:
84
+ Select | Update | Delete: Statement con le condizioni ILIKE aggiunte.
85
+ """
86
+
87
+ for alias in self.ilike_aliases:
88
+ condition = where.pop(alias, None)
89
+ if condition:
90
+ stmt = stmt.where(self._handle(alias, condition))
91
+ return stmt
92
+
93
+ def _handle(self, alias: str, condition: str, neq: bool = False):
94
+ """Costruisce la condizione ILIKE con OR su tutte le colonne configurate.
95
+
96
+ Args:
97
+ alias (str): Alias attivato (usato per identificare la ricerca, non la colonna).
98
+ condition (str): Stringa da cercare (viene wrappata con ``%``).
99
+ neq (bool): Non utilizzato in questa implementazione.
100
+
101
+ Returns:
102
+ ColumnElement: Espressione ``OR(col1 ILIKE '%val%', col2 ILIKE '%val%', ...)``.
103
+ """
104
+ return or_(*[self.columns[col].ilike(f'%{condition}%') for col in self.ilike_attributes], *[self.columns[self.json_column][key].as_string().ilike(f'%{condition}%') for key in self.json_ilike_keys])
@@ -74,10 +74,11 @@ class StatementManager:
74
74
  Raises:
75
75
  HasNoAttributeException: Se la colonna ``order_by`` non esiste sull'entità.
76
76
  """
77
- try:
78
- stmt = stmt.order_by(self._base_handler.columns[order_by].asc()) if asc else stmt.order_by(self._base_handler.columns[order_by].desc())
79
- except KeyError:
80
- raise HasNoAttributeException(order_by)
77
+ if order_by:
78
+ try:
79
+ stmt = stmt.order_by(self._base_handler.columns[order_by].asc()) if asc else stmt.order_by(self._base_handler.columns[order_by].desc())
80
+ except KeyError:
81
+ raise HasNoAttributeException(order_by)
81
82
  return stmt
82
83
 
83
84
  def limit_offset_condition(self, stmt: Select, limit: int, page: int = None):
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends.legacy:build"
4
4
 
5
5
  [project]
6
6
  name = "python-general-be-lib"
7
- version = "0.5.2"
7
+ version = "0.5.4"
8
8
  description = "General purpose backend library — SQLAlchemy CRUD/Geometry repositories, FastAPI exceptions, Pydantic base models, logger utilities."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-general-be-lib
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -25,6 +25,7 @@ general/interface/repository/handler/base_handler.py
25
25
  general/interface/repository/handler/ilike_handler.py
26
26
  general/interface/repository/handler/interval_handler.py
27
27
  general/interface/repository/handler/json_handler.py
28
+ general/interface/repository/handler/json_ilike_handler.py
28
29
  general/interface/repository/handler/statement_manager.py
29
30
  python_general_be_lib.egg-info/PKG-INFO
30
31
  python_general_be_lib.egg-info/SOURCES.txt
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="python-general-be-lib",
5
- version="0.5.2",
5
+ version="0.5.4",
6
6
  packages=find_packages(include=["general*"]),
7
7
  python_requires=">=3.12",
8
8
  install_requires=[
@@ -11,4 +11,4 @@ setup(
11
11
  "sqlalchemy>=2.0.0",
12
12
  "geoalchemy2>=0.14.0",
13
13
  ],
14
- )
14
+ )