python-general-be-lib 0.6.0__py3-none-any.whl → 0.7.0__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/base/base_model.py +13 -4
- general/interface/metadata/crud_metadata.py +10 -2
- general/interface/repository/crud_repository.py +77 -9
- general/interface/repository/handler/base_handler.py +9 -5
- general/interface/repository/handler/or_handler.py +90 -0
- general/interface/repository/view_repository.py +12 -2
- {python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/METADATA +1 -1
- {python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/RECORD +11 -10
- {python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/WHEEL +0 -0
- {python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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", "
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-general-be-lib
|
|
3
|
-
Version: 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
|
|
@@ -7,25 +7,26 @@ general/exception/access_exceptions.py,sha256=8AV4_gybcdA7mTpA2XiZdl9rRGx08QCjHp
|
|
|
7
7
|
general/exception/crud_exceptions.py,sha256=c-VlatTPjEp7FWzA7rDByqfMqsEOworJqqfUJ3s0Z2w,1101
|
|
8
8
|
general/exception/exception_interface.py,sha256=T4bq8jwZ1YFpGlP60TxbvhkcyIrfsjcw5I6Xlxvzjig,352
|
|
9
9
|
general/interface/base/__init__.py,sha256=SDlTi28M8rzJ9yiKefo5l7V3K109LnnBHW-SuZ-5JtM,58
|
|
10
|
-
general/interface/base/base_model.py,sha256=
|
|
10
|
+
general/interface/base/base_model.py,sha256=Tsy70uC6HHe194ZrQaX7pXROqobOkZIOB63VdagmFHE,2561
|
|
11
11
|
general/interface/base/declarative_base.py,sha256=5hw2VpJHHebjixuI3d4wpkyAentGBGN54QXcniaoJ-A,5236
|
|
12
12
|
general/interface/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
general/interface/metadata/crud_metadata.py,sha256=
|
|
13
|
+
general/interface/metadata/crud_metadata.py,sha256=wBBFfwq7xUu1sDlAg14qTAToYtSf6omcEOu2oLn-j5w,22672
|
|
14
14
|
general/interface/metadata/geom_metadata.py,sha256=kc6jkWis-WPdWAP-hN6U97ENVjNjny5NE2j-llwZYOQ,11998
|
|
15
15
|
general/interface/repository/__init__.py,sha256=EcRukwf61Ua-9-JfM_ingVv1wXNRV8HBrrWwjj8m23w,136
|
|
16
|
-
general/interface/repository/crud_repository.py,sha256=
|
|
16
|
+
general/interface/repository/crud_repository.py,sha256=coAL3ubvlAFEmwnkI5fOysbHdHBGYB6sRGgJF8r4TZI,35311
|
|
17
17
|
general/interface/repository/geometry_repository.py,sha256=ULU-FnDATojHLDrObLPyAA3KErgX_tyYEeTU8h2O4Hs,11989
|
|
18
18
|
general/interface/repository/many_to_many_repository.py,sha256=iEhWyD8nPZSAPL8Z2wXrK-aRBnoh3evaSN-voaf0EP0,8662
|
|
19
|
-
general/interface/repository/view_repository.py,sha256=
|
|
19
|
+
general/interface/repository/view_repository.py,sha256=Rr9toRVop0sLpPwo9QvBDJgLQnB0ca--mp-hiwjGrzE,5835
|
|
20
20
|
general/interface/repository/handler/__init__.py,sha256=J-fc-5Dl_C821oy_EVRdyU-L2iXiNFriYJDbK4ODIMM,184
|
|
21
|
-
general/interface/repository/handler/base_handler.py,sha256=
|
|
21
|
+
general/interface/repository/handler/base_handler.py,sha256=jedf7ACFPlYKHC9U9ZpVN4vCC0EXO3s4Rtyn15kKi_w,3558
|
|
22
22
|
general/interface/repository/handler/ilike_handler.py,sha256=_HHTvj6Tm1L-WvUy1S9faRChoRZni11i9mv76I4Xak0,3919
|
|
23
23
|
general/interface/repository/handler/interval_handler.py,sha256=XxMQr6UuWnoCtbOOO7l80_Yja_aPKYtHhDfhFCRMaKg,4135
|
|
24
24
|
general/interface/repository/handler/json_handler.py,sha256=7bS59seB-It5KHsWXTbHGsEvbQZ6lYyvX-d29BcRLVE,2956
|
|
25
25
|
general/interface/repository/handler/json_ilike_handler.py,sha256=yDuKER3yqtnHpVWndF8PVEaqDThcq1EhZzLaqxbQ0bU,4491
|
|
26
|
+
general/interface/repository/handler/or_handler.py,sha256=v97QMQwqrhTpfpZQWfvL6HC4lM9kjMEmY0-Sps968oI,3818
|
|
26
27
|
general/interface/repository/handler/statement_manager.py,sha256=sGDMU4fPkF0_9-SbDmAw8Vpel1Z7eImBs8fHO-5Lr_0,4429
|
|
27
|
-
python_general_be_lib-0.
|
|
28
|
-
python_general_be_lib-0.
|
|
29
|
-
python_general_be_lib-0.
|
|
30
|
-
python_general_be_lib-0.
|
|
31
|
-
python_general_be_lib-0.
|
|
28
|
+
python_general_be_lib-0.7.0.dist-info/licenses/LICENSE,sha256=iUaO1XZyB9P3Tmog0OILuTisP6vXGe3QKz-4yRTxOFk,1069
|
|
29
|
+
python_general_be_lib-0.7.0.dist-info/METADATA,sha256=MYAC_VO9xBrhFGETHyaU6d70Wqy0tQYypXp_gDIDdA0,1384
|
|
30
|
+
python_general_be_lib-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
python_general_be_lib-0.7.0.dist-info/top_level.txt,sha256=tTZePW8_CNUqSgKFd2SEH72ZbnhS0OYjRsgcv0ikSFY,8
|
|
32
|
+
python_general_be_lib-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
{python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{python_general_be_lib-0.6.0.dist-info → python_general_be_lib-0.7.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|