maleo-database 0.0.20__tar.gz → 0.0.21__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.
- {maleo_database-0.0.20 → maleo_database-0.0.21}/PKG-INFO +1 -1
- {maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/PKG-INFO +1 -1
- {maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/SOURCES.txt +2 -8
- {maleo_database-0.0.20 → maleo_database-0.0.21}/pyproject.toml +1 -7
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/managers/session.py +30 -10
- maleo_database-0.0.21/src/orm/mixins.py +91 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/orm/queries.py +79 -80
- maleo_database-0.0.20/src/orm/base.py +0 -7
- maleo_database-0.0.20/src/orm/models/__init__.py +0 -0
- maleo_database-0.0.20/src/orm/models/mixins/__init__.py +0 -0
- maleo_database-0.0.20/src/orm/models/mixins/identifier.py +0 -11
- maleo_database-0.0.20/src/orm/models/mixins/status.py +0 -12
- maleo_database-0.0.20/src/orm/models/mixins/timestamp.py +0 -65
- maleo_database-0.0.20/src/orm/models/table.py +0 -17
- {maleo_database-0.0.20 → maleo_database-0.0.21}/LICENSE +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/README.md +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/dependency_links.txt +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/requires.txt +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/top_level.txt +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/setup.cfg +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/__init__.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/config/__init__.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/config/additional.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/config/connection.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/config/identifier.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/config/pooling.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/dtos.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/enums.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/handlers.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/managers/__init__.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/managers/client.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/managers/engine.py +0 -0
- {maleo_database-0.0.20 → maleo_database-0.0.21}/src/orm/__init__.py +0 -0
@@ -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/
|
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.
|
7
|
+
version = "0.0.21"
|
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"
|
@@ -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
|
@@ -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 Sequence, 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,19 @@ 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
|
+
|
18
|
+
|
17
19
|
def filter_column(
|
18
|
-
|
19
|
-
table: Type[
|
20
|
+
stmt: Select,
|
21
|
+
table: Type[T],
|
20
22
|
column: str,
|
21
23
|
value: OptionalAny = None,
|
22
24
|
include_null: bool = False,
|
23
|
-
) ->
|
25
|
+
) -> Select:
|
24
26
|
column_attr = getattr(table, column, None)
|
25
27
|
if column_attr is None or not isinstance(column_attr, InstrumentedAttribute):
|
26
|
-
return
|
28
|
+
return stmt
|
27
29
|
|
28
30
|
value_filters = []
|
29
31
|
if value is not None:
|
@@ -32,21 +34,21 @@ def filter_column(
|
|
32
34
|
if value_filters:
|
33
35
|
if include_null:
|
34
36
|
value_filters.append(column_attr.is_(None))
|
35
|
-
|
37
|
+
stmt = stmt.filter(or_(*value_filters))
|
36
38
|
|
37
|
-
return
|
39
|
+
return stmt
|
38
40
|
|
39
41
|
|
40
42
|
def filter_ids(
|
41
|
-
|
42
|
-
table: Type[
|
43
|
+
stmt: Select,
|
44
|
+
table: Type[T],
|
43
45
|
column: str,
|
44
46
|
ids: OptionalListOfIntegers = None,
|
45
47
|
include_null: bool = False,
|
46
|
-
) ->
|
48
|
+
) -> Select:
|
47
49
|
column_attr = getattr(table, column, None)
|
48
50
|
if column_attr is None or not isinstance(column_attr, InstrumentedAttribute):
|
49
|
-
return
|
51
|
+
return stmt
|
50
52
|
|
51
53
|
id_filters = []
|
52
54
|
if ids is not None:
|
@@ -55,180 +57,177 @@ def filter_ids(
|
|
55
57
|
if id_filters:
|
56
58
|
if include_null:
|
57
59
|
id_filters.append(column_attr.is_(None))
|
58
|
-
|
60
|
+
stmt = stmt.filter(or_(*id_filters))
|
59
61
|
|
60
|
-
return
|
62
|
+
return stmt
|
61
63
|
|
62
64
|
|
63
65
|
def filter_timestamps(
|
64
|
-
|
65
|
-
table: Type[
|
66
|
+
stmt: Select,
|
67
|
+
table: Type[T],
|
66
68
|
date_filters: Sequence[DateFilter],
|
67
|
-
) ->
|
68
|
-
if date_filters
|
69
|
+
) -> Select:
|
70
|
+
if date_filters:
|
69
71
|
for date_filter in date_filters:
|
70
72
|
try:
|
71
|
-
|
72
|
-
column
|
73
|
+
sqla_table = table.__table__
|
74
|
+
column = sqla_table.columns[date_filter.name]
|
73
75
|
column_attr: InstrumentedAttribute = getattr(table, date_filter.name)
|
74
76
|
if isinstance(column.type, (TIMESTAMP, DATE)):
|
75
77
|
if date_filter.from_date and date_filter.to_date:
|
76
|
-
|
78
|
+
stmt = stmt.filter(
|
77
79
|
column_attr.between(
|
78
80
|
date_filter.from_date, date_filter.to_date
|
79
81
|
)
|
80
82
|
)
|
81
83
|
elif date_filter.from_date:
|
82
|
-
|
84
|
+
stmt = stmt.filter(column_attr >= date_filter.from_date)
|
83
85
|
elif date_filter.to_date:
|
84
|
-
|
86
|
+
stmt = stmt.filter(column_attr <= date_filter.to_date)
|
85
87
|
except KeyError:
|
86
88
|
continue
|
87
|
-
return
|
89
|
+
return stmt
|
88
90
|
|
89
91
|
|
90
92
|
def filter_statuses(
|
91
|
-
|
92
|
-
table: Type[
|
93
|
+
stmt: Select,
|
94
|
+
table: Type[T],
|
93
95
|
statuses: OptionalListOfDataStatuses,
|
94
|
-
) ->
|
96
|
+
) -> Select:
|
95
97
|
if statuses is not None:
|
96
98
|
status_filters = [table.status == status for status in statuses] # type: ignore
|
97
|
-
|
98
|
-
return
|
99
|
+
stmt = stmt.filter(or_(*status_filters))
|
100
|
+
return stmt
|
99
101
|
|
100
102
|
|
101
103
|
def filter_is_root(
|
102
|
-
|
103
|
-
table: Type[
|
104
|
+
stmt: Select,
|
105
|
+
table: Type[T],
|
104
106
|
parent_column: str = "parent_id",
|
105
107
|
is_root: OptionalBoolean = None,
|
106
|
-
) ->
|
108
|
+
) -> Select:
|
107
109
|
parent_attr = getattr(table, parent_column, None)
|
108
110
|
if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
|
109
|
-
return
|
111
|
+
return stmt
|
110
112
|
if is_root is not None:
|
111
|
-
|
113
|
+
stmt = stmt.filter(
|
112
114
|
parent_attr.is_(None) if is_root else parent_attr.is_not(None)
|
113
115
|
)
|
114
|
-
return
|
116
|
+
return stmt
|
115
117
|
|
116
118
|
|
117
119
|
def filter_is_parent(
|
118
120
|
session: Session,
|
119
|
-
|
120
|
-
table: Type[
|
121
|
+
stmt: Select,
|
122
|
+
table: Type[T],
|
121
123
|
id_column: str = "id",
|
122
124
|
parent_column: str = "parent_id",
|
123
125
|
is_parent: OptionalBoolean = None,
|
124
|
-
) ->
|
126
|
+
) -> Select:
|
125
127
|
id_attr = getattr(table, id_column, None)
|
126
128
|
if id_attr is None or not isinstance(id_attr, InstrumentedAttribute):
|
127
|
-
return
|
129
|
+
return stmt
|
128
130
|
parent_attr = getattr(table, parent_column, None)
|
129
131
|
if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
|
130
|
-
return
|
132
|
+
return stmt
|
131
133
|
if is_parent is not None:
|
132
134
|
child_table = aliased(table)
|
133
135
|
child_parent_attr = getattr(child_table, parent_column)
|
134
|
-
subq =
|
135
|
-
|
136
|
-
return
|
136
|
+
subq = select(child_table).filter(child_parent_attr == id_attr).exists()
|
137
|
+
stmt = stmt.filter(subq if is_parent else ~subq)
|
138
|
+
return stmt
|
137
139
|
|
138
140
|
|
139
141
|
def filter_is_child(
|
140
|
-
|
141
|
-
table: Type[
|
142
|
+
stmt: Select,
|
143
|
+
table: Type[T],
|
142
144
|
parent_column: str = "parent_id",
|
143
145
|
is_child: OptionalBoolean = None,
|
144
|
-
) ->
|
146
|
+
) -> Select:
|
145
147
|
parent_attr = getattr(table, parent_column, None)
|
146
148
|
if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
|
147
|
-
return
|
149
|
+
return stmt
|
148
150
|
if is_child is not None:
|
149
|
-
|
151
|
+
stmt = stmt.filter(
|
150
152
|
parent_attr.is_not(None) if is_child else parent_attr.is_(None)
|
151
153
|
)
|
152
|
-
return
|
154
|
+
return stmt
|
153
155
|
|
154
156
|
|
155
157
|
def filter_is_leaf(
|
156
158
|
session: Session,
|
157
|
-
|
158
|
-
table: Type[
|
159
|
+
stmt: Select,
|
160
|
+
table: Type[T],
|
159
161
|
id_column: str = "id",
|
160
162
|
parent_column: str = "parent_id",
|
161
163
|
is_leaf: OptionalBoolean = None,
|
162
|
-
) ->
|
164
|
+
) -> Select:
|
163
165
|
id_attr = getattr(table, id_column, None)
|
164
166
|
if id_attr is None or not isinstance(id_attr, InstrumentedAttribute):
|
165
|
-
return
|
167
|
+
return stmt
|
166
168
|
parent_attr = getattr(table, parent_column, None)
|
167
169
|
if parent_attr is None or not isinstance(parent_attr, InstrumentedAttribute):
|
168
|
-
return
|
170
|
+
return stmt
|
169
171
|
if is_leaf is not None:
|
170
172
|
child_table = aliased(table)
|
171
173
|
child_parent_attr = getattr(child_table, parent_column)
|
172
|
-
subq =
|
173
|
-
|
174
|
-
return
|
174
|
+
subq = select(child_table).filter(child_parent_attr == id_attr).exists()
|
175
|
+
stmt = stmt.filter(~subq if is_leaf else subq)
|
176
|
+
return stmt
|
175
177
|
|
176
178
|
|
177
179
|
def search(
|
178
|
-
|
179
|
-
table: Type[
|
180
|
+
stmt: Select,
|
181
|
+
table: Type[T],
|
180
182
|
search: OptionalString = None,
|
181
183
|
columns: OptionalListOfStrings = None,
|
182
|
-
) ->
|
184
|
+
) -> Select:
|
183
185
|
if search is None:
|
184
|
-
return
|
186
|
+
return stmt
|
185
187
|
|
186
188
|
search_term = f"%{search}%"
|
187
|
-
sqla_table
|
189
|
+
sqla_table = table.__table__
|
188
190
|
search_filters = []
|
189
191
|
|
190
192
|
for name, attr in vars(table).items():
|
191
|
-
# Only consider InstrumentedAttribute (mapped columns)
|
192
193
|
if not isinstance(attr, InstrumentedAttribute):
|
193
194
|
continue
|
194
195
|
|
195
196
|
try:
|
196
|
-
column
|
197
|
+
column = sqla_table.columns[name]
|
197
198
|
except KeyError:
|
198
199
|
continue
|
199
200
|
|
200
|
-
# Skip columns not in the user-provided list
|
201
201
|
if columns is not None and name not in columns:
|
202
202
|
continue
|
203
203
|
|
204
|
-
# Only allow string/TEXT columns
|
205
204
|
if isinstance(column.type, (String, TEXT)):
|
206
205
|
search_filters.append(cast(attr, TEXT).ilike(search_term))
|
207
206
|
|
208
207
|
if search_filters:
|
209
|
-
|
208
|
+
stmt = stmt.filter(or_(*search_filters))
|
210
209
|
|
211
|
-
return
|
210
|
+
return stmt
|
212
211
|
|
213
212
|
|
214
213
|
def sort(
|
215
|
-
|
216
|
-
table: Type[
|
214
|
+
stmt: Select,
|
215
|
+
table: Type[T],
|
217
216
|
sort_columns: Sequence[SortColumn],
|
218
|
-
) ->
|
217
|
+
) -> Select:
|
219
218
|
for sort_column in sort_columns:
|
220
219
|
try:
|
221
220
|
sort_col = getattr(table, sort_column.name)
|
222
221
|
sort_col = (
|
223
222
|
asc(sort_col) if sort_column.order is Order.ASC else desc(sort_col)
|
224
223
|
)
|
225
|
-
|
224
|
+
stmt = stmt.order_by(sort_col)
|
226
225
|
except AttributeError:
|
227
226
|
continue
|
228
|
-
return
|
227
|
+
return stmt
|
229
228
|
|
230
229
|
|
231
|
-
def paginate(
|
232
|
-
offset: int = int((page - 1) * limit)
|
233
|
-
|
234
|
-
return
|
230
|
+
def paginate(stmt: Select, page: int, limit: int) -> Select:
|
231
|
+
offset: int = int((page - 1) * limit)
|
232
|
+
stmt = stmt.limit(limit).offset(offset)
|
233
|
+
return stmt
|
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
|
File without changes
|
{maleo_database-0.0.20 → maleo_database-0.0.21}/maleo_database.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|