python-general-be-lib 0.5.4__tar.gz → 0.6.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.
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/PKG-INFO +1 -1
- python_general_be_lib-0.6.0/general/interface/metadata/crud_metadata.py +502 -0
- python_general_be_lib-0.6.0/general/interface/metadata/geom_metadata.py +211 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/crud_repository.py +85 -24
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/geometry_repository.py +23 -15
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/base_handler.py +13 -2
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/pyproject.toml +1 -1
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/python_general_be_lib.egg-info/PKG-INFO +1 -1
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/setup.py +1 -1
- python_general_be_lib-0.5.4/general/interface/metadata/crud_metadata.py +0 -179
- python_general_be_lib-0.5.4/general/interface/metadata/geom_metadata.py +0 -88
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/LICENSE +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/README.md +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/exception/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/exception/access_exceptions.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/exception/crud_exceptions.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/exception/exception_interface.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/base/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/base/base_model.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/base/declarative_base.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/metadata/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/__init__.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/ilike_handler.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/interval_handler.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/json_handler.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/json_ilike_handler.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/handler/statement_manager.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/many_to_many_repository.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/interface/repository/view_repository.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/logger.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/general/paginator_dto.py +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/python_general_be_lib.egg-info/SOURCES.txt +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/python_general_be_lib.egg-info/dependency_links.txt +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/python_general_be_lib.egg-info/requires.txt +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.0}/python_general_be_lib.egg-info/top_level.txt +0 -0
- {python_general_be_lib-0.5.4 → python_general_be_lib-0.6.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.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
__all__ = ["CrudMetadata"]
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, Any, Type, Iterable, Sequence
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from sqlalchemy import MetaData, Engine, Column, Table, inspect, Connection, select, RowMapping, CursorResult, not_
|
|
8
|
+
from sqlalchemy.exc import NoSuchTableError
|
|
9
|
+
|
|
10
|
+
from ...exception.crud_exceptions import HasNoAttributeException, InternalServerException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CrudMetadata:
|
|
14
|
+
"""Repository CRUD basato su SQLAlchemy Core e ``MetaData``, senza ORM.
|
|
15
|
+
|
|
16
|
+
Alternativa a ``CrudRepository`` che opera su tabelle riflesse (``Table``)
|
|
17
|
+
e ``Connection`` invece che su entità mappate e ``Session``. Utile per
|
|
18
|
+
lavorare con tabelle prive di un modello ORM: lo schema viene riflesso a
|
|
19
|
+
runtime e le tabelle sono risolte per nome.
|
|
20
|
+
|
|
21
|
+
Le operazioni accettano una connessione esterna opzionale: se fornita
|
|
22
|
+
(``keep_open=True``) i risultati grezzi vengono restituiti senza commit né
|
|
23
|
+
chiusura; altrimenti la connessione viene aperta, committata e chiusa
|
|
24
|
+
internamente.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
schema (str | None): Nome dello schema di default per le tabelle.
|
|
28
|
+
metadata (MetaData): Contenitore SQLAlchemy delle tabelle riflesse/definite.
|
|
29
|
+
engine (Engine): Istanza SQLAlchemy Engine connessa al database.
|
|
30
|
+
engine_inspection (Inspector): Inspector usato per le introspezioni
|
|
31
|
+
(es. presenza tabelle).
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> meta = CrudMetadata(engine=engine, schema_name="public")
|
|
35
|
+
>>> rows = meta.find(from_table="users", where={"active": True}, limit=10)
|
|
36
|
+
"""
|
|
37
|
+
__slots__ = "schema", "metadata", "engine", "engine_inspection"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def open_connection(self):
|
|
41
|
+
"""Apre e restituisce una nuova connessione all'engine.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Connection: Nuova connessione associata all'engine configurato.
|
|
45
|
+
"""
|
|
46
|
+
return self.engine.connect()
|
|
47
|
+
|
|
48
|
+
def __init__(self, engine: Engine, schema_name: str = None, reflect: bool = True):
|
|
49
|
+
"""Inizializza il repository con l'engine e (opzionalmente) riflette lo schema.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
engine (Engine): Istanza SQLAlchemy Engine connessa al database.
|
|
53
|
+
schema_name (str, optional): Nome dello schema di default a cui
|
|
54
|
+
appartengono le tabelle. Se ``None``, viene usato lo schema di
|
|
55
|
+
default della connessione.
|
|
56
|
+
reflect (bool): Se ``True``, riflette tutte le tabelle dello schema
|
|
57
|
+
indicato alla costruzione (operazione potenzialmente costosa su
|
|
58
|
+
schemi grandi). Default: ``True``.
|
|
59
|
+
"""
|
|
60
|
+
self.schema = schema_name
|
|
61
|
+
self.metadata = MetaData(schema_name) if schema_name else MetaData()
|
|
62
|
+
self.engine = engine
|
|
63
|
+
self.engine_inspection = inspect(self.engine)
|
|
64
|
+
if reflect:
|
|
65
|
+
self.metadata.reflect(bind=self.engine, schema=schema_name)
|
|
66
|
+
|
|
67
|
+
# ---------------------------
|
|
68
|
+
|
|
69
|
+
def _handle_exception(self, connection: Connection, e: Exception, to_raise: bool = True):
|
|
70
|
+
"""Chiude la connessione, logga l'errore ed eventualmente lo rilancia.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
connection (Connection): Connessione da chiudere.
|
|
74
|
+
e (Exception): Eccezione catturata.
|
|
75
|
+
to_raise (bool): Se ``True``, rilancia l'eccezione dopo il logging.
|
|
76
|
+
Default: ``True``.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
Exception: L'eccezione originale, se ``to_raise=True``.
|
|
80
|
+
"""
|
|
81
|
+
connection.close()
|
|
82
|
+
logging.error(e)
|
|
83
|
+
if to_raise:
|
|
84
|
+
raise e
|
|
85
|
+
|
|
86
|
+
def commit(self, connection: Connection):
|
|
87
|
+
"""Esegue il commit sulla connessione, gestendo eventuali eccezioni.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
connection (Connection): Connessione su cui eseguire il commit.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
Exception: In caso di errore durante il commit (via ``_handle_exception``).
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
connection.commit()
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self._handle_exception(connection, e)
|
|
99
|
+
|
|
100
|
+
def has_columns(self, *columns: Iterable[str], table: Table):
|
|
101
|
+
"""Verifica che tutte le colonne specificate esistano sulla tabella.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
*columns (str): Nomi delle colonne da verificare.
|
|
105
|
+
table (Table): Tabella su cui effettuare la verifica.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
bool: ``True`` se tutte le colonne sono presenti, ``False`` altrimenti.
|
|
109
|
+
"""
|
|
110
|
+
return all([column in [c.key for c in table.c] for column in columns])
|
|
111
|
+
|
|
112
|
+
def _select(self, table: Table, columns: list[str] = None):
|
|
113
|
+
"""Costruisce lo statement SELECT sulla tabella.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
table (Table): Tabella su cui costruire il SELECT.
|
|
117
|
+
columns (list[str], optional): Colonne da selezionare. Se ``None``,
|
|
118
|
+
seleziona tutte le colonne della tabella.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Select: Statement SELECT configurato.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
HasNoAttributeException: Se una colonna specificata non esiste sulla tabella.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
stmt = table.select() if not columns else select(*[table.c[column] for column in columns])
|
|
128
|
+
except:
|
|
129
|
+
raise HasNoAttributeException()
|
|
130
|
+
return stmt
|
|
131
|
+
|
|
132
|
+
def _return(self, connection: Connection, result: CursorResult | RowMapping | Sequence[RowMapping] | Sequence[dict] = None, keep_open: bool = False, commit: bool = True,
|
|
133
|
+
parsing_model: Type[BaseModel] | BaseModel = None):
|
|
134
|
+
"""Serializza il risultato e chiude la connessione se necessario.
|
|
135
|
+
|
|
136
|
+
Se ``keep_open=True`` (connessione esterna), restituisce il risultato
|
|
137
|
+
grezzo senza chiudere la connessione. Altrimenti esegue il commit (se
|
|
138
|
+
richiesto), serializza con il ``parsing_model`` e chiude la connessione.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
connection (Connection): Connessione corrente.
|
|
142
|
+
result (CursorResult | RowMapping | Sequence): Righe risultanti
|
|
143
|
+
(tipicamente mapping ``{colonna: valore}``).
|
|
144
|
+
keep_open (bool): Se ``True``, non chiude la connessione e non serializza.
|
|
145
|
+
commit (bool): Se ``True`` e ``keep_open=False``, esegue il commit.
|
|
146
|
+
parsing_model (Type[BaseModel], optional): Modello Pydantic con cui
|
|
147
|
+
istanziare ogni riga (``parsing_model(**row)``). Se ``None``,
|
|
148
|
+
restituisce le righe grezze.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
list[BaseModel] | Sequence: Modelli serializzati o righe grezze.
|
|
152
|
+
"""
|
|
153
|
+
if keep_open:
|
|
154
|
+
return result
|
|
155
|
+
else:
|
|
156
|
+
if commit:
|
|
157
|
+
self.commit(connection=connection)
|
|
158
|
+
result = result if parsing_model is None else [parsing_model(**r) for r in result]
|
|
159
|
+
connection.close()
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def create_table(self, name: str, columns: Iterable[Column], **kwargs):
|
|
163
|
+
"""Crea una nuova tabella sul database.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
name (str): Nome della tabella da creare.
|
|
167
|
+
columns (Iterable[Column]): Colonne che compongono la tabella.
|
|
168
|
+
**kwargs: Argomenti aggiuntivi passati al costruttore ``Table``
|
|
169
|
+
(es. ``schema``, vincoli, ``Index``).
|
|
170
|
+
"""
|
|
171
|
+
table = Table(name=name, metadata=self.metadata, *columns, **kwargs)
|
|
172
|
+
table.create(self.engine)
|
|
173
|
+
|
|
174
|
+
def drop_table(self, name: str):
|
|
175
|
+
"""Elimina una tabella dal database e dal ``MetaData`` locale.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name (str): Nome della tabella da eliminare.
|
|
179
|
+
"""
|
|
180
|
+
table = self.get_table(name=name)
|
|
181
|
+
table.drop(self.engine)
|
|
182
|
+
self.metadata.remove(table=table)
|
|
183
|
+
|
|
184
|
+
def find(self, from_table: str, connection: Connection = None, where: dict[str, Any] = None, columns: list[str] = None, limit: int = 0, parsing_model: Type[BaseModel] = None, **kwhere):
|
|
185
|
+
"""Cerca le righe della tabella che corrispondono ai criteri specificati.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
from_table (str): Nome della tabella su cui eseguire la ricerca.
|
|
189
|
+
connection (Connection, optional): Connessione esterna. Se ``None``,
|
|
190
|
+
ne viene aperta una nuova che viene chiusa al termine.
|
|
191
|
+
where (dict[str, Any], optional): Filtri come dizionario
|
|
192
|
+
``{colonna: valore}``. Ha precedenza su ``**kwhere``.
|
|
193
|
+
columns (list[str], optional): Colonne da includere nel risultato.
|
|
194
|
+
limit (int): Numero massimo di righe. 0 = nessun limite. Default: 0.
|
|
195
|
+
parsing_model (Type[BaseModel], optional): Modello Pydantic per la
|
|
196
|
+
serializzazione delle righe.
|
|
197
|
+
**kwhere: Filtri aggiuntivi come keyword arguments.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
list[BaseModel] | Sequence[RowMapping]: Righe serializzate o mapping grezzi.
|
|
201
|
+
|
|
202
|
+
Example:
|
|
203
|
+
>>> meta.find(from_table="users", name="Mario", limit=10)
|
|
204
|
+
"""
|
|
205
|
+
keep_open = False if connection is None else True
|
|
206
|
+
connection = self.open_connection if connection is None else connection
|
|
207
|
+
table = self.get_table(name=from_table)
|
|
208
|
+
result = self._find(table=table, connection=connection, where=where, columns=columns, limit=limit, **kwhere)
|
|
209
|
+
return self._return(connection=connection, result=result, keep_open=keep_open, commit=False, parsing_model=parsing_model)
|
|
210
|
+
|
|
211
|
+
def _find(self, table: Table, connection: Connection, where: dict[str, Any] = None, columns: list[str] = None, limit: int = 0, **kwhere):
|
|
212
|
+
"""Costruisce ed esegue lo statement SELECT con filtri e limit.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
table (Table): Tabella su cui eseguire la SELECT.
|
|
216
|
+
connection (Connection): Connessione corrente.
|
|
217
|
+
where (dict[str, Any], optional): Filtri ``{colonna: valore}``.
|
|
218
|
+
Se ``None``, usa ``**kwhere``.
|
|
219
|
+
columns (list[str], optional): Colonne da selezionare.
|
|
220
|
+
limit (int): Numero massimo di righe. Default: 0 (nessun limite).
|
|
221
|
+
**kwhere: Filtri aggiuntivi come keyword arguments.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Sequence[RowMapping]: Righe risultanti come mapping.
|
|
225
|
+
"""
|
|
226
|
+
if where is None:
|
|
227
|
+
where = kwhere if kwhere else {}
|
|
228
|
+
stmt = self._select(table=table, columns=columns)
|
|
229
|
+
stmt = stmt.where(*[self._eq_where(table.c[column], condition) for column, condition in where.items()])
|
|
230
|
+
stmt = self._add_limit_offset_condition(stmt, limit)
|
|
231
|
+
return self.execute(connection=connection, stmt=stmt, mapping=True)
|
|
232
|
+
|
|
233
|
+
def insert(self, from_table: str, connection: Connection = None, models: list[Type[BaseModel] | BaseModel] = None, values: list[dict[str, Any]] = None, returning: bool = True,
|
|
234
|
+
parsing_model: Type[BaseModel] | BaseModel = None):
|
|
235
|
+
"""Inserisce righe nella tabella a partire da modelli e/o dizionari di valori.
|
|
236
|
+
|
|
237
|
+
Le righe possono essere fornite come modelli Pydantic (``models``, da cui
|
|
238
|
+
si estraggono i valori con ``model_dump(exclude_none=True)``) e/o come
|
|
239
|
+
dizionari grezzi (``values``). Le due fonti vengono unite.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
from_table (str): Nome della tabella su cui inserire.
|
|
243
|
+
connection (Connection, optional): Connessione esterna. Se ``None``,
|
|
244
|
+
ne viene aperta una nuova con commit automatico.
|
|
245
|
+
models (list[BaseModel], optional): Modelli Pydantic da inserire.
|
|
246
|
+
values (list[dict[str, Any]], optional): Righe come dizionari grezzi.
|
|
247
|
+
returning (bool): Se ``True``, usa ``RETURNING`` per recuperare le righe
|
|
248
|
+
inserite. Default: ``True``.
|
|
249
|
+
parsing_model (Type[BaseModel], optional): Modello per la serializzazione
|
|
250
|
+
del risultato.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
list[BaseModel] | Sequence[RowMapping]: Righe inserite (serializzate o grezze).
|
|
254
|
+
|
|
255
|
+
Note:
|
|
256
|
+
Se ``values`` è fornito, viene esteso in-place con i valori derivati
|
|
257
|
+
da ``models``.
|
|
258
|
+
"""
|
|
259
|
+
keep_open = False if connection is None else True
|
|
260
|
+
connection = self.open_connection if connection is None else connection
|
|
261
|
+
table = self.get_table(name=from_table)
|
|
262
|
+
values_condition = values if values else []
|
|
263
|
+
values_condition.extend([model.model_dump(exclude_none=True) for model in models]) if models else values_condition
|
|
264
|
+
result = self._insert(table=table, connection=connection, values=values_condition, returning=returning)
|
|
265
|
+
return self._return(connection=connection, result=result, keep_open=keep_open, commit=True, parsing_model=parsing_model)
|
|
266
|
+
|
|
267
|
+
def _insert(self, table: Table, connection: Connection, values: list[dict[str, Any]] = None, returning: bool = True):
|
|
268
|
+
"""Costruisce ed esegue lo statement INSERT con i valori forniti.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
table (Table): Tabella su cui inserire.
|
|
272
|
+
connection (Connection): Connessione corrente.
|
|
273
|
+
values (list[dict[str, Any]], optional): Righe da inserire. Se vuoto,
|
|
274
|
+
non esegue nessuna operazione.
|
|
275
|
+
returning (bool): Se ``True``, usa ``RETURNING``. Default: ``True``.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Sequence[RowMapping] | list: Righe inserite come mapping, o lista vuota
|
|
279
|
+
se non ci sono valori.
|
|
280
|
+
"""
|
|
281
|
+
if values:
|
|
282
|
+
stmt = table.insert().values(values)
|
|
283
|
+
stmt = stmt if not returning else stmt.returning()
|
|
284
|
+
return self.execute(connection=connection, stmt=stmt, mapping=True)
|
|
285
|
+
else:
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
def update(self, from_table: str, connection: Connection = None, where: dict[str, Any] = None, returning: bool = True, parsing_model: Type[BaseModel] | BaseModel = None, **kwargs):
|
|
289
|
+
"""Aggiorna le righe che soddisfano i criteri ``where`` con i valori in ``**kwargs``.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
from_table (str): Nome della tabella da aggiornare.
|
|
293
|
+
connection (Connection, optional): Connessione esterna. Se ``None``,
|
|
294
|
+
ne viene aperta una nuova con commit automatico.
|
|
295
|
+
where (dict[str, Any], optional): Filtri per selezionare le righe da
|
|
296
|
+
aggiornare. Se ``None``, aggiorna tutte le righe (usare con cautela).
|
|
297
|
+
returning (bool): Se ``True``, usa ``RETURNING``. Default: ``True``.
|
|
298
|
+
parsing_model (Type[BaseModel], optional): Modello per la serializzazione
|
|
299
|
+
del risultato.
|
|
300
|
+
**kwargs: Valori da impostare sulle righe trovate.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
list[BaseModel] | Sequence[RowMapping]: Righe aggiornate (serializzate o grezze).
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
>>> meta.update(from_table="users", where={"id": 1}, name="Luigi")
|
|
307
|
+
"""
|
|
308
|
+
keep_open = False if connection is None else True
|
|
309
|
+
connection = self.open_connection if connection is None else connection
|
|
310
|
+
table = self.get_table(name=from_table)
|
|
311
|
+
result = self._update(table=table, connection=connection, where=where, returning=returning, **kwargs)
|
|
312
|
+
return self._return(connection=connection, result=result, keep_open=keep_open, commit=True, parsing_model=parsing_model)
|
|
313
|
+
|
|
314
|
+
def _update(self, table: Table, connection: Connection, where: dict[str, Any] = None, returning: bool = True, **kwargs):
|
|
315
|
+
"""Costruisce ed esegue lo statement UPDATE con filtri e valori.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
table (Table): Tabella da aggiornare.
|
|
319
|
+
connection (Connection): Connessione corrente.
|
|
320
|
+
where (dict[str, Any], optional): Filtri per le righe da aggiornare.
|
|
321
|
+
returning (bool): Se ``True``, usa ``RETURNING``. Default: ``True``.
|
|
322
|
+
**kwargs: Valori da impostare sulle righe.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Sequence[RowMapping]: Righe aggiornate come mapping.
|
|
326
|
+
"""
|
|
327
|
+
if where is None:
|
|
328
|
+
where = {}
|
|
329
|
+
stmt = table.update()
|
|
330
|
+
stmt = stmt.where(*[self._eq_where(table.c[column], condition) for column, condition in where.items()])
|
|
331
|
+
stmt = stmt.values(**kwargs)
|
|
332
|
+
stmt = stmt if not returning else stmt.returning()
|
|
333
|
+
return self.execute(connection=connection, stmt=stmt, mapping=True)
|
|
334
|
+
|
|
335
|
+
def delete(self, from_table: str, connection: Connection = None, where: dict[str, Any] = None, **kwhere):
|
|
336
|
+
"""Elimina le righe che soddisfano i criteri specificati.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
from_table (str): Nome della tabella da cui eliminare.
|
|
340
|
+
connection (Connection, optional): Connessione esterna. Se fornita, il
|
|
341
|
+
commit non viene eseguito automaticamente.
|
|
342
|
+
where (dict[str, Any], optional): Filtri come dizionario.
|
|
343
|
+
Ha precedenza su ``**kwhere``.
|
|
344
|
+
**kwhere: Filtri come keyword arguments.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
int: Numero di righe eliminate.
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
>>> meta.delete(from_table="users", id=5)
|
|
351
|
+
"""
|
|
352
|
+
keep_open = False if connection is None else True
|
|
353
|
+
connection = self.open_connection if connection is None else connection
|
|
354
|
+
table = self.get_table(name=from_table)
|
|
355
|
+
num_rows = self._delete(table=table, connection=connection, where=where, **kwhere)
|
|
356
|
+
if not num_rows or keep_open:
|
|
357
|
+
return num_rows
|
|
358
|
+
else:
|
|
359
|
+
self.commit(connection)
|
|
360
|
+
connection.close()
|
|
361
|
+
return num_rows
|
|
362
|
+
|
|
363
|
+
def _delete(self, table: Table, connection: Connection, where: dict[str, Any] = None, **kwhere):
|
|
364
|
+
"""Costruisce ed esegue lo statement DELETE con i filtri forniti.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
table (Table): Tabella da cui eliminare.
|
|
368
|
+
connection (Connection): Connessione corrente.
|
|
369
|
+
where (dict[str, Any], optional): Filtri ``{colonna: valore}``.
|
|
370
|
+
Se ``None``, usa ``**kwhere``.
|
|
371
|
+
**kwhere: Filtri come keyword arguments.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
int: Numero di righe eliminate (``rowcount``).
|
|
375
|
+
"""
|
|
376
|
+
if where is None:
|
|
377
|
+
where = kwhere if kwhere else {}
|
|
378
|
+
stmt = table.delete()
|
|
379
|
+
stmt = stmt.where(*[self._eq_where(table.c[column], condition) for column, condition in where.items()])
|
|
380
|
+
return self.execute(connection=connection, stmt=stmt).rowcount
|
|
381
|
+
|
|
382
|
+
# --------------
|
|
383
|
+
|
|
384
|
+
def execute(self, connection: Connection, stmt, mapping: bool = False):
|
|
385
|
+
"""Esegue uno statement sulla connessione, gestendo le eccezioni.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
connection (Connection): Connessione su cui eseguire lo statement.
|
|
389
|
+
stmt: Statement SQLAlchemy Core da eseguire.
|
|
390
|
+
mapping (bool): Se ``True``, restituisce le righe come mapping
|
|
391
|
+
(``result.mappings().all()``); altrimenti restituisce il
|
|
392
|
+
``CursorResult`` grezzo. Default: ``False``.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Sequence[RowMapping] | CursorResult: Righe come mapping se ``mapping=True``,
|
|
396
|
+
altrimenti il result grezzo.
|
|
397
|
+
|
|
398
|
+
Raises:
|
|
399
|
+
Exception: In caso di errore durante l'esecuzione (via ``_handle_exception``).
|
|
400
|
+
"""
|
|
401
|
+
try:
|
|
402
|
+
result = connection.execute(stmt)
|
|
403
|
+
except Exception as e:
|
|
404
|
+
self._handle_exception(connection, e)
|
|
405
|
+
else:
|
|
406
|
+
if mapping:
|
|
407
|
+
result = result.mappings().all()
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
def _get_table(self, name: str):
|
|
411
|
+
"""Recupera una tabella dal ``MetaData`` locale per nome.
|
|
412
|
+
|
|
413
|
+
Normalizza il nome anteponendo lo schema configurato, se necessario.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
name (str): Nome della tabella (con o senza prefisso di schema).
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Table | None: La tabella se già presente nel ``MetaData``, altrimenti ``None``.
|
|
420
|
+
"""
|
|
421
|
+
if self.schema is not None:
|
|
422
|
+
table_name = name if name.startswith(f"{self.schema}.") else f"{self.schema}.{name}"
|
|
423
|
+
else:
|
|
424
|
+
table_name = name
|
|
425
|
+
return self.metadata.tables.get(table_name)
|
|
426
|
+
|
|
427
|
+
def get_table(self, name: str) -> Optional[Table]:
|
|
428
|
+
"""Restituisce una tabella, riflettendola dal database se non già nota.
|
|
429
|
+
|
|
430
|
+
Cerca prima nel ``MetaData`` locale; se assente, tenta il caricamento
|
|
431
|
+
automatico (``autoload_with``) dal database.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
name (str): Nome della tabella da recuperare.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Optional[Table]: La tabella richiesta.
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
InternalServerException: Se la tabella non esiste nel database.
|
|
441
|
+
"""
|
|
442
|
+
table = self._get_table(name=name)
|
|
443
|
+
if table is None:
|
|
444
|
+
try:
|
|
445
|
+
table = Table(name, self.metadata, schema=self.schema, autoload_with=self.engine)
|
|
446
|
+
except NoSuchTableError:
|
|
447
|
+
raise InternalServerException(f"No such table {name}")
|
|
448
|
+
return table
|
|
449
|
+
|
|
450
|
+
def has_table(self, name: str):
|
|
451
|
+
"""Verifica se una tabella esiste nel database.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
name (str): Nome della tabella da verificare.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
bool: ``True`` se la tabella esiste nello schema configurato.
|
|
458
|
+
"""
|
|
459
|
+
return self.engine_inspection.has_table(name, self.schema)
|
|
460
|
+
|
|
461
|
+
def _add_limit_offset_condition(self, stmt, limit: int, page: int = None):
|
|
462
|
+
"""Aggiunge le clausole LIMIT e OFFSET allo statement per la paginazione.
|
|
463
|
+
|
|
464
|
+
Non applica nessuna modifica se ``limit`` è 0 o negativo.
|
|
465
|
+
L'offset viene calcolato come ``(page - 1) * limit``.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
stmt: Statement su cui applicare LIMIT/OFFSET.
|
|
469
|
+
limit (int): Numero massimo di righe. 0 = nessun limite.
|
|
470
|
+
page (int, optional): Numero di pagina (1-based). Se ``None`` o <= 0,
|
|
471
|
+
non viene applicato nessun offset.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Statement con LIMIT e/o OFFSET applicati.
|
|
475
|
+
"""
|
|
476
|
+
if limit and limit > 0:
|
|
477
|
+
stmt = stmt.limit(limit)
|
|
478
|
+
if page and page > 0:
|
|
479
|
+
offset = (page - 1) * limit
|
|
480
|
+
stmt = stmt.offset(offset)
|
|
481
|
+
return stmt
|
|
482
|
+
|
|
483
|
+
def _eq_where(self, col: Column, condition, neq: bool = False):
|
|
484
|
+
"""Costruisce la singola condizione WHERE per una colonna.
|
|
485
|
+
|
|
486
|
+
Logica applicata:
|
|
487
|
+
- ``list`` → ``col IN (values)``
|
|
488
|
+
- Tipo ``ARRAY`` → ``value = ANY(col)``
|
|
489
|
+
- Scalare → ``col == value``
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
col (Column): Colonna su cui applicare la condizione.
|
|
493
|
+
condition: Valore o lista di valori da confrontare.
|
|
494
|
+
neq (bool): Se ``True``, nega la condizione (``NOT``). Default: ``False``.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
ColumnElement: Espressione booleana SQLAlchemy.
|
|
498
|
+
"""
|
|
499
|
+
eq_where = col.in_(condition) if isinstance(condition, list) else col == condition if str(col.type) != "ARRAY" else condition == col.any_()
|
|
500
|
+
if neq:
|
|
501
|
+
eq_where = not_(eq_where)
|
|
502
|
+
return eq_where
|