database-wrapper 0.1.44__tar.gz → 0.1.73__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 (23) hide show
  1. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/PKG-INFO +7 -5
  2. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/__init__.py +12 -1
  3. database_wrapper-0.1.73/database_wrapper/abc.py +68 -0
  4. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/config.py +3 -3
  5. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/db_backend.py +131 -42
  6. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/db_data_model.py +109 -60
  7. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/db_wrapper.py +44 -124
  8. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/db_wrapper_async.py +54 -118
  9. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/db_wrapper_mixin.py +18 -44
  10. database_wrapper-0.1.73/database_wrapper/serialization.py +82 -0
  11. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/utils/__init__.py +0 -2
  12. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/utils/dataclass_addons.py +1 -1
  13. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper.egg-info/PKG-INFO +7 -5
  14. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper.egg-info/SOURCES.txt +3 -2
  15. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper.egg-info/requires.txt +6 -4
  16. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/pyproject.toml +7 -5
  17. database_wrapper-0.1.44/database_wrapper/utils/timer.py +0 -297
  18. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/README.md +0 -0
  19. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/common.py +0 -0
  20. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper/py.typed +0 -0
  21. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper.egg-info/dependency_links.txt +0 -0
  22. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/database_wrapper.egg-info/top_level.txt +0 -0
  23. {database_wrapper-0.1.44 → database_wrapper-0.1.73}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: database_wrapper
3
- Version: 0.1.44
3
+ Version: 0.1.73
4
4
  Summary: A Different Approach to Database Wrappers in Python
5
5
  Author-email: Gints Murans <gm@gm.lv>
6
6
  License: GNU General Public License v3.0 (GPL-3.0)
@@ -33,13 +33,13 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
33
  Requires-Python: >=3.8
34
34
  Description-Content-Type: text/markdown
35
35
  Provides-Extra: pgsql
36
- Requires-Dist: database_wrapper_pgsql==0.1.44; extra == "pgsql"
36
+ Requires-Dist: database_wrapper_pgsql==0.1.73; extra == "pgsql"
37
37
  Provides-Extra: mysql
38
- Requires-Dist: database_wrapper_mysql==0.1.44; extra == "mysql"
38
+ Requires-Dist: database_wrapper_mysql==0.1.73; extra == "mysql"
39
39
  Provides-Extra: mssql
40
- Requires-Dist: database_wrapper_mssql==0.1.44; extra == "mssql"
40
+ Requires-Dist: database_wrapper_mssql==0.1.73; extra == "mssql"
41
41
  Provides-Extra: sqlite
42
- Requires-Dist: database_wrapper_sqlite==0.1.44; extra == "sqlite"
42
+ Requires-Dist: database_wrapper_sqlite==0.1.73; extra == "sqlite"
43
43
  Provides-Extra: all
44
44
  Requires-Dist: database_wrapper[mssql,mysql,pgsql,sqlite]; extra == "all"
45
45
  Provides-Extra: dev
@@ -47,8 +47,10 @@ Requires-Dist: ast-comments>=1.1.2; extra == "dev"
47
47
  Requires-Dist: codespell>=2.2; extra == "dev"
48
48
  Requires-Dist: build>=1.2.1; extra == "dev"
49
49
  Requires-Dist: black>=24.1.0; extra == "dev"
50
+ Requires-Dist: mypy>=1.9.0; extra == "dev"
50
51
  Requires-Dist: types-setuptools>=61.0.0; extra == "dev"
51
52
  Requires-Dist: types-pymssql>=2.1.0; extra == "dev"
53
+ Requires-Dist: types-mysqlclient>=2.2.0; extra == "dev"
52
54
  Requires-Dist: psycopg[binary]>=3.2.0; extra == "dev"
53
55
  Requires-Dist: psycopg[pool]>=3.2.0; extra == "dev"
54
56
  Requires-Dist: mysqlclient>=2.2.2; extra == "dev"
@@ -6,12 +6,15 @@ database_wrapper package - Base for database wrappers
6
6
 
7
7
  import logging
8
8
 
9
+ from .abc import ConnectionABC, CursorABC, CursorAsyncABC, ConnectionAsyncABC
9
10
  from . import utils
10
11
  from .db_backend import DatabaseBackend
11
- from .db_data_model import DBDataModel, DBDefaultsDataModel
12
+ from .db_data_model import MetadataDict, DBDataModel, DBDefaultsDataModel
12
13
  from .common import OrderByItem, DataModelType, NoParam
13
14
  from .db_wrapper import DBWrapper
14
15
  from .db_wrapper_async import DBWrapperAsync
16
+ from .serialization import SerializeType
17
+ from .utils.dataclass_addons import ignore_unknown_kwargs
15
18
 
16
19
  # Set the logger to a quiet default, can be enabled if needed
17
20
  logger = logging.getLogger("database_wrapper")
@@ -30,8 +33,16 @@ __all__ = [
30
33
  "DBWrapper",
31
34
  "DBWrapperAsync",
32
35
  # Helpers
36
+ "MetadataDict",
33
37
  "DataModelType",
34
38
  "OrderByItem",
35
39
  "NoParam",
36
40
  "utils",
41
+ "SerializeType",
42
+ "ignore_unknown_kwargs",
43
+ # Abstract classes
44
+ "ConnectionABC",
45
+ "CursorABC",
46
+ "CursorAsyncABC",
47
+ "ConnectionAsyncABC",
37
48
  ]
@@ -0,0 +1,68 @@
1
+ from typing import Dict, Any, Literal
2
+ from typing import Any, Protocol, Sequence
3
+
4
+ from psycopg.sql import Composable
5
+
6
+ Row = Dict[str, Any]
7
+
8
+
9
+ class CursorABC(Protocol):
10
+ def execute(
11
+ self,
12
+ operation: str | Composable,
13
+ parameters: Sequence[Any] | None = None,
14
+ ) -> None: ...
15
+ def executemany(
16
+ self,
17
+ operation: str | Composable,
18
+ seq_of_parameters: Sequence[Sequence[Any]],
19
+ ) -> None: ...
20
+ def fetchone(self) -> Row | None: ...
21
+ def fetchmany(self, size: int | None = None) -> list[Row]: ...
22
+ def fetchall(self) -> list[Row]: ...
23
+ def close(self) -> None: ...
24
+ def setinputsizes(self, sizes: Sequence[Any]) -> None: ...
25
+ def setoutputsize(self, size: Any, column: Any = None) -> None: ...
26
+
27
+ @property
28
+ def rowcount(self) -> int | Literal[-1]: ...
29
+ @property
30
+ def lastrowid(self) -> int | None: ...
31
+
32
+
33
+ class ConnectionABC(Protocol):
34
+ def cursor(self) -> CursorABC: ...
35
+ def commit(self) -> None: ...
36
+ def rollback(self) -> None: ...
37
+ def close(self) -> None: ...
38
+
39
+
40
+ class CursorAsyncABC(Protocol):
41
+ async def execute(
42
+ self,
43
+ operation: str | Composable,
44
+ parameters: Sequence[Any] | None = None,
45
+ ) -> None: ...
46
+ async def executemany(
47
+ self,
48
+ operation: str | Composable,
49
+ seq_of_parameters: Sequence[Sequence[Any]],
50
+ ) -> None: ...
51
+ async def fetchone(self) -> Row | None: ...
52
+ async def fetchmany(self, size: int | None = None) -> list[Row]: ...
53
+ async def fetchall(self) -> list[Row]: ...
54
+ async def close(self) -> None: ...
55
+ def setinputsizes(self, sizes: Sequence[Any]) -> None: ...
56
+ def setoutputsize(self, size: Any, column: Any = None) -> None: ...
57
+
58
+ @property
59
+ def rowcount(self) -> int | Literal[-1]: ...
60
+ @property
61
+ def lastrowid(self) -> int | None: ...
62
+
63
+
64
+ class ConnectionAsyncABC(Protocol):
65
+ async def cursor(self) -> CursorAsyncABC: ...
66
+ async def commit(self) -> None: ...
67
+ async def rollback(self) -> None: ...
68
+ async def close(self) -> None: ...
@@ -3,7 +3,7 @@ from typing import Any
3
3
  CONFIG: dict[str, Any] = {
4
4
  # These are supposed to be set automatically by a git pre-compile script
5
5
  # They are one git commit hash behind, if used automatically
6
- "git_commit_hash": "ec2eca45cd9798f0c045418602fbe07ed7734848",
7
- "git_commit_date": "15.11.2024 18:06",
8
- "app_version": "0.1.44",
6
+ "git_commit_hash": "443d5e2328c4e0bc041cab252394454c3dfe09bd",
7
+ "git_commit_date": "26.11.2024 22:13",
8
+ "app_version": "0.1.73",
9
9
  }
@@ -5,63 +5,114 @@ from typing import Any
5
5
  from threading import Event
6
6
  from contextvars import ContextVar
7
7
 
8
- from .utils.timer import Timer
9
-
10
8
 
11
9
  class DatabaseBackend:
10
+ config: Any
11
+ """ Database configuration """
12
+
13
+ connectionTimeout: int
14
+ """ Connection timeout """
15
+
16
+ name: str
17
+ """ Instance name """
18
+
19
+ # TODO: This should be made to increase exponentially
20
+ slowDownTimeout: int
21
+ """ How long to wait before trying to reconnect """
22
+
23
+ pool: Any
24
+ """ Connection pool """
25
+
26
+ poolAsync: Any
27
+ """ Async connection pool """
28
+
12
29
  connection: Any
30
+ """ Connection to database """
31
+
13
32
  cursor: Any
33
+ """ Cursor to database """
34
+
14
35
  contextConnection: ContextVar[Any | None]
15
- contextAsyncConnection: ContextVar[Any | None]
36
+ """ Connection used in context manager """
16
37
 
17
- config: Any
38
+ contextConnectionAsync: ContextVar[Any | None]
39
+ """ Connection used in async context manager """
18
40
 
19
- connectionTimeout: int
20
- slowDownTimeout: int = 5
41
+ loggerName: str
42
+ """ Logger name """
21
43
 
22
- name: str
23
44
  logger: logging.Logger
24
- timer: ContextVar[Timer | None]
45
+ """ Logger """
25
46
 
26
47
  shutdownRequested: Event
48
+ """
49
+ Event to signal shutdown
50
+ Used to stop database pool from creating new connections
51
+ """
52
+
53
+ ########################
54
+ ### Class Life Cycle ###
55
+ ########################
27
56
 
28
57
  def __init__(
29
58
  self,
30
59
  dbConfig: Any,
31
60
  connectionTimeout: int = 5,
32
61
  instanceName: str = "database_backend",
62
+ slowDownTimeout: int = 5,
33
63
  ) -> None:
34
64
  """
35
65
  Main concept here is that in init we do not connect to database,
36
66
  so that class instances can be safely made regardless of connection statuss.
37
67
 
38
- Remember to call open() before using this class.
68
+ Remember to call open() or openPool() before using this class.
39
69
  Close will be called automatically when class is destroyed.
40
- But sometimes in async environment you should call close() proactively.
70
+
71
+ Contexts are not implemented here, but in child classes should be used
72
+ by using connection pooling.
73
+
74
+ Async classes should be called manually and should override __del__ method,
75
+ if not upon destroying the class, an error will be raised that method was not awaited.
41
76
  """
42
77
 
43
78
  self.config = dbConfig
44
79
  self.connectionTimeout = connectionTimeout
45
80
  self.name = instanceName
81
+ self.slowDownTimeout = slowDownTimeout
82
+
83
+ self.loggerName = f"{__name__}.{self.__class__.__name__}.{self.name}"
84
+ self.logger = logging.getLogger(self.loggerName)
46
85
 
47
- loggerName = f"{__name__}.{self.__class__.__name__}.{self.name}"
48
- self.logger = logging.getLogger(loggerName)
49
- self.timer = ContextVar(f"db_timer", default=None)
86
+ self.pool = None
87
+ self.poolAsync = None
50
88
 
51
89
  self.connection = None
52
90
  self.cursor = None
53
91
  self.shutdownRequested = Event()
54
92
  self.contextConnection = ContextVar(f"db_connection_{self.name}", default=None)
55
- self.contextAsyncConnection = ContextVar(
93
+ self.contextConnectionAsync = ContextVar(
56
94
  f"db_connection_{self.name}_async", default=None
57
95
  )
58
96
 
59
97
  def __del__(self) -> None:
60
98
  """What to do when class is destroyed"""
61
99
  self.logger.debug("Dealloc")
100
+
101
+ # Clean up connections
62
102
  self.close()
103
+ self.closePool()
104
+
105
+ # Clean just in case
106
+ del self.connection
107
+ del self.cursor
108
+
109
+ del self.pool
110
+ del self.poolAsync
111
+
112
+ ###############
113
+ ### Context ###
114
+ ###############
63
115
 
64
- # Context
65
116
  def __enter__(self) -> tuple[Any, Any]:
66
117
  """Context manager"""
67
118
  raise Exception("Not implemented")
@@ -78,16 +129,23 @@ class DatabaseBackend:
78
129
  """Context manager"""
79
130
  raise Exception("Not implemented")
80
131
 
81
- # Connection
82
- def open(self) -> None:
83
- """Connect to database"""
84
- raise Exception("Not implemented")
132
+ ##################
133
+ ### Connection ###
134
+ ##################
135
+
136
+ def openPool(self) -> Any:
137
+ """Open connection pool"""
138
+ ...
85
139
 
86
- async def openAsync(self) -> None:
140
+ def closePool(self) -> Any:
141
+ """Close connection pool"""
142
+ ...
143
+
144
+ def open(self) -> Any:
87
145
  """Connect to database"""
88
- raise Exception("Not implemented")
146
+ ...
89
147
 
90
- def close(self) -> None:
148
+ def close(self) -> Any:
91
149
  """Close connections"""
92
150
  if self.cursor:
93
151
  self.logger.debug("Closing cursor")
@@ -99,7 +157,34 @@ class DatabaseBackend:
99
157
  self.connection.close()
100
158
  self.connection = None
101
159
 
102
- def fixSocketTimeouts(self, fd: Any):
160
+ def newConnection(self) -> Any:
161
+ """
162
+ Create new connection
163
+
164
+ Used for async context manager and async connection creation
165
+
166
+ Returns:
167
+ tuple[Any, Any] | None: Connection and cursor
168
+ """
169
+ raise Exception("Not implemented")
170
+
171
+ def returnConnection(self, connection: Any) -> Any:
172
+ """
173
+ Return connection to pool
174
+
175
+ Used for async context manager and async connections return.
176
+ For example to return connection to a pool.
177
+
178
+ Args:
179
+ connection (Any): Connection to return to pool
180
+ """
181
+ raise Exception("Not implemented")
182
+
183
+ ###############
184
+ ### Helpers ###
185
+ ###############
186
+
187
+ def fixSocketTimeouts(self, fd: Any) -> None:
103
188
  # Lets do some socket magic
104
189
  s = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
105
190
  # Enable sending of keep-alive messages
@@ -118,32 +203,36 @@ class DatabaseBackend:
118
203
  socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, self.connectionTimeout * 1000
119
204
  )
120
205
 
121
- async def newConnection(
122
- self,
123
- ) -> tuple[Any, Any] | None:
124
- """
125
- Create new connection
206
+ ####################
207
+ ### Transactions ###
208
+ ####################
126
209
 
127
- Used for async context manager and async connection creation
210
+ def beginTransaction(self) -> Any:
211
+ """Start transaction"""
212
+ raise Exception("Not implemented")
128
213
 
129
- Returns:
130
- tuple[Any, Any] | None: Connection and cursor
131
- """
214
+ def commitTransaction(self) -> Any:
215
+ """Commit transaction"""
132
216
  raise Exception("Not implemented")
133
217
 
134
- async def returnConnection(self, connection: Any) -> None:
135
- """
136
- Return connection to pool
218
+ def rollbackTransaction(self) -> Any:
219
+ """Rollback transaction"""
220
+ raise Exception("Not implemented")
137
221
 
138
- Used for async context manager and async connections return.
139
- For example to return connection to a pool.
222
+ # @contextmanager
223
+ def transaction(self, dbConn: Any = None) -> Any:
224
+ """
225
+ Transaction context manager
140
226
 
141
- Args:
142
- connection (Any): Connection to return to pool
227
+ ! When overriding this method, remember to use context manager.
228
+ ! Its not defined here, so that it can be used in both sync and async methods.
143
229
  """
144
230
  raise Exception("Not implemented")
145
231
 
146
- # Data
232
+ ############
233
+ ### Data ###
234
+ ############
235
+
147
236
  def lastInsertId(self) -> int:
148
237
  """Get last inserted row id generated by auto increment"""
149
238
  raise Exception("Not implemented")
@@ -152,10 +241,10 @@ class DatabaseBackend:
152
241
  """Get affected rows count"""
153
242
  raise Exception("Not implemented")
154
243
 
155
- def commit(self) -> None:
244
+ def commit(self) -> Any:
156
245
  """Commit DB queries"""
157
246
  raise Exception("Not implemented")
158
247
 
159
- def rollback(self) -> None:
248
+ def rollback(self) -> Any:
160
249
  """Rollback DB queries"""
161
250
  raise Exception("Not implemented")