python-general-be-lib 0.6.0__tar.gz → 0.7.0__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 (37) hide show
  1. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/PKG-INFO +1 -1
  2. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/base/base_model.py +13 -4
  3. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/metadata/crud_metadata.py +10 -2
  4. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/crud_repository.py +77 -9
  5. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/base_handler.py +9 -5
  6. python_general_be_lib-0.7.0/general/interface/repository/handler/or_handler.py +90 -0
  7. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/view_repository.py +12 -2
  8. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/pyproject.toml +1 -1
  9. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/python_general_be_lib.egg-info/PKG-INFO +1 -1
  10. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/python_general_be_lib.egg-info/SOURCES.txt +1 -0
  11. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/setup.py +1 -1
  12. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/LICENSE +0 -0
  13. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/README.md +0 -0
  14. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/__init__.py +0 -0
  15. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/exception/__init__.py +0 -0
  16. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/exception/access_exceptions.py +0 -0
  17. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/exception/crud_exceptions.py +0 -0
  18. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/exception/exception_interface.py +0 -0
  19. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/base/__init__.py +0 -0
  20. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/base/declarative_base.py +0 -0
  21. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/metadata/__init__.py +0 -0
  22. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/metadata/geom_metadata.py +0 -0
  23. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/__init__.py +0 -0
  24. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/geometry_repository.py +0 -0
  25. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/__init__.py +0 -0
  26. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/ilike_handler.py +0 -0
  27. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/interval_handler.py +0 -0
  28. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/json_handler.py +0 -0
  29. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/json_ilike_handler.py +0 -0
  30. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/handler/statement_manager.py +0 -0
  31. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/interface/repository/many_to_many_repository.py +0 -0
  32. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/logger.py +0 -0
  33. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/general/paginator_dto.py +0 -0
  34. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/python_general_be_lib.egg-info/dependency_links.txt +0 -0
  35. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/python_general_be_lib.egg-info/requires.txt +0 -0
  36. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/python_general_be_lib.egg-info/top_level.txt +0 -0
  37. {python_general_be_lib-0.6.0 → python_general_be_lib-0.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-general-be-lib
3
- Version: 0.6.0
3
+ Version: 0.7.0
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
@@ -1,9 +1,21 @@
1
1
  __all__ = ["ExtBaseModel", "CamelExtBaseModel"]
2
2
 
3
+ from functools import lru_cache
4
+
3
5
  from pydantic import BaseModel, ConfigDict
4
6
  from pydantic.alias_generators import to_camel
5
7
 
6
8
 
9
+ @lru_cache(maxsize=None)
10
+ def _alias_to_name(model: type[BaseModel]) -> dict[str, str]:
11
+ """Mappa ``{alias: nome_campo}`` di un modello, costruita una volta per classe.
12
+
13
+ Cachata sulla classe (long-lived) per evitare la scansione lineare di
14
+ ``model_fields`` a ogni ``get_attr_by_alias``.
15
+ """
16
+ return {field.alias: name for name, field in model.model_fields.items() if field.alias}
17
+
18
+
7
19
  class ConfigModel(BaseModel):
8
20
  """Modello Pydantic base con configurazione condivisa.
9
21
 
@@ -40,10 +52,7 @@ class CamelConfigModel(ConfigModel):
40
52
  >>> MyModel.get_attr_by_alias("myField")
41
53
  'my_field'
42
54
  """
43
- for name, field in cls.model_fields.items():
44
- if field.alias == alias:
45
- return name
46
- return alias
55
+ return _alias_to_name(cls).get(alias, alias)
47
56
 
48
57
 
49
58
  class ExtBaseModel(ConfigModel):
@@ -4,7 +4,7 @@ import logging
4
4
  from typing import Optional, Any, Type, Iterable, Sequence
5
5
 
6
6
  from pydantic import BaseModel
7
- from sqlalchemy import MetaData, Engine, Column, Table, inspect, Connection, select, RowMapping, CursorResult, not_
7
+ from sqlalchemy import MetaData, Engine, Column, Table, inspect, Connection, select, RowMapping, CursorResult, not_, ARRAY, type_coerce
8
8
  from sqlalchemy.exc import NoSuchTableError
9
9
 
10
10
  from ...exception.crud_exceptions import HasNoAttributeException, InternalServerException
@@ -496,7 +496,15 @@ class CrudMetadata:
496
496
  Returns:
497
497
  ColumnElement: Espressione booleana SQLAlchemy.
498
498
  """
499
- eq_where = col.in_(condition) if isinstance(condition, list) else col == condition if str(col.type) != "ARRAY" else condition == col.any_()
499
+ is_array = isinstance(col.type, ARRAY)
500
+ if is_array and isinstance(condition, list):
501
+ eq_where = col.op("&&")(type_coerce(condition, col.type))
502
+ elif isinstance(condition, list):
503
+ eq_where = col.in_(condition)
504
+ elif is_array:
505
+ eq_where = condition == col.any_()
506
+ else:
507
+ eq_where = col == condition
500
508
  if neq:
501
509
  eq_where = not_(eq_where)
502
510
  return eq_where
@@ -2,10 +2,11 @@ __all__ = ["CrudRepository"]
2
2
 
3
3
  import logging
4
4
  import math
5
+ from functools import lru_cache
5
6
  from typing import Any, Type, Sequence, Iterable, Optional, Mapping
6
7
 
7
- from pydantic import BaseModel
8
- from sqlalchemy import select, delete, update, insert, tuple_, Row, RowMapping, ColumnElement, Select, Insert, Update, Delete
8
+ from pydantic import BaseModel, TypeAdapter
9
+ from sqlalchemy import select, delete, update, insert, tuple_, Row, RowMapping, ColumnElement, Select, Insert, Update, Delete, func, ARRAY
9
10
  from sqlalchemy.engine.base import Engine
10
11
  from sqlalchemy.exc import DataError, IntegrityError, NoResultFound, OperationalError
11
12
  from sqlalchemy.inspection import inspect
@@ -19,6 +20,18 @@ from ...interface.base.declarative_base import Base
19
20
  from ...paginator_dto import PaginatorResponseModel
20
21
 
21
22
 
23
+ @lru_cache(maxsize=None)
24
+ def _list_adapter(model: Type[BaseModel]) -> TypeAdapter:
25
+ """Restituisce (cachato per classe) un ``TypeAdapter`` per ``list[model]``.
26
+
27
+ Validare l'intera lista con un solo ``validate_python`` fa iterare il loop
28
+ dentro pydantic-core (Rust) invece che con una list-comprehension che chiama
29
+ ``model_validate`` riga per riga in Python. Il ``TypeAdapter`` è costruito una
30
+ sola volta per modello e riusato (anche per i ``parsing_model`` alternativi).
31
+ """
32
+ return TypeAdapter(list[model])
33
+
34
+
22
35
  class CrudRepository[Entity: Base | Type[Base]]:
23
36
  """Repository ORM generico con operazioni CRUD complete su una singola entità SQLAlchemy.
24
37
 
@@ -41,13 +54,27 @@ class CrudRepository[Entity: Base | Type[Base]]:
41
54
  >>> repo.create()
42
55
  >>> users = repo.find(active=True, limit=10)
43
56
  """
44
- __slots__ = "model", "entity", "engine", "columns", "stmt_manager", "relationship_attributes"
57
+ __slots__ = "model", "entity", "engine", "columns", "stmt_manager", "_relationship_attributes"
45
58
 
46
59
  model: Type[BaseModel] | BaseModel
47
60
  entity: Entity
48
61
  engine: Type[Engine] | Engine
49
62
  stmt_manager: StatementManager | Type[StatementManager]
50
63
 
64
+ @property
65
+ def relationship_attributes(self) -> set[str]:
66
+ """Nomi degli attributi di relazione dell'entità (calcolati una volta, poi cachati).
67
+
68
+ Lazy di proposito: accedere a ``__mapper__.relationships`` a import-time
69
+ (quando le repository vengono istanziate) forzerebbe la configurazione dei
70
+ mapper prima che tutte le entità siano importate, rompendo le relationship
71
+ con forward-ref non ancora risolvibili. Al primo accesso (a runtime, in
72
+ ``_select``) i mapper sono già configurati.
73
+ """
74
+ if self._relationship_attributes is None:
75
+ self._relationship_attributes = set(self.entity.__mapper__.relationships.keys())
76
+ return self._relationship_attributes
77
+
51
78
  @property
52
79
  def open_session(self):
53
80
  """Apre e restituisce una nuova sessione SQLAlchemy.
@@ -74,6 +101,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
74
101
  self.model = model
75
102
  self.engine = engine
76
103
  self.columns = self.entity.columns()
104
+ self._relationship_attributes = None # popolato lazy dalla property relationship_attributes
77
105
 
78
106
  base_handler = handler_class(self.columns) if handler_class else BaseHandler(self.columns)
79
107
 
@@ -181,8 +209,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
181
209
  try:
182
210
  stmt = select(entity)
183
211
  if columns:
184
- relationship_attributes = set(entity.__mapper__.relationships.keys())
185
- entity_columns = {column for column in columns if column not in relationship_attributes}
212
+ entity_columns = {column for column in columns if column not in self.relationship_attributes}
186
213
  stmt = stmt.options(load_only(*[getattr(self.entity, column) for column in entity_columns]))
187
214
  if exclude_columns:
188
215
  stmt = stmt.options(defer(*[getattr(self.entity, column) for column in exclude_columns]))
@@ -222,7 +249,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
222
249
  return_model = parsing_model or self.model
223
250
  if commit:
224
251
  self._flush(session=session)
225
- models = [return_model.model_validate(entity) for entity in entities]
252
+ models = _list_adapter(return_model).validate_python(entities, from_attributes=True)
226
253
  if commit:
227
254
  self.commit(session=session)
228
255
  session.close()
@@ -607,7 +634,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
607
634
  session = self.open_session if session is None else session
608
635
  paginated = self._paging_find(session=session, where=where, columns=columns, exclude_columns=exclude_columns, limit=limit, page=page, order_by=order_by, asc=asc, **kwhere)
609
636
  if not keep_open:
610
- paginated.data = [parsing_model.model_validate(entity) for entity in paginated.data] if parsing_model else [self.model.model_validate(entity) for entity in paginated.data]
637
+ paginated.data = _list_adapter(parsing_model or self.model).validate_python(paginated.data, from_attributes=True)
611
638
  session.close()
612
639
  return paginated
613
640
 
@@ -619,9 +646,9 @@ class CrudRepository[Entity: Base | Type[Base]]:
619
646
 
620
647
  row_numbers = self._preconf_count(session=session, where=stmt.whereclause)
621
648
 
649
+ order_by = order_by or self.entity.primary_key()[0].name
622
650
  stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit, page=page)
623
- if order_by:
624
- stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
651
+ stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
625
652
 
626
653
  entities = self.execute(session, stmt)
627
654
  return self.parse_pagination(limit=limit, page=page, order_by=order_by, count=row_numbers, asc=asc, data=entities)
@@ -650,6 +677,47 @@ class CrudRepository[Entity: Base | Type[Base]]:
650
677
 
651
678
  return PaginatorResponseModel(limit=limit, page=page, order_by=order_by, asc=asc, tot_pages=tot_pages, data=data)
652
679
 
680
+ def distinct_pairs(self, key: str, value: str, session: Session = None, where: dict[str, Any] = None, **kwhere) -> list[Row] | None:
681
+ """Coppie distinte ``(key, value)`` per i record che soddisfano ``where``.
682
+
683
+ Calcola i valori distinti lato DB con ``SELECT DISTINCT`` invece di caricare
684
+ tutte le righe e dedupplicare in Python (usato da ``BaseService.company_filters``
685
+ per popolare le opzioni dei filtri). Per le colonne ``ARRAY`` srotola in
686
+ parallelo chiave ed etichetta con ``unnest`` (lateral implicito).
687
+
688
+ Args:
689
+ key (str): Colonna chiave (es. ``"id_operatore"``).
690
+ value (str): Colonna etichetta (es. ``"operatore"``).
691
+ session (Session, optional): Sessione esterna; se ``None`` ne apre/chiude una.
692
+ where (dict, optional): Filtri (es. ``{"id_azienda": ...}``).
693
+ **kwhere: Filtri aggiuntivi, usati se ``where`` è ``None``.
694
+
695
+ Returns:
696
+ list[Row]: Coppie ``(key, value)`` distinte.
697
+ """
698
+ if where is None:
699
+ where = kwhere if kwhere else {}
700
+
701
+ keep_open = session is not None
702
+ session = self.open_session if session is None else session
703
+
704
+ key_col, value_col = self.columns[key], self.columns[value]
705
+ if isinstance(key_col.type, ARRAY):
706
+ pairs = func.unnest(key_col, value_col).table_valued("k", "v")
707
+ stmt = select(pairs.c.k, pairs.c.v).select_from(self.entity, pairs).distinct()
708
+ else:
709
+ stmt = select(key_col, value_col).distinct()
710
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
711
+
712
+ try:
713
+ rows = session.execute(stmt).all()
714
+ except Exception as e:
715
+ self._handle_exception(session, e)
716
+ else:
717
+ if not keep_open:
718
+ session.close()
719
+ return rows
720
+
653
721
  def execute(self, session: Session, stmt: Select | Insert | Update | Delete, values: Sequence[Mapping[str, Any]] | Mapping[str, Any] = None):
654
722
  """Esegue uno statement SQLAlchemy e restituisce i risultati scalari.
655
723
 
@@ -2,7 +2,7 @@ __all__ = ["BaseHandler"]
2
2
 
3
3
  from typing import Any
4
4
 
5
- from sqlalchemy import Select, Update, Delete, Column, not_
5
+ from sqlalchemy import Select, Update, Delete, Column, not_, ARRAY, type_coerce
6
6
  from sqlalchemy.sql.base import ReadOnlyColumnCollection
7
7
 
8
8
 
@@ -30,7 +30,7 @@ class BaseHandler:
30
30
  ottenute da ``Entity.__table__.c``.
31
31
  """
32
32
  self.columns = columns
33
- self.array_columns = {column.name for column in columns if str(column.type) == "ARRAY"}
33
+ self.array_columns = {column.name for column in columns if isinstance(column.type, ARRAY)}
34
34
 
35
35
  def handle(self, stmt: Select | Update | Delete, neq: bool = False, where: dict[str, Any] = None):
36
36
  """Applica i filtri WHERE allo statement per tutte le coppie chiave-valore in ``where``.
@@ -54,8 +54,9 @@ class BaseHandler:
54
54
  """Costruisce la singola condizione WHERE per una colonna.
55
55
 
56
56
  Logica applicata:
57
+ - Colonna ``ARRAY`` + ``list`` → ``col && ARRAY[values]`` (overlap)
58
+ - Colonna ``ARRAY`` + scalare → ``value = ANY(col)``
57
59
  - ``list`` → ``col IN (values)``
58
- - Tipo ``ARRAY`` → ``value = ANY(col)``
59
60
  - Scalare → ``col == value``
60
61
 
61
62
  Se ``neq=True``, la condizione viene negata con ``NOT``.
@@ -68,9 +69,12 @@ class BaseHandler:
68
69
  Returns:
69
70
  ColumnElement: Espressione booleana SQLAlchemy.
70
71
  """
71
- if isinstance(condition, list):
72
+ is_array = column.name in self.array_columns
73
+ if is_array and isinstance(condition, list):
74
+ eq_where = column.op("&&")(type_coerce(condition, column.type))
75
+ elif isinstance(condition, list):
72
76
  eq_where = column.in_(condition)
73
- elif column.name in self.array_columns:
77
+ elif is_array:
74
78
  eq_where = condition == column.any_()
75
79
  else:
76
80
  eq_where = column == condition
@@ -0,0 +1,90 @@
1
+ __all__ = ["OrHandler"]
2
+
3
+ from typing import Any
4
+
5
+ from sqlalchemy import Column, Select, Update, Delete, or_
6
+ from sqlalchemy.sql.base import ReadOnlyColumnCollection
7
+
8
+ from .base_handler import BaseHandler
9
+
10
+
11
+ class OrHandler(BaseHandler):
12
+ """Handler per ricerche case-insensitive (ILIKE) su più colonne tramite un alias.
13
+
14
+ Permette di esporre un singolo parametro di ricerca testuale (es. ``"search"``)
15
+ che viene applicato come ``ILIKE '%value%'`` su un insieme di colonne con ``OR``.
16
+
17
+ Attributes:
18
+ or_aliases (list[str]): Chiavi nel dizionario ``where`` che attivano
19
+ la ricerca ILIKE (es. ``["search", "q"]``).
20
+ or_attributes (list[str]): Nomi delle colonne su cui applicare ILIKE
21
+ (es. ``["name", "surname", "email"]``).
22
+
23
+ Example:
24
+ >>> handler = OrHandler(
25
+ ... columns=User.columns(),
26
+ ... or_aliases=["search"],
27
+ ... or_attributes=["name", "surname"]
28
+ ... )
29
+ >>> repo.find(where={"search": "mario"})
30
+ # WHERE name ILIKE '%mario%' OR surname ILIKE '%mario%'
31
+ """
32
+ __slots__ = "or_aliases", "or_attributes"
33
+ or_aliases: list[str]
34
+ or_attributes: list[str]
35
+
36
+ def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]], or_aliases: list[str] = None, or_attributes: list[str] = None):
37
+ """Inizializza l'handler validando che le colonne ILIKE esistano sull'entità.
38
+
39
+ Args:
40
+ columns (ReadOnlyColumnCollection): Colonne della tabella.
41
+ or_aliases (list[str], optional): Chiavi alias che attivano la ricerca.
42
+ Default: lista vuota.
43
+ or_attributes (list[str], optional): Colonne su cui applicare ILIKE.
44
+ Default: lista vuota.
45
+
46
+ Raises:
47
+ ValueError: Se uno degli ``or_attributes`` non è presente tra le colonne.
48
+ """
49
+ self.columns = columns
50
+ self.or_aliases = or_aliases if or_aliases is not None else []
51
+ self.or_attributes = or_attributes if or_attributes is not None else []
52
+
53
+ for or_attribute in self.or_attributes:
54
+ if or_attribute not in self.columns:
55
+ raise ValueError(f"{or_attribute} not in columns")
56
+
57
+ def handle(self, stmt: Select | Update | Delete, where: dict[str, Any] = None, **kwargs):
58
+ """Applica i filtri ILIKE allo statement per ogni alias trovato in ``where``.
59
+
60
+ Per ogni alias presente in ``or_aliases`` e trovato in ``where``,
61
+ estrae il valore, lo rimuove dal dizionario e aggiunge la condizione ILIKE
62
+ su tutte le colonne in ``or_attributes`` con ``OR``.
63
+
64
+ Args:
65
+ stmt (Select | Update | Delete): Statement su cui applicare i filtri.
66
+ where (dict[str, Any]): Dizionario dei filtri. Gli alias ILIKE vengono
67
+ estratti e rimossi in-place.
68
+ **kwargs: Ignorati (compatibilità con l'interfaccia base).
69
+
70
+ Returns:
71
+ Select | Update | Delete: Statement con le condizioni ILIKE aggiunte.
72
+ """
73
+ for alias in self.or_aliases:
74
+ condition = where.pop(alias, None)
75
+ if condition:
76
+ stmt = stmt.where(self._handle(alias, condition))
77
+ return stmt
78
+
79
+ def _handle(self, alias: str, condition: str, neq: bool = False):
80
+ """Costruisce la condizione ILIKE con OR su tutte le colonne configurate.
81
+
82
+ Args:
83
+ alias (str): Alias attivato (usato per identificare la ricerca, non la colonna).
84
+ condition (str): Stringa da cercare (viene wrappata con ``%``).
85
+ neq (bool): Non utilizzato in questa implementazione.
86
+
87
+ Returns:
88
+ ColumnElement: Espressione ``OR(col1 ILIKE '%val%', col2 ILIKE '%val%', ...)``.
89
+ """
90
+ return or_(*[self.columns[col] == condition for col in self.or_attributes])
@@ -80,8 +80,18 @@ class ViewRepository[Entity: Base | Type[Base]](CrudRepository[Entity]):
80
80
  if self.materialized:
81
81
  unique_id_index = f"create unique index on {self.view_name} ({self.entity.primary_key()[0].name});"
82
82
  stmts.append(unique_id_index)
83
- if visualizer_db_role and any([isinstance(col.type, Geometry) for col in self.entity.columns()]):
84
- grant_visualized = f"grant select on table {self.view_name} to {visualizer_db_role}"
83
+
84
+ has_geom = False
85
+ for col in self.entity.columns():
86
+ if isinstance(col.type, Geometry):
87
+ has_geom = True
88
+ # l'indice GIST si può creare solo su materialized view: su una view normale
89
+ # CREATE INDEX fallisce e farebbe abortire la transazione (view non creata)
90
+ if self.materialized:
91
+ stmts.append(f"create index on {self.view_name} using gist ({col.name});")
92
+
93
+ if visualizer_db_role and has_geom:
94
+ grant_visualized = f"grant select on table {self.view_name} to {visualizer_db_role};"
85
95
  stmts.append(grant_visualized)
86
96
 
87
97
  with self.open_session as session:
@@ -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.6.0"
7
+ version = "0.7.0"
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.6.0
3
+ Version: 0.7.0
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
@@ -26,6 +26,7 @@ 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
28
  general/interface/repository/handler/json_ilike_handler.py
29
+ general/interface/repository/handler/or_handler.py
29
30
  general/interface/repository/handler/statement_manager.py
30
31
  python_general_be_lib.egg-info/PKG-INFO
31
32
  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.6.0",
5
+ version="0.7.0",
6
6
  packages=find_packages(include=["general*"]),
7
7
  python_requires=">=3.12",
8
8
  install_requires=[