maleo-database 0.0.20__py3-none-any.whl → 0.0.22__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.
- maleo/database/handlers.py +12 -35
- maleo/database/managers/__init__.py +14 -10
- maleo/database/managers/session.py +30 -10
- maleo/database/orm/mixins.py +91 -0
- maleo/database/orm/queries.py +82 -80
- {maleo_database-0.0.20.dist-info → maleo_database-0.0.22.dist-info}/METADATA +1 -1
- maleo_database-0.0.22.dist-info/RECORD +21 -0
- maleo/database/orm/base.py +0 -7
- maleo/database/orm/models/__init__.py +0 -0
- maleo/database/orm/models/mixins/__init__.py +0 -0
- maleo/database/orm/models/mixins/identifier.py +0 -11
- maleo/database/orm/models/mixins/status.py +0 -12
- maleo/database/orm/models/mixins/timestamp.py +0 -65
- maleo/database/orm/models/table.py +0 -17
- maleo_database-0.0.20.dist-info/RECORD +0 -27
- {maleo_database-0.0.20.dist-info → maleo_database-0.0.22.dist-info}/WHEEL +0 -0
- {maleo_database-0.0.20.dist-info → maleo_database-0.0.22.dist-info}/licenses/LICENSE +0 -0
- {maleo_database-0.0.20.dist-info → maleo_database-0.0.22.dist-info}/top_level.txt +0 -0
maleo/database/handlers.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
from pydantic import BaseModel, ConfigDict, Field
|
2
|
-
from sqlalchemy import
|
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
|
-
|
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
|
-
|
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
|
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
|
10
|
-
from
|
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
|
-
|
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.
|
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
|
-
|
115
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
245
|
-
session.
|
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
|
maleo/database/orm/queries.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
from sqlalchemy import
|
2
|
-
from sqlalchemy.
|
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
|
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
|
-
|
19
|
-
table: Type[
|
23
|
+
stmt: Select[R],
|
24
|
+
table: Type[T],
|
20
25
|
column: str,
|
21
26
|
value: OptionalAny = None,
|
22
27
|
include_null: bool = False,
|
23
|
-
) ->
|
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
|
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
|
-
|
40
|
+
stmt = stmt.filter(or_(*value_filters))
|
36
41
|
|
37
|
-
return
|
42
|
+
return stmt
|
38
43
|
|
39
44
|
|
40
45
|
def filter_ids(
|
41
|
-
|
42
|
-
table: Type[
|
46
|
+
stmt: Select[R],
|
47
|
+
table: Type[T],
|
43
48
|
column: str,
|
44
49
|
ids: OptionalListOfIntegers = None,
|
45
50
|
include_null: bool = False,
|
46
|
-
) ->
|
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
|
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
|
-
|
63
|
+
stmt = stmt.filter(or_(*id_filters))
|
59
64
|
|
60
|
-
return
|
65
|
+
return stmt
|
61
66
|
|
62
67
|
|
63
68
|
def filter_timestamps(
|
64
|
-
|
65
|
-
table: Type[
|
69
|
+
stmt: Select[R],
|
70
|
+
table: Type[T],
|
66
71
|
date_filters: Sequence[DateFilter],
|
67
|
-
) ->
|
68
|
-
if date_filters
|
72
|
+
) -> Select[R]:
|
73
|
+
if date_filters:
|
69
74
|
for date_filter in date_filters:
|
70
75
|
try:
|
71
|
-
|
72
|
-
column
|
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
|
-
|
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
|
-
|
87
|
+
stmt = stmt.filter(column_attr >= date_filter.from_date)
|
83
88
|
elif date_filter.to_date:
|
84
|
-
|
89
|
+
stmt = stmt.filter(column_attr <= date_filter.to_date)
|
85
90
|
except KeyError:
|
86
91
|
continue
|
87
|
-
return
|
92
|
+
return stmt
|
88
93
|
|
89
94
|
|
90
95
|
def filter_statuses(
|
91
|
-
|
92
|
-
table: Type[
|
96
|
+
stmt: Select[R],
|
97
|
+
table: Type[T],
|
93
98
|
statuses: OptionalListOfDataStatuses,
|
94
|
-
) ->
|
99
|
+
) -> Select[R]:
|
95
100
|
if statuses is not None:
|
96
101
|
status_filters = [table.status == status for status in statuses] # type: ignore
|
97
|
-
|
98
|
-
return
|
102
|
+
stmt = stmt.filter(or_(*status_filters))
|
103
|
+
return stmt
|
99
104
|
|
100
105
|
|
101
106
|
def filter_is_root(
|
102
|
-
|
103
|
-
table: Type[
|
107
|
+
stmt: Select[R],
|
108
|
+
table: Type[T],
|
104
109
|
parent_column: str = "parent_id",
|
105
110
|
is_root: OptionalBoolean = None,
|
106
|
-
) ->
|
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
|
114
|
+
return stmt
|
110
115
|
if is_root is not None:
|
111
|
-
|
116
|
+
stmt = stmt.filter(
|
112
117
|
parent_attr.is_(None) if is_root else parent_attr.is_not(None)
|
113
118
|
)
|
114
|
-
return
|
119
|
+
return stmt
|
115
120
|
|
116
121
|
|
117
122
|
def filter_is_parent(
|
118
123
|
session: Session,
|
119
|
-
|
120
|
-
table: Type[
|
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
|
-
) ->
|
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
|
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
|
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 =
|
135
|
-
|
136
|
-
return
|
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
|
-
|
141
|
-
table: Type[
|
145
|
+
stmt: Select[R],
|
146
|
+
table: Type[T],
|
142
147
|
parent_column: str = "parent_id",
|
143
148
|
is_child: OptionalBoolean = None,
|
144
|
-
) ->
|
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
|
152
|
+
return stmt
|
148
153
|
if is_child is not None:
|
149
|
-
|
154
|
+
stmt = stmt.filter(
|
150
155
|
parent_attr.is_not(None) if is_child else parent_attr.is_(None)
|
151
156
|
)
|
152
|
-
return
|
157
|
+
return stmt
|
153
158
|
|
154
159
|
|
155
160
|
def filter_is_leaf(
|
156
161
|
session: Session,
|
157
|
-
|
158
|
-
table: Type[
|
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
|
-
) ->
|
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
|
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
|
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 =
|
173
|
-
|
174
|
-
return
|
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
|
-
|
179
|
-
table: Type[
|
183
|
+
stmt: Select[R],
|
184
|
+
table: Type[T],
|
180
185
|
search: OptionalString = None,
|
181
186
|
columns: OptionalListOfStrings = None,
|
182
|
-
) ->
|
187
|
+
) -> Select[R]:
|
183
188
|
if search is None:
|
184
|
-
return
|
189
|
+
return stmt
|
185
190
|
|
186
191
|
search_term = f"%{search}%"
|
187
|
-
sqla_table
|
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
|
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
|
-
|
211
|
+
stmt = stmt.filter(or_(*search_filters))
|
210
212
|
|
211
|
-
return
|
213
|
+
return stmt
|
212
214
|
|
213
215
|
|
214
216
|
def sort(
|
215
|
-
|
216
|
-
table: Type[
|
217
|
+
stmt: Select[R],
|
218
|
+
table: Type[T],
|
217
219
|
sort_columns: Sequence[SortColumn],
|
218
|
-
) ->
|
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
|
-
|
227
|
+
stmt = stmt.order_by(sort_col)
|
226
228
|
except AttributeError:
|
227
229
|
continue
|
228
|
-
return
|
230
|
+
return stmt
|
229
231
|
|
230
232
|
|
231
|
-
def paginate(
|
232
|
-
offset: int = int((page - 1) * limit)
|
233
|
-
|
234
|
-
return
|
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
maleo/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
maleo/database/dtos.py,sha256=DArPtSXzPJd896i-t4OmoqBP1mW-it8NVxvfWq7wq8Y,220
|
3
|
+
maleo/database/enums.py,sha256=tHJugOgGWEarwyHbUXmqK-AFXO-D-pmhIdXHkE81ik8,1691
|
4
|
+
maleo/database/handlers.py,sha256=pPN2cxXZaok1yttj9O8L83rFcbW19FGLUoFeUlMDNIM,3937
|
5
|
+
maleo/database/config/__init__.py,sha256=ygBWG1iq9tPCKo_mtcWgd5jcJ6gKhHmxXuZOmLuL2Hc,11102
|
6
|
+
maleo/database/config/additional.py,sha256=8uTEQDoKjAlXif-1njKuicIT07H_slTWr9pE0OENAU0,1098
|
7
|
+
maleo/database/config/connection.py,sha256=0Cg0XUxf20Q_RVqFmzOtBUXl9m7I-x9BEyEhZVoWEh4,22869
|
8
|
+
maleo/database/config/identifier.py,sha256=b1MjhoKl3h7xJe1eVIj1wjvYH9BrjHzKnjzdjaEaTeQ,626
|
9
|
+
maleo/database/config/pooling.py,sha256=4Q0rkM4FAt6xAbBG1PItHmMSzS7aCZNBgFPeaEjKwTo,11552
|
10
|
+
maleo/database/managers/__init__.py,sha256=lRcOl_ouqs2yQqqCH5-vj1WQXxV2U1tf-X3Ih2Q2Hxs,13207
|
11
|
+
maleo/database/managers/client.py,sha256=hdfPn8Ae8G78yZYDxI1FylcM-rPMIL8Ty4XmsASDrt0,3065
|
12
|
+
maleo/database/managers/engine.py,sha256=aF7JCjSYujodbn2QQrT_OeLX-WjUdIv5cBgWtvLgHI0,1725
|
13
|
+
maleo/database/managers/session.py,sha256=aKyuiHiA171Vptr92AA5U-mRwzwbKXb9RAZ9Wq5Wo4s,19557
|
14
|
+
maleo/database/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
maleo/database/orm/mixins.py,sha256=rW-FVOrRm_R8yJyrcwGoVIPPV1oAOhSVFmANHR7bowU,2372
|
16
|
+
maleo/database/orm/queries.py,sha256=SQEgNzv7f-Twbi2X5P73bBnZRgYfwA9P2KYqSB4SaNc,7326
|
17
|
+
maleo_database-0.0.22.dist-info/licenses/LICENSE,sha256=aftGsecnk7TWVX-7KW94FqK4Syy6YSZ8PZEF7EcIp3M,2621
|
18
|
+
maleo_database-0.0.22.dist-info/METADATA,sha256=qLzPWG-fn5LsHRkRjebaI4JmOQxdHH8AuNauJ6-CKbI,3837
|
19
|
+
maleo_database-0.0.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
20
|
+
maleo_database-0.0.22.dist-info/top_level.txt,sha256=3Tpd1siVsfYoeI9FEOJNYnffx_shzZ3wsPpTvz5bljc,6
|
21
|
+
maleo_database-0.0.22.dist-info/RECORD,,
|
maleo/database/orm/base.py
DELETED
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
|
@@ -1,27 +0,0 @@
|
|
1
|
-
maleo/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
maleo/database/dtos.py,sha256=DArPtSXzPJd896i-t4OmoqBP1mW-it8NVxvfWq7wq8Y,220
|
3
|
-
maleo/database/enums.py,sha256=tHJugOgGWEarwyHbUXmqK-AFXO-D-pmhIdXHkE81ik8,1691
|
4
|
-
maleo/database/handlers.py,sha256=JsFlxvt9idY3cMXhXu_eBtcKGm9l4nyd8Gzw1WOR9vc,4722
|
5
|
-
maleo/database/config/__init__.py,sha256=ygBWG1iq9tPCKo_mtcWgd5jcJ6gKhHmxXuZOmLuL2Hc,11102
|
6
|
-
maleo/database/config/additional.py,sha256=8uTEQDoKjAlXif-1njKuicIT07H_slTWr9pE0OENAU0,1098
|
7
|
-
maleo/database/config/connection.py,sha256=0Cg0XUxf20Q_RVqFmzOtBUXl9m7I-x9BEyEhZVoWEh4,22869
|
8
|
-
maleo/database/config/identifier.py,sha256=b1MjhoKl3h7xJe1eVIj1wjvYH9BrjHzKnjzdjaEaTeQ,626
|
9
|
-
maleo/database/config/pooling.py,sha256=4Q0rkM4FAt6xAbBG1PItHmMSzS7aCZNBgFPeaEjKwTo,11552
|
10
|
-
maleo/database/managers/__init__.py,sha256=45un_rhnAeSFQLxmEsEXNsYLfg6kpWWljsXpqDFWZQk,13073
|
11
|
-
maleo/database/managers/client.py,sha256=hdfPn8Ae8G78yZYDxI1FylcM-rPMIL8Ty4XmsASDrt0,3065
|
12
|
-
maleo/database/managers/engine.py,sha256=aF7JCjSYujodbn2QQrT_OeLX-WjUdIv5cBgWtvLgHI0,1725
|
13
|
-
maleo/database/managers/session.py,sha256=QCyw1Um0xTmEGCHxX3kQ6z7Q_L2TT8Eq3_8NcpKIsxE,19000
|
14
|
-
maleo/database/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
maleo/database/orm/base.py,sha256=qdRK_TQqDoZ1hANvm1mJDiblqFr77J1IhBnlENr7RK8,229
|
16
|
-
maleo/database/orm/queries.py,sha256=NX9PYT6GZgwIhtRc7WSTS9EUzqTZCb-ib99ccYjmUkM,7643
|
17
|
-
maleo/database/orm/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
-
maleo/database/orm/models/table.py,sha256=UhEH7WN1lrNY3uTb4a5VWO4l9UaYhnRQK_0Z7R0lMNA,457
|
19
|
-
maleo/database/orm/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
-
maleo/database/orm/models/mixins/identifier.py,sha256=jICVsLUFw_f7TV_aX4KTTs_ycH35MRtRj5boTqctmZo,425
|
21
|
-
maleo/database/orm/models/mixins/status.py,sha256=Jzithn-Hl2ct1AqboGZSXrA35aaQEDvqtaB8oL7KIyo,376
|
22
|
-
maleo/database/orm/models/mixins/timestamp.py,sha256=TWqBTBvgSxcPsK0m7ggVrkHqzlqCM-2Zox7TAwCxd0I,1500
|
23
|
-
maleo_database-0.0.20.dist-info/licenses/LICENSE,sha256=aftGsecnk7TWVX-7KW94FqK4Syy6YSZ8PZEF7EcIp3M,2621
|
24
|
-
maleo_database-0.0.20.dist-info/METADATA,sha256=4oN0BbLhCOrTN19kraTRCxDWWOhWhN7-3CDZfCNWhAU,3837
|
25
|
-
maleo_database-0.0.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
26
|
-
maleo_database-0.0.20.dist-info/top_level.txt,sha256=3Tpd1siVsfYoeI9FEOJNYnffx_shzZ3wsPpTvz5bljc,6
|
27
|
-
maleo_database-0.0.20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|