maleo-database 0.0.1__py3-none-any.whl → 0.0.3__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.
@@ -4,18 +4,41 @@ from contextlib import (
4
4
  asynccontextmanager,
5
5
  contextmanager,
6
6
  )
7
+ from datetime import datetime, timezone
7
8
  from pydantic import ValidationError
8
9
  from sqlalchemy.engine import Engine
9
10
  from sqlalchemy.exc import SQLAlchemyError
10
11
  from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, AsyncSession
11
12
  from sqlalchemy.orm import sessionmaker, Session
12
13
  from typing import AsyncGenerator, Generator, Literal, Tuple, Union, overload
14
+ from uuid import UUID, uuid4
15
+ from maleo.mixins.timestamp import OperationTimestamp
16
+ from maleo.types.base.uuid import OptionalUUID
17
+ from maleo.logging.logger import Database
13
18
  from ..enums import Connection
19
+ from ..config import (
20
+ PostgreSQLDatabaseConfig,
21
+ MySQLDatabaseConfig,
22
+ SQLiteDatabaseConfig,
23
+ SQLServerDatabaseConfig,
24
+ )
14
25
 
15
26
 
16
27
  class SessionManager:
17
- def __init__(self, engines: Tuple[AsyncEngine, Engine]) -> None:
28
+ def __init__(
29
+ self,
30
+ config: Union[
31
+ PostgreSQLDatabaseConfig,
32
+ MySQLDatabaseConfig,
33
+ SQLiteDatabaseConfig,
34
+ SQLServerDatabaseConfig,
35
+ ],
36
+ engines: Tuple[AsyncEngine, Engine],
37
+ logger: Database,
38
+ ) -> None:
39
+ self._config = config
18
40
  self._async_engine, self._sync_engine = engines
41
+ self._logger = logger
19
42
  self._async_sessionmaker: async_sessionmaker[AsyncSession] = async_sessionmaker[
20
43
  AsyncSession
21
44
  ](bind=self._async_engine, expire_on_commit=True)
@@ -23,101 +46,211 @@ class SessionManager:
23
46
  bind=self._sync_engine, expire_on_commit=True
24
47
  )
25
48
 
26
- async def _async_session_handler(self) -> AsyncGenerator[AsyncSession, None]:
49
+ async def _async_session_handler(
50
+ self, operation_id: UUID
51
+ ) -> AsyncGenerator[AsyncSession, None]:
27
52
  """Async session handler with proper error handling."""
53
+ executed_at = datetime.now(tz=timezone.utc)
28
54
  session = self._async_sessionmaker()
29
55
  try:
30
56
  yield session
31
57
  await session.commit()
58
+ completed_at = datetime.now(tz=timezone.utc)
59
+ self._logger.info(
60
+ f"Operation {operation_id} - success - Committed async database transaction",
61
+ extra={
62
+ "json_fields": {
63
+ "config": self._config.model_dump(mode="json"),
64
+ "operation_id": operation_id,
65
+ "success": True,
66
+ "timestamp": OperationTimestamp(
67
+ executed_at=executed_at,
68
+ completed_at=completed_at,
69
+ duration=(completed_at - executed_at).total_seconds(),
70
+ ).model_dump(mode="json"),
71
+ },
72
+ },
73
+ )
32
74
  except (SQLAlchemyError, ValidationError, Exception):
33
75
  await session.rollback()
76
+ completed_at = datetime.now(tz=timezone.utc)
77
+ self._logger.error(
78
+ f"Operation {operation_id} - failed - Error handling async database session",
79
+ exc_info=True,
80
+ extra={
81
+ "json_fields": {
82
+ "config": self._config.model_dump(mode="json"),
83
+ "operation_id": operation_id,
84
+ "success": False,
85
+ "timestamp": OperationTimestamp(
86
+ executed_at=executed_at,
87
+ completed_at=completed_at,
88
+ duration=(completed_at - executed_at).total_seconds(),
89
+ ).model_dump(mode="json"),
90
+ },
91
+ },
92
+ )
34
93
  raise
35
94
  finally:
36
95
  await session.close()
96
+ completed_at = datetime.now(tz=timezone.utc)
97
+ self._logger.info(
98
+ f"Operation {operation_id} - success - Closed async database session",
99
+ extra={
100
+ "json_fields": {
101
+ "config": self._config.model_dump(mode="json"),
102
+ "operation_id": operation_id,
103
+ "success": True,
104
+ "timestamp": OperationTimestamp(
105
+ executed_at=executed_at,
106
+ completed_at=completed_at,
107
+ duration=(completed_at - executed_at).total_seconds(),
108
+ ).model_dump(mode="json"),
109
+ },
110
+ },
111
+ )
37
112
 
38
- def _sync_session_handler(self) -> Generator[Session, None, None]:
113
+ def _sync_session_handler(
114
+ self, operation_id: UUID
115
+ ) -> Generator[Session, None, None]:
39
116
  """Sync session handler with proper error handling."""
117
+ executed_at = datetime.now(tz=timezone.utc)
40
118
  session = self._sync_sessionmaker()
41
119
  try:
42
120
  yield session
43
121
  session.commit()
122
+ completed_at = datetime.now(tz=timezone.utc)
123
+ self._logger.info(
124
+ f"Operation {operation_id} - success - Committed sync database transaction",
125
+ extra={
126
+ "json_fields": {
127
+ "config": self._config.model_dump(mode="json"),
128
+ "operation_id": operation_id,
129
+ "success": True,
130
+ "timestamp": OperationTimestamp(
131
+ executed_at=executed_at,
132
+ completed_at=completed_at,
133
+ duration=(completed_at - executed_at).total_seconds(),
134
+ ).model_dump(mode="json"),
135
+ },
136
+ },
137
+ )
44
138
  except (SQLAlchemyError, ValidationError, Exception):
45
139
  session.rollback()
140
+ completed_at = datetime.now(tz=timezone.utc)
141
+ self._logger.error(
142
+ f"Operation {operation_id} - failed - Error handling sync database session",
143
+ exc_info=True,
144
+ extra={
145
+ "json_fields": {
146
+ "config": self._config.model_dump(mode="json"),
147
+ "operation_id": operation_id,
148
+ "success": False,
149
+ "timestamp": OperationTimestamp(
150
+ executed_at=executed_at,
151
+ completed_at=completed_at,
152
+ duration=(completed_at - executed_at).total_seconds(),
153
+ ).model_dump(mode="json"),
154
+ },
155
+ },
156
+ )
46
157
  raise
47
158
  finally:
48
159
  session.close()
160
+ completed_at = datetime.now(tz=timezone.utc)
161
+ self._logger.info(
162
+ f"Operation {operation_id} - success - Closed sync database session",
163
+ extra={
164
+ "json_fields": {
165
+ "config": self._config.model_dump(mode="json"),
166
+ "operation_id": operation_id,
167
+ "success": True,
168
+ "timestamp": OperationTimestamp(
169
+ executed_at=executed_at,
170
+ completed_at=completed_at,
171
+ duration=(completed_at - executed_at).total_seconds(),
172
+ ).model_dump(mode="json"),
173
+ },
174
+ },
175
+ )
49
176
 
50
- # Overloaded inject methods
51
- @overload
52
- def inject(
53
- self, connection: Literal[Connection.ASYNC]
54
- ) -> AsyncGenerator[AsyncSession, None]: ...
177
+ @asynccontextmanager
178
+ async def _async_context_manager(
179
+ self, operation_id: UUID
180
+ ) -> AsyncGenerator[AsyncSession, None]:
181
+ """Async context manager implementation."""
182
+ async for session in self._async_session_handler(operation_id):
183
+ yield session
55
184
 
56
- @overload
57
- def inject(
58
- self, connection: Literal[Connection.SYNC]
59
- ) -> Generator[Session, None, None]: ...
60
-
61
- def inject(
62
- self, connection: Connection = Connection.ASYNC
63
- ) -> Union[AsyncGenerator[AsyncSession, None], Generator[Session, None, None]]:
64
- """Returns a generator for dependency injection."""
65
- if connection is Connection.ASYNC:
66
- return self._async_session_handler()
67
- else:
68
- return self._sync_session_handler()
185
+ @contextmanager
186
+ def _sync_context_manager(
187
+ self, operation_id: UUID
188
+ ) -> Generator[Session, None, None]:
189
+ """Sync context manager implementation."""
190
+ yield from self._sync_session_handler(operation_id)
69
191
 
70
192
  # Overloaded context manager methods
71
193
  @overload
72
194
  def get(
73
- self, connection: Literal[Connection.ASYNC]
195
+ self, connection: Literal[Connection.ASYNC], operation_id: OptionalUUID = None
74
196
  ) -> AbstractAsyncContextManager[AsyncSession]: ...
75
197
 
76
198
  @overload
77
199
  def get(
78
- self, connection: Literal[Connection.SYNC]
200
+ self, connection: Literal[Connection.SYNC], operation_id: OptionalUUID = None
79
201
  ) -> AbstractContextManager[Session]: ...
80
202
 
81
203
  def get(
82
- self, connection: Connection = Connection.ASYNC
204
+ self,
205
+ connection: Connection = Connection.ASYNC,
206
+ operation_id: OptionalUUID = None,
83
207
  ) -> Union[
84
208
  AbstractAsyncContextManager[AsyncSession], AbstractContextManager[Session]
85
209
  ]:
86
210
  """Context manager for manual session handling."""
211
+ if operation_id is None:
212
+ operation_id = uuid4()
87
213
  if connection is Connection.ASYNC:
88
- return self._async_context_manager()
214
+ return self._async_context_manager(operation_id)
89
215
  else:
90
- return self._sync_context_manager()
91
-
92
- @asynccontextmanager
93
- async def _async_context_manager(self) -> AsyncGenerator[AsyncSession, None]:
94
- """Async context manager implementation."""
95
- async for session in self._async_session_handler():
96
- yield session
97
-
98
- @contextmanager
99
- def _sync_context_manager(self) -> Generator[Session, None, None]:
100
- """Sync context manager implementation."""
101
- yield from self._sync_session_handler()
216
+ return self._sync_context_manager(operation_id)
102
217
 
103
218
  # Alternative: More explicit methods
104
219
  @asynccontextmanager
105
- async def get_async(self) -> AsyncGenerator[AsyncSession, None]:
220
+ async def get_async(
221
+ self, operation_id: OptionalUUID = None
222
+ ) -> AsyncGenerator[AsyncSession, None]:
106
223
  """Explicit async context manager."""
107
- async for session in self._async_session_handler():
224
+ if operation_id is None:
225
+ operation_id = uuid4()
226
+ async for session in self._async_session_handler(operation_id):
108
227
  yield session
109
228
 
110
229
  @contextmanager
111
- def get_sync(self) -> Generator[Session, None, None]:
230
+ def get_sync(
231
+ self, operation_id: OptionalUUID = None
232
+ ) -> Generator[Session, None, None]:
112
233
  """Explicit sync context manager."""
113
- yield from self._sync_session_handler()
114
- # with self._sync_session_handler() as session:
115
- # yield session
234
+ if operation_id is None:
235
+ operation_id = uuid4()
236
+ yield from self._sync_session_handler(operation_id)
116
237
 
117
- def inject_async(self) -> AsyncGenerator[AsyncSession, None]:
238
+ def as_async_dependency(self, operation_id: OptionalUUID = None):
118
239
  """Explicit async dependency injection."""
119
- return self._async_session_handler()
240
+ if operation_id is None:
241
+ operation_id = uuid4()
242
+
243
+ def dependency() -> AsyncGenerator[AsyncSession, None]:
244
+ return self._async_session_handler(operation_id)
245
+
246
+ return dependency
120
247
 
121
- def inject_sync(self) -> Generator[Session, None, None]:
248
+ def as_sync_dependency(self, operation_id: OptionalUUID = None):
122
249
  """Explicit sync dependency injection."""
123
- return self._sync_session_handler()
250
+ if operation_id is None:
251
+ operation_id = uuid4()
252
+
253
+ def dependency() -> AsyncGenerator[AsyncSession, None]:
254
+ return self._async_session_handler(operation_id)
255
+
256
+ return dependency
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo-database
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Database package for MaleoSuite
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: Proprietary
@@ -16,6 +16,7 @@ Requires-Dist: cachetools>=5.5.2
16
16
  Requires-Dist: certifi>=2025.8.3
17
17
  Requires-Dist: cffi>=1.17.1
18
18
  Requires-Dist: cfgv>=3.4.0
19
+ Requires-Dist: charset-normalizer>=3.4.3
19
20
  Requires-Dist: click>=8.2.1
20
21
  Requires-Dist: cryptography>=45.0.6
21
22
  Requires-Dist: distlib>=0.4.0
@@ -24,23 +25,37 @@ Requires-Dist: elastic-transport>=9.1.0
24
25
  Requires-Dist: elasticsearch>=9.1.0
25
26
  Requires-Dist: fastapi>=0.116.1
26
27
  Requires-Dist: filelock>=3.19.1
28
+ Requires-Dist: google-api-core>=2.25.1
27
29
  Requires-Dist: google-auth>=2.40.3
30
+ Requires-Dist: google-cloud-appengine-logging>=1.6.2
31
+ Requires-Dist: google-cloud-audit-log>=0.3.2
32
+ Requires-Dist: google-cloud-core>=2.4.3
33
+ Requires-Dist: google-cloud-logging>=3.12.1
34
+ Requires-Dist: googleapis-common-protos>=1.70.0
28
35
  Requires-Dist: greenlet>=3.2.4
36
+ Requires-Dist: grpc-google-iam-v1>=0.14.2
37
+ Requires-Dist: grpcio>=1.74.0
38
+ Requires-Dist: grpcio-status>=1.74.0
29
39
  Requires-Dist: identify>=2.6.13
30
40
  Requires-Dist: idna>=3.10
41
+ Requires-Dist: importlib_metadata>=8.7.0
31
42
  Requires-Dist: maleo-constants>=0.0.2
32
43
  Requires-Dist: maleo-enums>=0.0.2
33
- Requires-Dist: maleo-mixins>=0.0.2
44
+ Requires-Dist: maleo-logging>=0.0.1
45
+ Requires-Dist: maleo-mixins>=0.0.5
34
46
  Requires-Dist: maleo-types-base>=0.0.2
35
47
  Requires-Dist: maleo-types-enums>=0.0.2
36
48
  Requires-Dist: maleo-utils>=0.0.3
37
49
  Requires-Dist: motor>=3.7.1
38
50
  Requires-Dist: mypy_extensions>=1.1.0
39
51
  Requires-Dist: nodeenv>=1.9.1
52
+ Requires-Dist: opentelemetry-api>=1.36.0
40
53
  Requires-Dist: packaging>=25.0
41
54
  Requires-Dist: pathspec>=0.12.1
42
55
  Requires-Dist: platformdirs>=4.4.0
43
56
  Requires-Dist: pre_commit>=4.3.0
57
+ Requires-Dist: proto-plus>=1.26.1
58
+ Requires-Dist: protobuf>=6.32.0
44
59
  Requires-Dist: pyasn1>=0.6.1
45
60
  Requires-Dist: pyasn1_modules>=0.4.2
46
61
  Requires-Dist: pycparser>=2.22
@@ -51,6 +66,7 @@ Requires-Dist: pymongo>=4.14.1
51
66
  Requires-Dist: python-dateutil>=2.9.0.post0
52
67
  Requires-Dist: PyYAML>=6.0.2
53
68
  Requires-Dist: redis>=6.4.0
69
+ Requires-Dist: requests>=2.32.5
54
70
  Requires-Dist: rsa>=4.9.1
55
71
  Requires-Dist: six>=1.17.0
56
72
  Requires-Dist: sniffio>=1.3.1
@@ -60,6 +76,7 @@ Requires-Dist: typing-inspection>=0.4.1
60
76
  Requires-Dist: typing_extensions>=4.15.0
61
77
  Requires-Dist: urllib3>=2.5.0
62
78
  Requires-Dist: virtualenv>=20.34.0
79
+ Requires-Dist: zipp>=3.23.0
63
80
  Dynamic: license-file
64
81
 
65
82
  # README #
@@ -6,7 +6,7 @@ maleo/database/config/connection.py,sha256=lfnEE69355CNtPVlfoXaIu-IWiw4VPEkiY9tf
6
6
  maleo/database/config/identifier.py,sha256=b1MjhoKl3h7xJe1eVIj1wjvYH9BrjHzKnjzdjaEaTeQ,626
7
7
  maleo/database/config/pooling.py,sha256=tVJTxFV-fSEp4xJEVVLkTHFhhKEClSTGDQw5_pqLZgo,9246
8
8
  maleo/database/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- maleo/database/managers/session.py,sha256=qXNKxdBaY6tFqkMnj-dOLutdB5BosdsrmF60z1ooafE,4428
9
+ maleo/database/managers/session.py,sha256=2kfHOziwnjyQVNAskQ2LLx2xbrCcnxb760T5yP1xXM0,10054
10
10
  maleo/database/managers/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  maleo/database/managers/clients/elasticsearch.py,sha256=2US-S26N2o61iGwZZ8L0baYU7cXzJ8yFKmYFCHZ9sg0,2471
12
12
  maleo/database/managers/clients/mongodb.py,sha256=h20IW1BDvZWBH-XH1U_8quoNMBDV44uyg3AAS2vIXoA,2012
@@ -25,8 +25,8 @@ maleo/database/orm/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
25
25
  maleo/database/orm/models/mixins/identifier.py,sha256=jICVsLUFw_f7TV_aX4KTTs_ycH35MRtRj5boTqctmZo,425
26
26
  maleo/database/orm/models/mixins/status.py,sha256=Jzithn-Hl2ct1AqboGZSXrA35aaQEDvqtaB8oL7KIyo,376
27
27
  maleo/database/orm/models/mixins/timestamp.py,sha256=TWqBTBvgSxcPsK0m7ggVrkHqzlqCM-2Zox7TAwCxd0I,1500
28
- maleo_database-0.0.1.dist-info/licenses/LICENSE,sha256=aftGsecnk7TWVX-7KW94FqK4Syy6YSZ8PZEF7EcIp3M,2621
29
- maleo_database-0.0.1.dist-info/METADATA,sha256=leFM_T3mKI2qPUA01R-PPKXUg3B52IuR-jkdlZzzKsY,2595
30
- maleo_database-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- maleo_database-0.0.1.dist-info/top_level.txt,sha256=3Tpd1siVsfYoeI9FEOJNYnffx_shzZ3wsPpTvz5bljc,6
32
- maleo_database-0.0.1.dist-info/RECORD,,
28
+ maleo_database-0.0.3.dist-info/licenses/LICENSE,sha256=aftGsecnk7TWVX-7KW94FqK4Syy6YSZ8PZEF7EcIp3M,2621
29
+ maleo_database-0.0.3.dist-info/METADATA,sha256=aYpB8hcTx8HQXyBT0DKnOtZzxGCf365WAGAoqXpXYUE,3258
30
+ maleo_database-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ maleo_database-0.0.3.dist-info/top_level.txt,sha256=3Tpd1siVsfYoeI9FEOJNYnffx_shzZ3wsPpTvz5bljc,6
32
+ maleo_database-0.0.3.dist-info/RECORD,,