maleo-database 0.0.20__tar.gz → 0.0.22__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 (33) hide show
  1. {maleo_database-0.0.20 → maleo_database-0.0.22}/PKG-INFO +1 -1
  2. {maleo_database-0.0.20 → maleo_database-0.0.22}/maleo_database.egg-info/PKG-INFO +1 -1
  3. {maleo_database-0.0.20 → maleo_database-0.0.22}/maleo_database.egg-info/SOURCES.txt +2 -8
  4. {maleo_database-0.0.20 → maleo_database-0.0.22}/pyproject.toml +1 -7
  5. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/handlers.py +12 -35
  6. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/managers/__init__.py +14 -10
  7. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/managers/session.py +30 -10
  8. maleo_database-0.0.22/src/orm/mixins.py +91 -0
  9. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/orm/queries.py +82 -80
  10. maleo_database-0.0.20/src/orm/base.py +0 -7
  11. maleo_database-0.0.20/src/orm/models/__init__.py +0 -0
  12. maleo_database-0.0.20/src/orm/models/mixins/__init__.py +0 -0
  13. maleo_database-0.0.20/src/orm/models/mixins/identifier.py +0 -11
  14. maleo_database-0.0.20/src/orm/models/mixins/status.py +0 -12
  15. maleo_database-0.0.20/src/orm/models/mixins/timestamp.py +0 -65
  16. maleo_database-0.0.20/src/orm/models/table.py +0 -17
  17. {maleo_database-0.0.20 → maleo_database-0.0.22}/LICENSE +0 -0
  18. {maleo_database-0.0.20 → maleo_database-0.0.22}/README.md +0 -0
  19. {maleo_database-0.0.20 → maleo_database-0.0.22}/maleo_database.egg-info/dependency_links.txt +0 -0
  20. {maleo_database-0.0.20 → maleo_database-0.0.22}/maleo_database.egg-info/requires.txt +0 -0
  21. {maleo_database-0.0.20 → maleo_database-0.0.22}/maleo_database.egg-info/top_level.txt +0 -0
  22. {maleo_database-0.0.20 → maleo_database-0.0.22}/setup.cfg +0 -0
  23. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/__init__.py +0 -0
  24. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/config/__init__.py +0 -0
  25. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/config/additional.py +0 -0
  26. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/config/connection.py +0 -0
  27. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/config/identifier.py +0 -0
  28. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/config/pooling.py +0 -0
  29. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/dtos.py +0 -0
  30. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/enums.py +0 -0
  31. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/managers/client.py +0 -0
  32. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/managers/engine.py +0 -0
  33. {maleo_database-0.0.20 → maleo_database-0.0.22}/src/orm/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo-database
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: Database package for MaleoSuite
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo-database
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: Database package for MaleoSuite
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: Proprietary
@@ -20,11 +20,5 @@ src/managers/client.py
20
20
  src/managers/engine.py
21
21
  src/managers/session.py
22
22
  src/orm/__init__.py
23
- src/orm/base.py
24
- src/orm/queries.py
25
- src/orm/models/__init__.py
26
- src/orm/models/table.py
27
- src/orm/models/mixins/__init__.py
28
- src/orm/models/mixins/identifier.py
29
- src/orm/models/mixins/status.py
30
- src/orm/models/mixins/timestamp.py
23
+ src/orm/mixins.py
24
+ src/orm/queries.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maleo-database"
7
- version = "0.0.20"
7
+ version = "0.0.22"
8
8
  description = "Database package for MaleoSuite"
9
9
  authors = [
10
10
  { name = "Agra Bima Yuda", email = "agra@nexmedis.com" }
@@ -106,8 +106,6 @@ packages = [
106
106
  "maleo.database.config",
107
107
  "maleo.database.managers",
108
108
  "maleo.database.orm",
109
- "maleo.database.orm.models",
110
- "maleo.database.orm.models.mixins",
111
109
  ]
112
110
 
113
111
  [tool.setuptools.package-data]
@@ -115,13 +113,9 @@ packages = [
115
113
  "maleo.database.config" = ["*.json", "*.yaml"]
116
114
  "maleo.database.managers" = ["*.json", "*.yaml"]
117
115
  "maleo.database.orm" = ["*.json", "*.yaml"]
118
- "maleo.database.orm.models" = ["*.json", "*.yaml"]
119
- "maleo.database.orm.models.mixins" = ["*.json", "*.yaml"]
120
116
 
121
117
  [tool.setuptools.package-dir]
122
118
  "maleo.database" = "src"
123
119
  "maleo.database.config" = "src/config"
124
120
  "maleo.database.managers" = "src/managers"
125
121
  "maleo.database.orm" = "src/orm"
126
- "maleo.database.orm.models" = "src/orm/models"
127
- "maleo.database.orm.models.mixins" = "src/orm/models/mixins"
@@ -1,8 +1,6 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
- from sqlalchemy import MetaData
2
+ from sqlalchemy.orm import DeclarativeBase
3
3
  from typing import Generic, Optional, TypeVar
4
- from maleo.dtos.contexts.service import ServiceContext
5
- from maleo.logging.logger import Database
6
4
  from .config import (
7
5
  ElasticsearchConfig,
8
6
  MongoConfig,
@@ -42,17 +40,7 @@ HandlerT = TypeVar("HandlerT", bound=Handler)
42
40
 
43
41
 
44
42
  class ElasticsearchHandler(Handler[ElasticsearchConfig, ElasticsearchManager]):
45
- @classmethod
46
- def new(
47
- cls,
48
- config: ElasticsearchConfig,
49
- logger: Database,
50
- service_context: Optional[ServiceContext] = None,
51
- ) -> "ElasticsearchHandler":
52
- manager = ElasticsearchManager(
53
- config=config, logger=logger, service_context=service_context
54
- )
55
- return cls(config=config, manager=manager)
43
+ pass
56
44
 
57
45
 
58
46
  class MongoHandler(Handler[MongoConfig, MongoManager]):
@@ -63,33 +51,22 @@ class RedisHandler(Handler[RedisConfig, RedisManager]):
63
51
  pass
64
52
 
65
53
 
66
- class MySQLHandler(Handler[MySQLConfig, MySQLManager]):
54
+ T = TypeVar("T", bound=DeclarativeBase)
55
+
56
+
57
+ class MySQLHandler(Handler[MySQLConfig, MySQLManager[T]], Generic[T]):
58
+ pass
59
+
60
+
61
+ class PostgreSQLHandler(Handler[PostgreSQLConfig, PostgreSQLManager[T]], Generic[T]):
67
62
  pass
68
63
 
69
64
 
70
- class PostgreSQLHandler(Handler[PostgreSQLConfig, PostgreSQLManager]):
71
- @classmethod
72
- def new(
73
- cls,
74
- config: PostgreSQLConfig,
75
- logger: Database,
76
- metadata: MetaData,
77
- service_context: Optional[ServiceContext] = None,
78
- ) -> "PostgreSQLHandler":
79
- manager = PostgreSQLManager(
80
- config=config,
81
- logger=logger,
82
- metadata=metadata,
83
- service_context=service_context,
84
- )
85
- return cls(config=config, manager=manager)
86
-
87
-
88
- class SQLiteHandler(Handler[SQLiteConfig, SQLiteManager]):
65
+ class SQLiteHandler(Handler[SQLiteConfig, SQLiteManager[T]], Generic[T]):
89
66
  pass
90
67
 
91
68
 
92
- class SQLServerHandler(Handler[SQLServerConfig, SQLServerManager]):
69
+ class SQLServerHandler(Handler[SQLServerConfig, SQLServerManager[T]], Generic[T]):
93
70
  pass
94
71
 
95
72
 
@@ -6,8 +6,9 @@ from motor.motor_asyncio import AsyncIOMotorClient
6
6
  from pymongo import MongoClient
7
7
  from redis.asyncio import Redis as AsyncRedis
8
8
  from redis import Redis as SyncRedis
9
- from sqlalchemy import MetaData, text
10
- from typing import Generic, Optional, TypeVar
9
+ from sqlalchemy.orm import DeclarativeBase
10
+ from sqlalchemy import text
11
+ from typing import Generic, Optional, Type, TypeVar
11
12
  from uuid import uuid4
12
13
  from maleo.dtos.authentication import GenericAuthentication
13
14
  from maleo.dtos.contexts.operation import generate_operation_context
@@ -96,16 +97,18 @@ class Manager(ABC, Generic[ConfigT]):
96
97
  pass
97
98
 
98
99
 
99
- class SQLManager(Manager[SQLConfigT], Generic[SQLConfigT]):
100
+ T = TypeVar("T", bound=DeclarativeBase)
101
+
102
+
103
+ class SQLManager(Manager[SQLConfigT], Generic[SQLConfigT, T]):
100
104
  def __init__(
101
105
  self,
106
+ Base: Type[T],
102
107
  config: SQLConfigT,
103
108
  logger: Database,
104
- metadata: MetaData,
105
109
  service_context: Optional[ServiceContext] = None,
106
110
  ) -> None:
107
111
  super().__init__(config, logger, service_context)
108
- self._metadata = metadata
109
112
  self._operation_context.target.details = self._config.model_dump()
110
113
  self._engine_manager = EngineManager[SQLConfigT](config)
111
114
  self._session_manager = SessionManager(
@@ -114,7 +117,8 @@ class SQLManager(Manager[SQLConfigT], Generic[SQLConfigT]):
114
117
  logger=self._logger,
115
118
  service_context=self._service_context,
116
119
  )
117
- self._metadata.create_all(bind=self._engine_manager.get(Connection.SYNC))
120
+ self.Base = Base
121
+ self.Base.metadata.create_all(bind=self._engine_manager.get(Connection.SYNC))
118
122
 
119
123
  @property
120
124
  def engine(self) -> EngineManager[SQLConfigT]:
@@ -251,19 +255,19 @@ class SQLManager(Manager[SQLConfigT], Generic[SQLConfigT]):
251
255
  await self._engine_manager.dispose()
252
256
 
253
257
 
254
- class MySQLManager(SQLManager[MySQLConfig]):
258
+ class MySQLManager(SQLManager[MySQLConfig, T], Generic[T]):
255
259
  pass
256
260
 
257
261
 
258
- class PostgreSQLManager(SQLManager[PostgreSQLConfig]):
262
+ class PostgreSQLManager(SQLManager[PostgreSQLConfig, T], Generic[T]):
259
263
  pass
260
264
 
261
265
 
262
- class SQLiteManager(SQLManager[SQLiteConfig]):
266
+ class SQLiteManager(SQLManager[SQLiteConfig, T], Generic[T]):
263
267
  pass
264
268
 
265
269
 
266
- class SQLServerManager(SQLManager[SQLServerConfig]):
270
+ class SQLServerManager(SQLManager[SQLServerConfig, T], Generic[T]):
267
271
  pass
268
272
 
269
273
 
@@ -92,7 +92,7 @@ class SessionManager(Generic[SQLConfigT]):
92
92
  operation_action = SystemOperationAction(
93
93
  type=SystemOperationType.DATABASE_CONNECTION, details=None
94
94
  )
95
- session = self._async_sessionmaker()
95
+ session: AsyncSession = self._async_sessionmaker()
96
96
  SuccessfulSystemOperation[
97
97
  Optional[GenericAuthentication], NoDataResponse[None]
98
98
  ](
@@ -111,8 +111,9 @@ class SessionManager(Generic[SQLConfigT]):
111
111
 
112
112
  executed_at = datetime.now(tz=timezone.utc)
113
113
  try:
114
- yield session # Provide session
115
- await session.commit() # Auto-commit on success
114
+ # explicit transaction context — will commit on success, rollback on exception
115
+ async with session.begin():
116
+ yield session
116
117
  SuccessfulSystemOperation[
117
118
  Optional[GenericAuthentication], NoDataResponse[None]
118
119
  ](
@@ -129,7 +130,11 @@ class SessionManager(Generic[SQLConfigT]):
129
130
  self._logger, level=Level.INFO
130
131
  )
131
132
  except SQLAlchemyError as se:
132
- await session.rollback() # Rollback on error
133
+ # session.begin() will rollback, but keep explicit rollback to be safe
134
+ try:
135
+ await session.rollback()
136
+ except Exception:
137
+ pass
133
138
  error = DatabaseError[Optional[GenericAuthentication]](
134
139
  OperationType.SYSTEM,
135
140
  service_context=self._service_context,
@@ -152,7 +157,10 @@ class SessionManager(Generic[SQLConfigT]):
152
157
  operation.log(self._logger, level=Level.ERROR)
153
158
  raise error from se
154
159
  except ValidationError as ve:
155
- await session.rollback() # Rollback on error
160
+ try:
161
+ await session.rollback()
162
+ except Exception:
163
+ pass
156
164
  error = UnprocessableEntity[Optional[GenericAuthentication]](
157
165
  OperationType.SYSTEM,
158
166
  service_context=self._service_context,
@@ -169,9 +177,16 @@ class SessionManager(Generic[SQLConfigT]):
169
177
  operation.log(self._logger, level=Level.ERROR)
170
178
  raise error from ve
171
179
  except MaleoException:
180
+ try:
181
+ await session.rollback()
182
+ except Exception:
183
+ pass
172
184
  raise
173
185
  except Exception as e:
174
- await session.rollback() # Rollback on error
186
+ try:
187
+ await session.rollback()
188
+ except Exception:
189
+ pass
175
190
  error = InternalServerError[Optional[GenericAuthentication]](
176
191
  OperationType.SYSTEM,
177
192
  service_context=self._service_context,
@@ -194,7 +209,11 @@ class SessionManager(Generic[SQLConfigT]):
194
209
  operation.log(self._logger, level=Level.ERROR)
195
210
  raise error from e
196
211
  finally:
197
- await session.close() # Ensure session closes
212
+ # close session
213
+ try:
214
+ await session.close()
215
+ except Exception:
216
+ pass
198
217
  SuccessfulSystemOperation[
199
218
  Optional[GenericAuthentication], NoDataResponse[None]
200
219
  ](
@@ -222,7 +241,7 @@ class SessionManager(Generic[SQLConfigT]):
222
241
  operation_action = SystemOperationAction(
223
242
  type=SystemOperationType.DATABASE_CONNECTION, details=None
224
243
  )
225
- session = self._sync_sessionmaker()
244
+ session: Session = self._sync_sessionmaker()
226
245
  SuccessfulSystemOperation[
227
246
  Optional[GenericAuthentication], NoDataResponse[None]
228
247
  ](
@@ -241,8 +260,9 @@ class SessionManager(Generic[SQLConfigT]):
241
260
 
242
261
  executed_at = datetime.now(tz=timezone.utc)
243
262
  try:
244
- yield session # Provide session
245
- session.commit() # Auto-commit on success
263
+ # explicit transaction context — will commit on success, rollback on exception
264
+ with session.begin():
265
+ yield session
246
266
  SuccessfulSystemOperation[
247
267
  Optional[GenericAuthentication], NoDataResponse[None]
248
268
  ](
@@ -0,0 +1,91 @@
1
+ from datetime import datetime
2
+ from sqlalchemy import func
3
+ from sqlalchemy.dialects.postgresql import UUID as PostgresUUID
4
+ from sqlalchemy.orm import Mapped, declared_attr, mapped_column
5
+ from sqlalchemy.types import Integer, Enum, TIMESTAMP
6
+ from maleo.enums.status import DataStatus as DataStatusEnum
7
+ from uuid import UUID as PythonUUID, uuid4
8
+ from maleo.types.base.datetime import OptionalDatetime
9
+ from maleo.utils.formatters.case import to_snake
10
+
11
+
12
+ class TableName:
13
+ @declared_attr.directive
14
+ def __tablename__(cls) -> str:
15
+ return to_snake(cls.__name__) # type: ignore
16
+
17
+
18
+ class DataIdentifier:
19
+ id: Mapped[int] = mapped_column("id", Integer, primary_key=True)
20
+ uuid: Mapped[PythonUUID] = mapped_column(
21
+ "uuid", PostgresUUID(as_uuid=True), default=uuid4, unique=True, nullable=False
22
+ )
23
+
24
+
25
+ class DataStatus:
26
+ status: Mapped[DataStatusEnum] = mapped_column(
27
+ "status",
28
+ Enum(DataStatusEnum, name="statustype", create_constraints=True),
29
+ default=DataStatusEnum.ACTIVE,
30
+ nullable=False,
31
+ )
32
+
33
+
34
+ class CreationTimestamp:
35
+ created_at: Mapped[datetime] = mapped_column(
36
+ "created_at",
37
+ TIMESTAMP(timezone=True),
38
+ server_default=func.now(),
39
+ nullable=False,
40
+ )
41
+
42
+
43
+ class UpdateTimestamp:
44
+ updated_at: Mapped[datetime] = mapped_column(
45
+ "updated_at",
46
+ TIMESTAMP(timezone=True),
47
+ server_default=func.now(),
48
+ onupdate=func.now(),
49
+ nullable=False,
50
+ )
51
+
52
+
53
+ class LifecyleTimestamp(UpdateTimestamp, CreationTimestamp):
54
+ pass
55
+
56
+
57
+ class DeletionTimestamp:
58
+ deleted_at: Mapped[OptionalDatetime] = mapped_column(
59
+ "deleted_at", TIMESTAMP(timezone=True)
60
+ )
61
+
62
+
63
+ class RestorationTimestamp:
64
+ restored_at: Mapped[OptionalDatetime] = mapped_column(
65
+ "restored_at", TIMESTAMP(timezone=True)
66
+ )
67
+
68
+
69
+ class DeactivationTimestamp:
70
+ deactivated_at: Mapped[OptionalDatetime] = mapped_column(
71
+ "deactivated_at", TIMESTAMP(timezone=True)
72
+ )
73
+
74
+
75
+ class ActivationTimestamp:
76
+ activated_at: Mapped[datetime] = mapped_column(
77
+ "activated_at",
78
+ TIMESTAMP(timezone=True),
79
+ server_default=func.now(),
80
+ nullable=False,
81
+ )
82
+
83
+
84
+ class StatusTimestamp(
85
+ ActivationTimestamp, DeactivationTimestamp, RestorationTimestamp, DeletionTimestamp
86
+ ):
87
+ pass
88
+
89
+
90
+ class DataTimestamp(StatusTimestamp, LifecyleTimestamp):
91
+ pass
@@ -1,10 +1,9 @@
1
- from sqlalchemy import Column, Table
2
- from sqlalchemy.ext.declarative import DeclarativeMeta
3
- from sqlalchemy.orm import Query, Session, aliased
1
+ from sqlalchemy import asc, cast, desc, or_, select
2
+ from sqlalchemy.orm import DeclarativeBase, Session, aliased
4
3
  from sqlalchemy.orm.attributes import InstrumentedAttribute
5
- from sqlalchemy.sql.expression import or_, asc, cast, desc
4
+ from sqlalchemy.sql import Select
6
5
  from sqlalchemy.types import DATE, String, TEXT, TIMESTAMP
7
- from typing import Sequence, Type
6
+ from typing import Any, Sequence, Tuple, Type, TypeVar
8
7
  from maleo.enums.sort import Order
9
8
  from maleo.mixins.general import DateFilter, SortColumn
10
9
  from maleo.types.base.any import OptionalAny
@@ -14,16 +13,22 @@ from maleo.types.base.string import OptionalListOfStrings, OptionalString
14
13
  from maleo.types.enums.status import OptionalListOfDataStatuses
15
14
 
16
15
 
16
+ T = TypeVar("T", bound=DeclarativeBase)
17
+ R = TypeVar(
18
+ "R", bound=Tuple[Any, ...]
19
+ ) # result shape inferred from Select (tuple[...])
20
+
21
+
17
22
  def filter_column(
18
- query: Query,
19
- table: Type[DeclarativeMeta],
23
+ stmt: Select[R],
24
+ table: Type[T],
20
25
  column: str,
21
26
  value: OptionalAny = None,
22
27
  include_null: bool = False,
23
- ) -> Query:
28
+ ) -> Select[R]:
24
29
  column_attr = getattr(table, column, None)
25
30
  if column_attr is None or not isinstance(column_attr, InstrumentedAttribute):
26
- return query
31
+ return stmt
27
32
 
28
33
  value_filters = []
29
34
  if value is not None:
@@ -32,21 +37,21 @@ def filter_column(
32
37
  if value_filters:
33
38
  if include_null:
34
39
  value_filters.append(column_attr.is_(None))
35
- query = query.filter(or_(*value_filters))
40
+ stmt = stmt.filter(or_(*value_filters))
36
41
 
37
- return query
42
+ return stmt
38
43
 
39
44
 
40
45
  def filter_ids(
41
- query: Query,
42
- table: Type[DeclarativeMeta],
46
+ stmt: Select[R],
47
+ table: Type[T],
43
48
  column: str,
44
49
  ids: OptionalListOfIntegers = None,
45
50
  include_null: bool = False,
46
- ) -> Query:
51
+ ) -> Select[R]:
47
52
  column_attr = getattr(table, column, None)
48
53
  if column_attr is None or not isinstance(column_attr, InstrumentedAttribute):
49
- return query
54
+ return stmt
50
55
 
51
56
  id_filters = []
52
57
  if ids is not None:
@@ -55,180 +60,177 @@ def filter_ids(
55
60
  if id_filters:
56
61
  if include_null:
57
62
  id_filters.append(column_attr.is_(None))
58
- query = query.filter(or_(*id_filters))
63
+ stmt = stmt.filter(or_(*id_filters))
59
64
 
60
- return query
65
+ return stmt
61
66
 
62
67
 
63
68
  def filter_timestamps(
64
- query: Query,
65
- table: Type[DeclarativeMeta], # type: ignore
69
+ stmt: Select[R],
70
+ table: Type[T],
66
71
  date_filters: Sequence[DateFilter],
67
- ) -> Query:
68
- if date_filters and len(date_filters) > 0:
72
+ ) -> Select[R]:
73
+ if date_filters:
69
74
  for date_filter in date_filters:
70
75
  try:
71
- table: Table = table.__table__ # type: ignore
72
- column: Column = table.columns[date_filter.name]
76
+ sqla_table = table.__table__
77
+ column = sqla_table.columns[date_filter.name]
73
78
  column_attr: InstrumentedAttribute = getattr(table, date_filter.name)
74
79
  if isinstance(column.type, (TIMESTAMP, DATE)):
75
80
  if date_filter.from_date and date_filter.to_date:
76
- query = query.filter(
81
+ stmt = stmt.filter(
77
82
  column_attr.between(
78
83
  date_filter.from_date, date_filter.to_date
79
84
  )
80
85
  )
81
86
  elif date_filter.from_date:
82
- query = query.filter(column_attr >= date_filter.from_date)
87
+ stmt = stmt.filter(column_attr >= date_filter.from_date)
83
88
  elif date_filter.to_date:
84
- query = query.filter(column_attr <= date_filter.to_date)
89
+ stmt = stmt.filter(column_attr <= date_filter.to_date)
85
90
  except KeyError:
86
91
  continue
87
- return query
92
+ return stmt
88
93
 
89
94
 
90
95
  def filter_statuses(
91
- query: Query,
92
- table: Type[DeclarativeMeta],
96
+ stmt: Select[R],
97
+ table: Type[T],
93
98
  statuses: OptionalListOfDataStatuses,
94
- ) -> Query:
99
+ ) -> Select[R]:
95
100
  if statuses is not None:
96
101
  status_filters = [table.status == status for status in statuses] # type: ignore
97
- query = query.filter(or_(*status_filters))
98
- return query
102
+ stmt = stmt.filter(or_(*status_filters))
103
+ return stmt
99
104
 
100
105
 
101
106
  def filter_is_root(
102
- query: Query,
103
- table: Type[DeclarativeMeta],
107
+ stmt: Select[R],
108
+ table: Type[T],
104
109
  parent_column: str = "parent_id",
105
110
  is_root: OptionalBoolean = None,
106
- ) -> Query:
111
+ ) -> Select[R]:
107
112
  parent_attr = getattr(table, parent_column, None)
108
113
  if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
109
- return query
114
+ return stmt
110
115
  if is_root is not None:
111
- query = query.filter(
116
+ stmt = stmt.filter(
112
117
  parent_attr.is_(None) if is_root else parent_attr.is_not(None)
113
118
  )
114
- return query
119
+ return stmt
115
120
 
116
121
 
117
122
  def filter_is_parent(
118
123
  session: Session,
119
- query: Query,
120
- table: Type[DeclarativeMeta],
124
+ stmt: Select[R],
125
+ table: Type[T],
121
126
  id_column: str = "id",
122
127
  parent_column: str = "parent_id",
123
128
  is_parent: OptionalBoolean = None,
124
- ) -> Query:
129
+ ) -> Select[R]:
125
130
  id_attr = getattr(table, id_column, None)
126
131
  if id_attr is None or not isinstance(id_attr, InstrumentedAttribute):
127
- return query
132
+ return stmt
128
133
  parent_attr = getattr(table, parent_column, None)
129
134
  if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
130
- return query
135
+ return stmt
131
136
  if is_parent is not None:
132
137
  child_table = aliased(table)
133
138
  child_parent_attr = getattr(child_table, parent_column)
134
- subq = session.query(child_table).filter(child_parent_attr == id_attr).exists()
135
- query = query.filter(subq if is_parent else ~subq)
136
- return query
139
+ subq = select(child_table).filter(child_parent_attr == id_attr).exists()
140
+ stmt = stmt.filter(subq if is_parent else ~subq)
141
+ return stmt
137
142
 
138
143
 
139
144
  def filter_is_child(
140
- query: Query,
141
- table: Type[DeclarativeMeta],
145
+ stmt: Select[R],
146
+ table: Type[T],
142
147
  parent_column: str = "parent_id",
143
148
  is_child: OptionalBoolean = None,
144
- ) -> Query:
149
+ ) -> Select[R]:
145
150
  parent_attr = getattr(table, parent_column, None)
146
151
  if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
147
- return query
152
+ return stmt
148
153
  if is_child is not None:
149
- query = query.filter(
154
+ stmt = stmt.filter(
150
155
  parent_attr.is_not(None) if is_child else parent_attr.is_(None)
151
156
  )
152
- return query
157
+ return stmt
153
158
 
154
159
 
155
160
  def filter_is_leaf(
156
161
  session: Session,
157
- query: Query,
158
- table: Type[DeclarativeMeta],
162
+ stmt: Select[R],
163
+ table: Type[T],
159
164
  id_column: str = "id",
160
165
  parent_column: str = "parent_id",
161
166
  is_leaf: OptionalBoolean = None,
162
- ) -> Query:
167
+ ) -> Select[R]:
163
168
  id_attr = getattr(table, id_column, None)
164
169
  if id_attr is None or not isinstance(id_attr, InstrumentedAttribute):
165
- return query
170
+ return stmt
166
171
  parent_attr = getattr(table, parent_column, None)
167
172
  if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
168
- return query
173
+ return stmt
169
174
  if is_leaf is not None:
170
175
  child_table = aliased(table)
171
176
  child_parent_attr = getattr(child_table, parent_column)
172
- subq = session.query(child_table).filter(child_parent_attr == id_attr).exists()
173
- query = query.filter(~subq if is_leaf else subq)
174
- return query
177
+ subq = select(child_table).filter(child_parent_attr == id_attr).exists()
178
+ stmt = stmt.filter(~subq if is_leaf else subq)
179
+ return stmt
175
180
 
176
181
 
177
182
  def search(
178
- query: Query,
179
- table: Type[DeclarativeMeta],
183
+ stmt: Select[R],
184
+ table: Type[T],
180
185
  search: OptionalString = None,
181
186
  columns: OptionalListOfStrings = None,
182
- ) -> Query:
187
+ ) -> Select[R]:
183
188
  if search is None:
184
- return query
189
+ return stmt
185
190
 
186
191
  search_term = f"%{search}%"
187
- sqla_table: Table = table.__table__ # type: ignore
192
+ sqla_table = table.__table__
188
193
  search_filters = []
189
194
 
190
195
  for name, attr in vars(table).items():
191
- # Only consider InstrumentedAttribute (mapped columns)
192
196
  if not isinstance(attr, InstrumentedAttribute):
193
197
  continue
194
198
 
195
199
  try:
196
- column: Column = sqla_table.columns[name]
200
+ column = sqla_table.columns[name]
197
201
  except KeyError:
198
202
  continue
199
203
 
200
- # Skip columns not in the user-provided list
201
204
  if columns is not None and name not in columns:
202
205
  continue
203
206
 
204
- # Only allow string/TEXT columns
205
207
  if isinstance(column.type, (String, TEXT)):
206
208
  search_filters.append(cast(attr, TEXT).ilike(search_term))
207
209
 
208
210
  if search_filters:
209
- query = query.filter(or_(*search_filters))
211
+ stmt = stmt.filter(or_(*search_filters))
210
212
 
211
- return query
213
+ return stmt
212
214
 
213
215
 
214
216
  def sort(
215
- query: Query,
216
- table: Type[DeclarativeMeta],
217
+ stmt: Select[R],
218
+ table: Type[T],
217
219
  sort_columns: Sequence[SortColumn],
218
- ) -> Query:
220
+ ) -> Select[R]:
219
221
  for sort_column in sort_columns:
220
222
  try:
221
223
  sort_col = getattr(table, sort_column.name)
222
224
  sort_col = (
223
225
  asc(sort_col) if sort_column.order is Order.ASC else desc(sort_col)
224
226
  )
225
- query = query.order_by(sort_col)
227
+ stmt = stmt.order_by(sort_col)
226
228
  except AttributeError:
227
229
  continue
228
- return query
230
+ return stmt
229
231
 
230
232
 
231
- def paginate(query: Query, page: int, limit: int) -> Query:
232
- offset: int = int((page - 1) * limit) # Calculate offset based on page
233
- query = query.limit(limit=limit).offset(offset=offset)
234
- return query
233
+ def paginate(stmt: Select[R], page: int, limit: int) -> Select[R]:
234
+ offset: int = int((page - 1) * limit)
235
+ stmt = stmt.limit(limit).offset(offset)
236
+ return stmt
@@ -1,7 +0,0 @@
1
- from sqlalchemy.orm import DeclarativeMeta, declarative_base
2
- from typing import Any, Type
3
- from .models.table import BaseTable
4
-
5
-
6
- def create_base(cls: Type[Any] = BaseTable) -> DeclarativeMeta:
7
- return declarative_base(cls=cls)
File without changes
File without changes
@@ -1,11 +0,0 @@
1
- from sqlalchemy.dialects.postgresql import UUID as PostgresUUID
2
- from sqlalchemy.orm import Mapped, mapped_column
3
- from sqlalchemy.types import Integer
4
- from uuid import UUID as PythonUUID, uuid4
5
-
6
-
7
- class DataIdentifier:
8
- id: Mapped[int] = mapped_column("id", Integer, primary_key=True)
9
- uuid: Mapped[PythonUUID] = mapped_column(
10
- "uuid", PostgresUUID(as_uuid=True), default=uuid4, unique=True, nullable=False
11
- )
@@ -1,12 +0,0 @@
1
- from sqlalchemy.orm import Mapped, mapped_column
2
- from sqlalchemy.types import Enum
3
- from maleo.enums.status import DataStatus as DataStatusEnum
4
-
5
-
6
- class DataStatus:
7
- status: Mapped[DataStatusEnum] = mapped_column(
8
- "status",
9
- Enum(DataStatusEnum, name="statustype", create_constraints=True),
10
- default=DataStatusEnum.ACTIVE,
11
- nullable=False,
12
- )
@@ -1,65 +0,0 @@
1
- from datetime import datetime
2
- from sqlalchemy import func
3
- from sqlalchemy.orm import Mapped, mapped_column
4
- from sqlalchemy.types import TIMESTAMP
5
- from maleo.types.base.datetime import OptionalDatetime
6
-
7
-
8
- class CreateTimestamp:
9
- created_at: Mapped[datetime] = mapped_column(
10
- "created_at",
11
- TIMESTAMP(timezone=True),
12
- server_default=func.now(),
13
- nullable=False,
14
- )
15
-
16
-
17
- class UpdateTimestamp:
18
- updated_at: Mapped[datetime] = mapped_column(
19
- "updated_at",
20
- TIMESTAMP(timezone=True),
21
- server_default=func.now(),
22
- onupdate=func.now(),
23
- nullable=False,
24
- )
25
-
26
-
27
- class LifecyleTimestamp(UpdateTimestamp, CreateTimestamp):
28
- pass
29
-
30
-
31
- class DeleteTimestamp:
32
- deleted_at: Mapped[OptionalDatetime] = mapped_column(
33
- "deleted_at", TIMESTAMP(timezone=True)
34
- )
35
-
36
-
37
- class RestoreTimestamp:
38
- restored_at: Mapped[OptionalDatetime] = mapped_column(
39
- "restored_at", TIMESTAMP(timezone=True)
40
- )
41
-
42
-
43
- class DeactivateTimestamp:
44
- deactivated_at: Mapped[OptionalDatetime] = mapped_column(
45
- "deactivated_at", TIMESTAMP(timezone=True)
46
- )
47
-
48
-
49
- class ActivateTimestamp:
50
- activated_at: Mapped[datetime] = mapped_column(
51
- "activated_at",
52
- TIMESTAMP(timezone=True),
53
- server_default=func.now(),
54
- nullable=False,
55
- )
56
-
57
-
58
- class StatusTimestamp(
59
- ActivateTimestamp, DeactivateTimestamp, RestoreTimestamp, DeleteTimestamp
60
- ):
61
- pass
62
-
63
-
64
- class DataTimestamp(StatusTimestamp, LifecyleTimestamp):
65
- pass
@@ -1,17 +0,0 @@
1
- from sqlalchemy.orm import declared_attr
2
- from maleo.utils.formatters.case import to_snake
3
- from .mixins.identifier import DataIdentifier
4
- from .mixins.status import DataStatus
5
- from .mixins.timestamp import DataTimestamp
6
-
7
-
8
- class BaseTable:
9
- __abstract__ = True
10
-
11
- @declared_attr # type: ignore
12
- def __tablename__(cls) -> str:
13
- return to_snake(cls.__name__) # type: ignore
14
-
15
-
16
- class DataTable(DataStatus, DataTimestamp, DataIdentifier):
17
- pass
File without changes