python-general-be-lib 0.4.2__tar.gz → 0.5.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 (36) hide show
  1. {python_general_be_lib-0.4.2/python_general_be_lib.egg-info → python_general_be_lib-0.5.0}/PKG-INFO +1 -1
  2. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/crud_repository.py +16 -12
  3. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/geometry_repository.py +7 -7
  4. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/handler/__init__.py +1 -0
  5. python_general_be_lib-0.5.0/general/interface/repository/handler/base_handler.py +23 -0
  6. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/handler/ilike_handler.py +11 -16
  7. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/handler/interval_handler.py +3 -6
  8. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/handler/json_handler.py +1 -3
  9. python_general_be_lib-0.5.0/general/interface/repository/handler/statement_manager.py +40 -0
  10. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/pyproject.toml +1 -1
  11. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0/python_general_be_lib.egg-info}/PKG-INFO +1 -1
  12. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/python_general_be_lib.egg-info/SOURCES.txt +1 -0
  13. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/setup.py +1 -1
  14. python_general_be_lib-0.4.2/general/interface/repository/handler/base_handler.py +0 -40
  15. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/LICENSE +0 -0
  16. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/README.md +0 -0
  17. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/__init__.py +0 -0
  18. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/exception/__init__.py +0 -0
  19. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/exception/access_exceptions.py +0 -0
  20. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/exception/crud_exceptions.py +0 -0
  21. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/exception/exception_interface.py +0 -0
  22. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/base/__init__.py +0 -0
  23. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/base/base_model.py +0 -0
  24. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/base/declarative_base.py +0 -0
  25. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/metadata/__init__.py +0 -0
  26. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/metadata/crud_metadata.py +0 -0
  27. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/metadata/geom_metadata.py +0 -0
  28. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/__init__.py +0 -0
  29. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/many_to_many_repository.py +0 -0
  30. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/interface/repository/view_repository.py +0 -0
  31. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/logger.py +0 -0
  32. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/general/paginator_dto.py +0 -0
  33. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/python_general_be_lib.egg-info/dependency_links.txt +0 -0
  34. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/python_general_be_lib.egg-info/requires.txt +0 -0
  35. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.0}/python_general_be_lib.egg-info/top_level.txt +0 -0
  36. {python_general_be_lib-0.4.2 → python_general_be_lib-0.5.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.4.2
3
+ Version: 0.5.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
@@ -13,30 +13,34 @@ from sqlalchemy.orm import Session, load_only, defer
13
13
  from sqlalchemy.sql.functions import count
14
14
 
15
15
  from .handler.base_handler import BaseHandler
16
+ from .handler.statement_manager import StatementManager
16
17
  from ...exception.crud_exceptions import HasNoAttributeException, EntityNotFoundException, ServiceUnavailableException, BadRequestException
17
18
  from ...interface.base.declarative_base import Base
18
19
  from ...paginator_dto import PaginatorResponseModel
19
20
 
20
21
 
21
22
  class CrudRepository[Entity: Base | Type[Base]]:
22
- __slots__ = "model", "entity", "engine", "columns", "handler", "relationship_attributes"
23
+ __slots__ = "model", "entity", "engine", "columns", "stmt_manager", "relationship_attributes"
23
24
 
24
25
  model: Type[BaseModel] | BaseModel
25
26
  entity: Entity
26
27
  engine: Type[Engine] | Engine
27
- handler: Type[BaseHandler] | BaseHandler
28
+ stmt_manager: StatementManager | Type[StatementManager]
28
29
 
29
30
  @property
30
31
  def open_session(self):
31
32
  return Session(self.engine)
32
33
 
33
- def __init__(self, entity: Entity, model: Type[BaseModel] | BaseModel, engine: Type[Engine] | Engine, handler_class: Type[BaseHandler] | BaseHandler = None) -> None:
34
+ def __init__(self, entity: Entity, model: Type[BaseModel] | BaseModel, engine: Type[Engine] | Engine, handlers: list[BaseHandler | Type[BaseHandler]] = None,
35
+ handler_class: Type[BaseHandler] | BaseHandler = None) -> None:
34
36
  self.entity = entity
35
37
  self.model = model
36
38
  self.engine = engine
37
39
  self.columns = self.entity.columns()
38
40
 
39
- self.handler = handler_class(self.columns) if handler_class else BaseHandler(self.columns)
41
+ base_handler = handler_class(self.columns) if handler_class else BaseHandler(self.columns)
42
+
43
+ self.stmt_manager = StatementManager(handlers=handlers, base_handler=base_handler)
40
44
 
41
45
  # -------------------------
42
46
 
@@ -106,8 +110,8 @@ class CrudRepository[Entity: Base | Type[Base]]:
106
110
  if where is None:
107
111
  where = kwhere if kwhere else {}
108
112
  stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
109
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
110
- stmt = self.handler.limit_offset_condition(stmt=stmt, limit=limit)
113
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
114
+ stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
111
115
  return self.execute(session, stmt)
112
116
 
113
117
  def insert(self, session: Session = None, models: list[Type[BaseModel] | BaseModel] = None, returning: bool = True, parsing_model: Type[BaseModel] | BaseModel = None) -> list[Entity]:
@@ -146,7 +150,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
146
150
  if where is None:
147
151
  where = {}
148
152
  stmt = update(self.entity)
149
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
153
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
150
154
 
151
155
  stmt = stmt.values(**kwargs)
152
156
  stmt = stmt if not returning else stmt.returning(self.entity)
@@ -167,7 +171,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
167
171
  if where is None:
168
172
  where = kwhere if kwhere else {}
169
173
  stmt = delete(self.entity)
170
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
174
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
171
175
  try:
172
176
  result = session.execute(stmt)
173
177
  except Exception as e:
@@ -218,7 +222,7 @@ class CrudRepository[Entity: Base | Type[Base]]:
218
222
  if where is None:
219
223
  where = kwhere if kwhere else {}
220
224
  stmt = select(count(pk))
221
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
225
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
222
226
  result = session.execute(stmt).all()[0]
223
227
  return result[0]
224
228
 
@@ -244,13 +248,13 @@ class CrudRepository[Entity: Base | Type[Base]]:
244
248
  if where is None:
245
249
  where = kwhere if kwhere else {}
246
250
  stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
247
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
251
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
248
252
 
249
253
  row_numbers = self._preconf_count(session=session, where=stmt.whereclause)
250
254
 
251
- stmt = self.handler.limit_offset_condition(stmt=stmt, limit=limit, page=page)
255
+ stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit, page=page)
252
256
  if order_by:
253
- stmt = self.handler.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
257
+ stmt = self.stmt_manager.order_by_condition(stmt=stmt, order_by=order_by, asc=asc)
254
258
 
255
259
  entities = self.execute(session, stmt)
256
260
  return self.parse_pagination(limit=limit, page=page, order_by=order_by, count=row_numbers, asc=asc, data=entities)
@@ -38,11 +38,11 @@ class GeometryRepository[Entity: Base | Type[Base]](CrudRepository[Entity]):
38
38
 
39
39
  intersecting_geom = self._geom_condition(geom=geom, geom_srid=geom_srid, buffer=tolerance)
40
40
 
41
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
42
- stmt = self.handler.prepare_statement(stmt=stmt, neq=True, **not_where)
41
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
42
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, neq=True, **not_where)
43
43
 
44
44
  stmt = stmt.where(self.geom_column.ST_Intersects(intersecting_geom))
45
- stmt = self.handler.limit_offset_condition(stmt=stmt, limit=limit)
45
+ stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
46
46
 
47
47
  return self.execute(session, stmt)
48
48
 
@@ -60,14 +60,14 @@ class GeometryRepository[Entity: Base | Type[Base]](CrudRepository[Entity]):
60
60
  where = {}
61
61
 
62
62
  stmt = self._select(entity=self.entity, columns=columns, exclude_columns=exclude_columns)
63
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
63
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
64
64
 
65
65
  geom_condition = self._geom_condition(geom=geom, geom_srid=geom_srid, buffer=tolerance)
66
66
  geom_condition = self.geom_column.ST_Contains(geom_condition) if not contained else geom_condition.ST_Contains(self.geom_column)
67
67
 
68
68
  stmt = stmt.where(geom_condition)
69
69
 
70
- stmt = self.handler.limit_offset_condition(stmt=stmt, limit=limit)
70
+ stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
71
71
  return self.execute(session, stmt)
72
72
 
73
73
  def nearest(self, geom: WKBElement, session: Session = None, columns: list[str] = None, exclude_columns: list[str] = None, limit: int = 1, where: dict[str, Any] = None, geom_srid: int = 4326,
@@ -89,9 +89,9 @@ class GeometryRepository[Entity: Base | Type[Base]](CrudRepository[Entity]):
89
89
  if distance:
90
90
  stmt = stmt.add_columns(distance_col)
91
91
 
92
- stmt = self.handler.prepare_statement(stmt=stmt, **where)
92
+ stmt = self.stmt_manager.prepare_statement(stmt=stmt, **where)
93
93
  stmt = stmt.order_by(literal_column("distance"))
94
- stmt = self.handler.limit_offset_condition(stmt=stmt, limit=limit)
94
+ stmt = self.stmt_manager.limit_offset_condition(stmt=stmt, limit=limit)
95
95
 
96
96
  rows = self.execute(session, stmt)
97
97
  return rows
@@ -2,3 +2,4 @@ from .base_handler import *
2
2
  from .ilike_handler import *
3
3
  from .interval_handler import *
4
4
  from .json_handler import *
5
+ from .statement_manager import *
@@ -0,0 +1,23 @@
1
+ __all__ = ["BaseHandler"]
2
+
3
+ from typing import Any
4
+
5
+ from sqlalchemy import Select, Update, Delete, Column, not_
6
+ from sqlalchemy.sql.base import ReadOnlyColumnCollection
7
+
8
+
9
+ class BaseHandler:
10
+ __slots__ = "columns"
11
+
12
+ def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]]):
13
+ self.columns = columns
14
+
15
+ def handle(self, stmt: Select | Update | Delete, neq: bool = False, where: dict[str, Any] = None):
16
+ stmt = stmt.where(*[self._handle(column=self.columns[column], condition=condition, neq=neq) for column, condition in where.items()])
17
+ return stmt
18
+
19
+ def _handle(self, column: Column, condition: Any, neq: bool = False):
20
+ eq_where = column.in_(condition) if isinstance(condition, list) else column == condition if str(column.type) != "ARRAY" else condition == column.any_()
21
+ if neq:
22
+ eq_where = not_(eq_where)
23
+ return eq_where
@@ -15,24 +15,19 @@ class ILikeHandler(BaseHandler):
15
15
 
16
16
  def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]], ilike_aliases: list[str] = None, ilike_attributes: list[str] = None):
17
17
  self.columns = columns
18
- self.ilike_aliases = ilike_aliases if not ilike_aliases else []
19
- self.ilike_attributes = ilike_attributes if not ilike_attributes else []
18
+ self.ilike_aliases = ilike_aliases if ilike_aliases is not None else []
19
+ self.ilike_attributes = ilike_attributes if ilike_attributes is not None else []
20
20
 
21
- def prepare_statement(self, stmt: Select | Update | Delete, **where):
22
- ilike_values = self.retrieve_ilike_values(where)
23
- ilike_conditions = [self._ilike_condition(ilike_value) for _, ilike_value in ilike_values]
21
+ for ilike_attribute in self.ilike_attributes:
22
+ if ilike_attribute not in self.columns:
23
+ raise ValueError(f"{ilike_attribute} not in columns")
24
24
 
25
- stmt = super().prepare_statement(stmt=stmt, **where)
26
- stmt = stmt.where(*ilike_conditions)
27
- return stmt
28
-
29
- def _ilike_condition(self, condition):
30
- return or_(*[self.columns[col].ilike(f'%{condition}%') for col in self.ilike_attributes])
31
-
32
- def retrieve_ilike_values(self, where: dict[str, Any]):
33
- ilike_values = []
25
+ def handle(self, stmt: Select | Update | Delete, where: dict[str, Any] = None, **kwargs):
34
26
  for alias in self.ilike_aliases:
35
27
  condition = where.pop(alias, None)
36
28
  if condition:
37
- ilike_values.append((alias, condition))
38
- return ilike_values
29
+ stmt = stmt.where(self._handle(alias, condition))
30
+ return stmt
31
+
32
+ def _handle(self, alias: str, condition: str, neq: bool = False):
33
+ return or_(*[self.columns[col].ilike(f'%{condition}%') for col in self.ilike_attributes])
@@ -14,18 +14,15 @@ class IntervalHandler(BaseHandler):
14
14
  to_: Any
15
15
  inf_is_none: bool
16
16
 
17
- def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]], from_: Any = None, to_: Any = None, inf_is_none: bool = None):
17
+ def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]], from_: Any = None, to_: Any = None, inf_is_none: bool = False):
18
18
  self.columns = columns
19
19
  self.from_ = from_
20
20
  self.to_ = to_
21
21
  self.inf_is_none = inf_is_none
22
22
 
23
- def prepare_statement(self, stmt: Select | Update | Delete, **where):
24
-
23
+ def handle(self, stmt: Select | Update | Delete, where: dict[str, Any] = None, **kwargs):
25
24
  from_condition = where.pop(self.from_, None)
26
- to_condition = where.pop(self.from_, None)
27
-
28
- stmt = super().prepare_statement(stmt=stmt, **where)
25
+ to_condition = where.pop(self.to_, None)
29
26
  if from_condition:
30
27
  stmt = stmt.where(self.columns[self.from_] >= from_condition)
31
28
  if to_condition:
@@ -22,10 +22,8 @@ class JsonHandler(BaseHandler):
22
22
  except NotImplementedError:
23
23
  continue
24
24
 
25
- def prepare_statement(self, stmt: Select | Update | Delete, **where):
25
+ def handle(self, stmt: Select | Update | Delete, where: dict[str, Any] = None, **kwargs):
26
26
  json_keys = {key for key in where.keys() if key not in self.columns}
27
27
  json_conditions = {key: where.pop(key) for key in json_keys}
28
-
29
- stmt = super().prepare_statement(stmt=stmt, **where)
30
28
  stmt = stmt.where(*[self.columns[self.json_column][key].as_string() == str(value) for key, value in json_conditions.items()])
31
29
  return stmt
@@ -0,0 +1,40 @@
1
+ __all__ = ["StatementManager"]
2
+
3
+ from ast import Delete
4
+ from typing import Type
5
+
6
+ from sqlalchemy import Select, Update
7
+
8
+ from general.exception.crud_exceptions import HasNoAttributeException
9
+ from .base_handler import BaseHandler
10
+
11
+
12
+ class StatementManager:
13
+ __slots__ = "_handlers", "_base_handler"
14
+
15
+ def __init__(self, handlers: list[BaseHandler | Type[BaseHandler]] = None, base_handler: BaseHandler = None):
16
+ if not base_handler:
17
+ raise ValueError("StatemantManager: base_handler is not set")
18
+ self._handlers = handlers if handlers else []
19
+ self._base_handler = base_handler
20
+
21
+ def prepare_statement(self, stmt: Select | Update | Delete, neq: bool = False, **where):
22
+ for handler in self._handlers:
23
+ stmt = handler.handle(stmt=stmt, neq=neq, where=where)
24
+ stmt = self._base_handler.handle(stmt=stmt, neq=neq, where=where)
25
+ return stmt
26
+
27
+ def order_by_condition(self, stmt: Select, order_by: str, asc: bool):
28
+ try:
29
+ stmt = stmt.order_by(self._base_handler.columns[order_by].asc()) if asc else stmt.order_by(self._base_handler.columns[order_by].desc())
30
+ except KeyError:
31
+ raise HasNoAttributeException(order_by)
32
+ return stmt
33
+
34
+ def limit_offset_condition(self, stmt: Select, limit: int, page: int = None):
35
+ if limit and limit > 0:
36
+ stmt = stmt.limit(limit)
37
+ if page and page > 0:
38
+ offset = (page - 1) * limit
39
+ stmt = stmt.offset(offset)
40
+ return stmt
@@ -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.4.2"
7
+ version = "0.5.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.4.2
3
+ Version: 0.5.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
@@ -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/statement_manager.py
28
29
  python_general_be_lib.egg-info/PKG-INFO
29
30
  python_general_be_lib.egg-info/SOURCES.txt
30
31
  python_general_be_lib.egg-info/dependency_links.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.4.2",
5
+ version="0.5.0",
6
6
  packages=find_packages(include=["general*"]),
7
7
  python_requires=">=3.12",
8
8
  install_requires=[
@@ -1,40 +0,0 @@
1
- __all__ = ["BaseHandler"]
2
-
3
- from typing import Any
4
-
5
- from sqlalchemy import Select, Update, Delete, Column, not_
6
- from sqlalchemy.sql.base import ReadOnlyColumnCollection
7
-
8
- from general.exception.crud_exceptions import HasNoAttributeException
9
-
10
-
11
- class BaseHandler:
12
- __slots__ = "columns"
13
-
14
- def __init__(self, columns: ReadOnlyColumnCollection[str, Column[Any]]):
15
- self.columns = columns
16
-
17
- def prepare_statement(self, stmt: Select | Update | Delete, neq: bool = False, **where):
18
- stmt = stmt.where(*[self.handle(column=self.columns[column], condition=condition, neq=neq) for column, condition in where.items()])
19
- return stmt
20
-
21
- def order_by_condition(self, stmt: Select, order_by: str, asc: bool):
22
- try:
23
- stmt = stmt.order_by(self.columns[order_by].asc()) if asc else stmt.order_by(self.columns[order_by].desc())
24
- except KeyError:
25
- raise HasNoAttributeException(order_by)
26
- return stmt
27
-
28
- def limit_offset_condition(self, stmt: Select, limit: int, page: int = None):
29
- if limit and limit > 0:
30
- stmt = stmt.limit(limit)
31
- if page and page > 0:
32
- offset = (page - 1) * limit
33
- stmt = stmt.offset(offset)
34
- return stmt
35
-
36
- def handle(self, column: Column, condition: Any, neq: bool = False):
37
- eq_where = column.in_(condition) if isinstance(condition, list) else column == condition if str(column.type) != "ARRAY" else condition == column.any_()
38
- if neq:
39
- eq_where = not_(eq_where)
40
- return eq_where