python-general-be-lib 0.12.0__py3-none-any.whl → 0.12.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.
@@ -169,14 +169,23 @@ class CrudRepository[Entity: Base | Type[Base]]:
169
169
  return _reduced_model(self.entity, frozenset(selected_columns))
170
170
 
171
171
  def _handle_exception(self, session: Session, e: Exception):
172
- """Close the session, log the error and raise an HTTP-friendly exception.
172
+ """Roll back and close the session, log the error and raise an HTTP-friendly exception.
173
173
 
174
174
  An ``OperationalError`` becomes a ``ServiceUnavailableException``, a
175
175
  ``NoResultFound`` an ``EntityNotFoundException``, a ``DataError`` or an
176
176
  ``IntegrityError`` a ``BadRequestException``; any other exception is re-raised as is.
177
177
 
178
+ ``close`` alone already rolls back the database transaction. What the explicit
179
+ ``rollback`` adds is the expiry of the ORM instances: an entity still held by the
180
+ caller raises ``DetachedInstanceError`` on access instead of exposing values that
181
+ were flushed but never committed. It runs on every error, a failed SELECT included,
182
+ since on PostgreSQL that aborts the transaction as well. An external session is
183
+ rolled back and closed too, so the caller's transaction is lost. A failure of the
184
+ rollback itself is logged and does not replace ``e``, and the session is closed
185
+ anyway.
186
+
178
187
  Args:
179
- session (Session): Session to close.
188
+ session (Session): Session to roll back and close.
180
189
  e (Exception): Caught exception to handle.
181
190
 
182
191
  Raises:
@@ -184,6 +193,11 @@ class CrudRepository[Entity: Base | Type[Base]]:
184
193
  EntityNotFoundException: If no record matches.
185
194
  BadRequestException: If the data is malformed or violates a constraint.
186
195
  """
196
+ try:
197
+ session.rollback()
198
+ except Exception:
199
+ # un rollback fallito non deve sostituire l'errore originale, né saltare la close
200
+ logging.exception("Rollback failed")
187
201
  session.close()
188
202
  logging.error(e)
189
203
  raise_http_exception(e)
@@ -195,12 +209,13 @@ class CrudRepository[Entity: Base | Type[Base]]:
195
209
  session (Session): Session to commit.
196
210
 
197
211
  Raises:
198
- ServiceUnavailableException | BadRequestException: On error during the commit.
212
+ ServiceUnavailableException | BadRequestException: On error during the commit,
213
+ after rolling back and closing the session.
199
214
  """
200
215
  try:
201
216
  session.commit()
202
217
  except Exception as e:
203
- self._handle_exception(session, e)
218
+ self._handle_exception(session=session, e=e)
204
219
 
205
220
  def _flush(self, session: Session):
206
221
  """Flush the session, handling the possible exceptions.
@@ -214,12 +229,13 @@ class CrudRepository[Entity: Base | Type[Base]]:
214
229
  session (Session): Session to flush.
215
230
 
216
231
  Raises:
217
- ServiceUnavailableException | BadRequestException: On error during the flush.
232
+ ServiceUnavailableException | BadRequestException: On error during the flush,
233
+ after rolling back and closing the session.
218
234
  """
219
235
  try:
220
236
  session.flush()
221
237
  except Exception as e:
222
- self._handle_exception(session, e)
238
+ self._handle_exception(session=session, e=e)
223
239
 
224
240
  def _entity_has_columns(self, *columns: str, entity: Type[Base] | Base = None) -> bool:
225
241
  """Check that every given name is a column of the entity.
@@ -441,7 +457,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
441
457
  try:
442
458
  entity = session.get(self.entity, values)
443
459
  except Exception as e:
444
- self._handle_exception(session, e)
460
+ self._handle_exception(session=session, e=e)
445
461
  else:
446
462
  return [entity] if entity is not None else []
447
463
 
@@ -607,7 +623,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
607
623
  try:
608
624
  session.add_all(entities)
609
625
  except Exception as e:
610
- self._handle_exception(session, e)
626
+ self._handle_exception(session=session, e=e)
611
627
  return self._return(session=session, entities=entities, keep_open=keep_open, commit=True, parsing_model=parsing_model)
612
628
 
613
629
  def update(self, session: Session = None, where: dict[str, Any] = None, returning: bool = True, parsing_model: Type[BaseModel] | BaseModel = None, **kwargs) -> list[Entity] | list[BaseModel]:
@@ -1049,9 +1065,9 @@ class CrudRepository[Entity: Base | Type[Base]]:
1049
1065
  """Execute a SQLAlchemy statement and return the result as it is.
1050
1066
 
1051
1067
  The method owns the error handling and nothing else: every exception goes to
1052
- ``_handle_exception``, which closes the session and maps the error to an HTTP
1053
- status. Reading the result is left to the caller, which is the only one knowing the
1054
- shape it needs — ``.scalars().all()`` for entities, ``.scalar()`` for an aggregate,
1068
+ ``_handle_exception``, which rolls back and closes the session and maps the error
1069
+ to an HTTP status. Reading the result is left to the caller, which is the only one
1070
+ knowing the shape it needs — ``.scalars().all()`` for entities, ``.scalar()`` for an aggregate,
1055
1071
  ``.all()`` for the multi-column rows of a statement built with ``add_columns``,
1056
1072
  ``.rowcount`` for a DML without ``RETURNING``. The result is bound to the session,
1057
1073
  so it has to be consumed before the session is closed. ``Session.execute`` returns
@@ -1076,6 +1092,6 @@ class CrudRepository[Entity: Base | Type[Base]]:
1076
1092
  try:
1077
1093
  result = session.execute(statement=stmt, params=values)
1078
1094
  except Exception as e:
1079
- self._handle_exception(session, e)
1095
+ self._handle_exception(session=session, e=e)
1080
1096
  else:
1081
1097
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-general-be-lib
3
- Version: 0.12.0
3
+ Version: 0.12.1
4
4
  Summary: General purpose backend library — SQLAlchemy CRUD/Geometry repositories, FastAPI exceptions, Pydantic base models, Keycloak JWT auth, 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
@@ -24,7 +24,7 @@ general/interface/mixin/audit_mixin.py,sha256=N2JDD9JxXDMeBqu0yAlhVWFOK3EeUA2b21
24
24
  general/interface/mixin/softdelete_mixin.py,sha256=526luzM4DBCorcq99kZ6BYqtQKKJCpnxX9rSEigfyIU,2148
25
25
  general/interface/mixin/timestamp_mixin.py,sha256=wmcIx2Y8jgVqg9A-1E61BvkcKCo52ZjKNHeKfXJmUl0,2220
26
26
  general/interface/repository/__init__.py,sha256=QQD6okLfRozC0uOZPGrwOUDKJyZTgpObvwJ3OOSJdbM,1813
27
- general/interface/repository/crud_repository.py,sha256=Hr7APBsN1uFqEfM78ZSIcgheg4ilvND4OMc7TBNOlcw,55962
27
+ general/interface/repository/crud_repository.py,sha256=Z7IgLz7dtfIyzZMJO7T_IDunHH1qTDYRApdWlbMkE2A,57031
28
28
  general/interface/repository/geometry_repository.py,sha256=Fmv0qhfqumnU5fiETMMAmiB0UyG43b19jlPXQ8cJ9CU,11289
29
29
  general/interface/repository/many_to_many_repository.py,sha256=KRJ2SMUGDYNoGV-ea541wlWg0FW4NQ_Ry85aJBarmC8,11183
30
30
  general/interface/repository/view_repository.py,sha256=wxDGw7kenuVrDd9nfh7-omw2m9Jex5WzF_lhEiqPoA0,7902
@@ -40,8 +40,8 @@ general/interface/repository/handler/soft_delete_handler.py,sha256=1WD1n6q5plyJ0
40
40
  general/interface/repository/handler/statement_manager.py,sha256=JSj6K-iQJ06kLdsMTOqK62cIvR87ioOA1LmLTGpqrB0,8205
41
41
  general/utils/__init__.py,sha256=_SPe88reIZ0JxwK7mp-u_p-9c4hCcZ-daUihi0gymNk,103
42
42
  general/utils/consts.py,sha256=nE75Ud7sVuDw9K_xmIJNjL_5f_BVJmwGNPGFYc26H3Y,264
43
- python_general_be_lib-0.12.0.dist-info/licenses/LICENSE,sha256=iUaO1XZyB9P3Tmog0OILuTisP6vXGe3QKz-4yRTxOFk,1069
44
- python_general_be_lib-0.12.0.dist-info/METADATA,sha256=5RrV-gffYJqAwx09wHOCqK-xN7GDSqK7dKgeP7Rfcyk,3995
45
- python_general_be_lib-0.12.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
46
- python_general_be_lib-0.12.0.dist-info/top_level.txt,sha256=tTZePW8_CNUqSgKFd2SEH72ZbnhS0OYjRsgcv0ikSFY,8
47
- python_general_be_lib-0.12.0.dist-info/RECORD,,
43
+ python_general_be_lib-0.12.1.dist-info/licenses/LICENSE,sha256=iUaO1XZyB9P3Tmog0OILuTisP6vXGe3QKz-4yRTxOFk,1069
44
+ python_general_be_lib-0.12.1.dist-info/METADATA,sha256=8T5p8u9J9MUHT1xZjRf_Ss29Y97hEeJX5wZD71aVTyI,3995
45
+ python_general_be_lib-0.12.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
46
+ python_general_be_lib-0.12.1.dist-info/top_level.txt,sha256=tTZePW8_CNUqSgKFd2SEH72ZbnhS0OYjRsgcv0ikSFY,8
47
+ python_general_be_lib-0.12.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.0.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5