database-wrapper 0.1.44__py3-none-any.whl → 0.1.72__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.
@@ -0,0 +1,82 @@
1
+ import datetime
2
+ import json
3
+ import re
4
+
5
+ from decimal import Decimal
6
+ from enum import Enum
7
+ from typing import Any, Type
8
+
9
+
10
+ class SerializeType(Enum):
11
+ DATETIME = "datetime"
12
+ JSON = "json"
13
+ ENUM = "enum"
14
+
15
+
16
+ def jsonEncoder(obj: Any) -> Any:
17
+ if isinstance(obj, Decimal):
18
+ return float(obj)
19
+
20
+ if isinstance(obj, datetime.date) or isinstance(obj, datetime.datetime):
21
+ return obj.strftime("%Y-%m-%dT%H:%M:%S")
22
+
23
+ if isinstance(obj, Enum):
24
+ return obj.value
25
+
26
+ if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, str):
27
+ return obj
28
+
29
+ return str(obj)
30
+
31
+
32
+ def serializeValue(value: Any, sType: SerializeType) -> Any:
33
+ if sType == SerializeType.DATETIME:
34
+ if not isinstance(value, datetime.datetime):
35
+ return value
36
+
37
+ return value.isoformat()
38
+
39
+ if sType == SerializeType.JSON:
40
+ return json.dumps(value, default=jsonEncoder)
41
+
42
+ if sType == SerializeType.ENUM:
43
+ return value.value
44
+ return value
45
+
46
+
47
+ def deserializeValue(
48
+ value: Any,
49
+ sType: SerializeType,
50
+ enumClass: Type[Enum] | None = None,
51
+ ) -> Any:
52
+ if sType == SerializeType.DATETIME:
53
+ if isinstance(value, datetime.datetime):
54
+ return value
55
+
56
+ if value and isinstance(value, str):
57
+ pattern = r"^\d+(\.\d+)?$"
58
+ if re.match(pattern, value):
59
+ return datetime.datetime.fromtimestamp(float(value))
60
+
61
+ return datetime.datetime.fromisoformat(value)
62
+
63
+ return datetime.datetime.now(datetime.UTC)
64
+
65
+ if sType == SerializeType.JSON:
66
+ if isinstance(value, dict) or isinstance(value, list) or value is None:
67
+ return value # type: ignore
68
+
69
+ return json.loads(value)
70
+
71
+ if sType == SerializeType.ENUM:
72
+ if enumClass is None:
73
+ raise ValueError(
74
+ "enumClass (enum_class) must be provided when deserializing Enum"
75
+ )
76
+
77
+ if isinstance(value, Enum) or value is None:
78
+ return value
79
+
80
+ return enumClass(value)
81
+
82
+ return value
@@ -1,7 +1,5 @@
1
- from .timer import Timer
2
1
  from .dataclass_addons import ignore_unknown_kwargs
3
2
 
4
3
  __all__ = [
5
- "Timer",
6
4
  "ignore_unknown_kwargs",
7
5
  ]
@@ -12,7 +12,7 @@ def ignore_unknown_kwargs() -> Callable[[AnyDataType], AnyDataType]:
12
12
  originalInit = cls.__init__
13
13
 
14
14
  # @wraps(originalInit)
15
- def newInit(self: Any, *args: Any, **kwargs: Any):
15
+ def newInit(self: Any, *args: Any, **kwargs: Any) -> None:
16
16
  # Filter out kwargs that are not properties of the class
17
17
  valid_kwargs = {k: v for k, v in kwargs.items() if hasattr(self, k)}
18
18
  originalInit(self, *args, **valid_kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: database_wrapper
3
- Version: 0.1.44
3
+ Version: 0.1.72
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)
@@ -32,27 +32,29 @@ Classifier: Topic :: Software Development
32
32
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
33
  Requires-Python: >=3.8
34
34
  Description-Content-Type: text/markdown
35
- Provides-Extra: all
36
- Requires-Dist: database-wrapper[mssql,mysql,pgsql,sqlite] ; extra == 'all'
37
- Provides-Extra: dev
38
- Requires-Dist: ast-comments >=1.1.2 ; extra == 'dev'
39
- Requires-Dist: codespell >=2.2 ; extra == 'dev'
40
- Requires-Dist: build >=1.2.1 ; extra == 'dev'
41
- Requires-Dist: black >=24.1.0 ; extra == 'dev'
42
- Requires-Dist: types-setuptools >=61.0.0 ; extra == 'dev'
43
- Requires-Dist: types-pymssql >=2.1.0 ; extra == 'dev'
44
- Requires-Dist: psycopg[binary] >=3.2.0 ; extra == 'dev'
45
- Requires-Dist: psycopg[pool] >=3.2.0 ; extra == 'dev'
46
- Requires-Dist: mysqlclient >=2.2.2 ; extra == 'dev'
47
- Requires-Dist: pymssql >=2.2.10 ; extra == 'dev'
48
- Provides-Extra: mssql
49
- Requires-Dist: database-wrapper-mssql ==0.1.44 ; extra == 'mssql'
50
- Provides-Extra: mysql
51
- Requires-Dist: database-wrapper-mysql ==0.1.44 ; extra == 'mysql'
52
35
  Provides-Extra: pgsql
53
- Requires-Dist: database-wrapper-pgsql ==0.1.44 ; extra == 'pgsql'
36
+ Requires-Dist: database_wrapper_pgsql==0.1.72; extra == "pgsql"
37
+ Provides-Extra: mysql
38
+ Requires-Dist: database_wrapper_mysql==0.1.72; extra == "mysql"
39
+ Provides-Extra: mssql
40
+ Requires-Dist: database_wrapper_mssql==0.1.72; extra == "mssql"
54
41
  Provides-Extra: sqlite
55
- Requires-Dist: database-wrapper-sqlite ==0.1.44 ; extra == 'sqlite'
42
+ Requires-Dist: database_wrapper_sqlite==0.1.72; extra == "sqlite"
43
+ Provides-Extra: all
44
+ Requires-Dist: database_wrapper[mssql,mysql,pgsql,sqlite]; extra == "all"
45
+ Provides-Extra: dev
46
+ Requires-Dist: ast-comments>=1.1.2; extra == "dev"
47
+ Requires-Dist: codespell>=2.2; extra == "dev"
48
+ Requires-Dist: build>=1.2.1; extra == "dev"
49
+ Requires-Dist: black>=24.1.0; extra == "dev"
50
+ Requires-Dist: mypy>=1.9.0; extra == "dev"
51
+ Requires-Dist: types-setuptools>=61.0.0; extra == "dev"
52
+ Requires-Dist: types-pymssql>=2.1.0; extra == "dev"
53
+ Requires-Dist: types-mysqlclient>=2.2.0; extra == "dev"
54
+ Requires-Dist: psycopg[binary]>=3.2.0; extra == "dev"
55
+ Requires-Dist: psycopg[pool]>=3.2.0; extra == "dev"
56
+ Requires-Dist: mysqlclient>=2.2.2; extra == "dev"
57
+ Requires-Dist: pymssql>=2.2.10; extra == "dev"
56
58
 
57
59
  # database_wrapper
58
60
 
@@ -0,0 +1,17 @@
1
+ database_wrapper/__init__.py,sha256=p-HLz9_zByIUeAS1tVxS7ZieslsryHSLw_IKZphqB7w,1200
2
+ database_wrapper/abc.py,sha256=JiQo6Yfv7xALrrHeICJCSgmyP2gHrp16Ov83mPBTGbE,2058
3
+ database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
4
+ database_wrapper/config.py,sha256=r8H8ElFt2aifHOvuRwdfRu09_dCjrQe_zNEMLwDZtyY,334
5
+ database_wrapper/db_backend.py,sha256=huueyQH_FW36qQIGx7dofFA2cTqwKZ2r7F2BTaF7K_o,7176
6
+ database_wrapper/db_data_model.py,sha256=boLYcSJZhSUMeeUd21iEERIo5K-oERPG_gBAdfNOKDQ,14077
7
+ database_wrapper/db_wrapper.py,sha256=vpRifOT7e9d6PmlXIQde-tZ4hEceSgzGFO2hwc4XsBI,14353
8
+ database_wrapper/db_wrapper_async.py,sha256=iUcvRZ1L3wNBt0MCx6tSYbtcuN7Kzgq37HkShqnOoXA,14739
9
+ database_wrapper/db_wrapper_mixin.py,sha256=GR6Zn-2GygzzSaFWPIqkiI6mZ8oWqKY_Sc-MeUp6SG8,9966
10
+ database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ database_wrapper/serialization.py,sha256=_yA1srkS6Vec3TMOAYpsCAbJFuB6nrblt8uJrY-7s0E,2046
12
+ database_wrapper/utils/__init__.py,sha256=uC8YaJqfyFIZIeNdTRTbZwcOUVhmnS5eyOG-9gMs70c,96
13
+ database_wrapper/utils/dataclass_addons.py,sha256=r8DD40tXA_DLMQJx62UqVaRe4Gr9BSOmChLRhxawet4,770
14
+ database_wrapper-0.1.72.dist-info/METADATA,sha256=bT8f7OJWnpD5I4CEntQMFaXFKUyxiHw4DeGOxABX9fA,3440
15
+ database_wrapper-0.1.72.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
16
+ database_wrapper-0.1.72.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
17
+ database_wrapper-0.1.72.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,297 +0,0 @@
1
- import time
2
- import logging
3
-
4
- from typing import Any, TypedDict
5
- from contextlib import asynccontextmanager, contextmanager
6
-
7
-
8
- class TimerProperties(TypedDict):
9
- level: int
10
- total: float
11
- runtimes: dict[int, float]
12
-
13
-
14
- class Timer:
15
- name: str
16
-
17
- startTimes: dict[str, float]
18
- totalTimes: dict[str, TimerProperties]
19
-
20
- timers: list[tuple[int, str]]
21
- level: int
22
- runIndex: int
23
-
24
- def __init__(self, name: str):
25
- self.name = name
26
-
27
- self.startTimes = {}
28
- self.totalTimes = {}
29
- self.timers = []
30
- self.level = 0
31
- self.runIndex = 0
32
-
33
- # * Class generator
34
- @contextmanager
35
- def enter(self, name: str):
36
- self.level += 1
37
- self.start(name=name)
38
-
39
- try:
40
- yield self
41
- finally:
42
- self.stop(name=name)
43
- self.level -= 1
44
-
45
- @asynccontextmanager
46
- async def aenter(self, name: str):
47
- self.level += 1
48
- self.start(name=name)
49
-
50
- try:
51
- yield self
52
- finally:
53
- self.stop(name=name)
54
- self.level -= 1
55
-
56
- # * Class context manager
57
- def __enter__(self) -> "Timer":
58
- self.start()
59
- return self
60
-
61
- def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
62
- self.stop()
63
-
64
- async def __aenter__(self) -> "Timer":
65
- self.start()
66
- return self
67
-
68
- async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
69
- self.stop()
70
-
71
- # * Start / stop
72
- def start(self, name: str | None = None, skipAppend: bool = False):
73
- if name is None:
74
- name = self.name
75
-
76
- if len(self.timers) > 0:
77
- (
78
- level,
79
- prevTimerName,
80
- ) = self.timers[-1]
81
- if prevTimerName != name and level != self.level:
82
- self.stop(name=prevTimerName, skipRemove=True)
83
-
84
- self.startTimes[name] = time.perf_counter()
85
-
86
- if not skipAppend:
87
- self.timers.append(
88
- (
89
- self.level,
90
- name,
91
- )
92
- )
93
-
94
- def stop(
95
- self,
96
- name: str | None = None,
97
- skipRemove: bool = False,
98
- ):
99
- if name is None:
100
- name = self.name
101
- endTime = time.perf_counter()
102
-
103
- if not name in self.startTimes:
104
- logging.getLogger().warning(f"Timer {name} stopped before it was started")
105
- return
106
-
107
- elapsed = endTime - self.startTimes[name]
108
- if not name in self.totalTimes:
109
- self.totalTimes[name] = TimerProperties(
110
- level=self.level, total=0, runtimes={}
111
- )
112
-
113
- self.totalTimes[name]["runtimes"][self.runIndex] = elapsed
114
- self.totalTimes[name]["total"] += elapsed
115
-
116
- if not skipRemove:
117
- # Remove current timer
118
- self.timers.pop()
119
-
120
- # Continue previous timer
121
- if len(self.timers) > 0:
122
- (
123
- level,
124
- prevTimerName,
125
- ) = self.timers[-1]
126
- if prevTimerName != name and level != self.level:
127
- self.start(name=prevTimerName, skipAppend=True)
128
-
129
- # * Stats
130
- def resetTimers(self):
131
- """Resets the current timer. Call this at the start of an iteration."""
132
- self.startTimes = {}
133
- self.totalTimes = {}
134
-
135
- # * Output
136
- def printTimerStatsTable(self, returnData: bool = False) -> str | None:
137
- """Prints the current timing information into a table."""
138
- timerStats = "\n\n"
139
-
140
- all_fn_names = [k for k in self.totalTimes.keys()]
141
- if len(all_fn_names) == 0:
142
- return
143
-
144
- # Max width of level column
145
- max_level_width = max([self.totalTimes[k].get("level") for k in all_fn_names])
146
- if max_level_width % 2 == 1:
147
- max_level_width += 1
148
- max_level_width = max(max_level_width, 6)
149
-
150
- # Max width of name column
151
- max_name_width = max([len(k) for k in all_fn_names] + [4])
152
- if max_name_width % 2 == 1:
153
- max_name_width += 1
154
-
155
- # Format string
156
- format_str = (
157
- "{:>%d} | {:>%d} | {:>10.4f} | {:>10.4f} | {:>10.4f} | {:>10.4f} | {:>5}"
158
- % (
159
- max_level_width,
160
- max_name_width,
161
- )
162
- )
163
-
164
- header = (
165
- "{:>%d} | {:>%d} | {:^10} | {:^10} | {:^10} | {:^10} | {:^5}"
166
- % (
167
- max_level_width,
168
- max_name_width,
169
- )
170
- ).format(
171
- "Level", "Name", "Total (ms)", "Min (ms)", "Max (ms)", "Avg (ms)", "Count"
172
- )
173
- timerStats += f"{header}\n"
174
-
175
- sepIdx = header.find("|")
176
- sepText = ("-" * sepIdx) + "+" + ("-" * (len(header) - sepIdx - 1))
177
- timerStats += f"{sepText}\n"
178
-
179
- for name in all_fn_names:
180
- runtimes = self.totalTimes[name].get("runtimes", {}).values()
181
- total_time = self.totalTimes[name].get("total", 0) * 1000
182
- min_time = min(runtimes) * 1000 if runtimes else 0
183
- max_time = max(runtimes) * 1000 if runtimes else 0
184
- avg_time = (sum(runtimes) / len(runtimes)) * 1000 if runtimes else 0
185
- run_count = len(runtimes)
186
-
187
- timerStats += format_str.format(
188
- self.totalTimes[name].get("level"),
189
- name,
190
- total_time,
191
- min_time,
192
- max_time,
193
- avg_time,
194
- run_count,
195
- )
196
- timerStats += "\n"
197
-
198
- totals_format_str = (
199
- "{:>%d} | {:>%d} | {:>10.4f} | {:>10.4} | {:>10.4} | {:>10.4} | {:>10.4}"
200
- % (
201
- max_level_width,
202
- max_name_width,
203
- )
204
- )
205
- timerStats += f"{sepText}\n"
206
- timerStats += totals_format_str.format(
207
- "",
208
- "Total",
209
- self.totalTime(self.totalTimes) * 1000,
210
- "",
211
- "",
212
- "",
213
- "",
214
- )
215
- timerStats += "\n\n"
216
-
217
- if returnData:
218
- return timerStats
219
-
220
- logging.getLogger().debug(timerStats)
221
-
222
- def printTimerStatsJSON(self, returnData: bool = False) -> str | None:
223
- """Prints the current timing information into a JSON string."""
224
- pass
225
-
226
- def printTimerStatsCSV(self, returnData: bool = False) -> str | None:
227
- """Prints the current timing information into a CSV string."""
228
- all_fn_names = list(self.totalTimes.keys())
229
- if not all_fn_names:
230
- return
231
-
232
- # Prepare header
233
- header = all_fn_names + ["Total"]
234
-
235
- # Get all runtime indices and find the maximum index
236
- all_indices: set[int] = set()
237
- for name in all_fn_names:
238
- all_indices.update(self.totalTimes[name].get("runtimes", {}).keys())
239
- max_index = max(all_indices) if all_indices else 0
240
-
241
- # Prepare data rows
242
- data_rows: "list[list[str]]" = []
243
- for i in range(max_index + 1):
244
- row: "list[str]" = []
245
- row_total = 0
246
- for name in all_fn_names:
247
- runtime = self.totalTimes[name].get("runtimes", {}).get(i)
248
- if runtime is not None:
249
- value = runtime * 1000
250
- row.append(f"{value:.4f}")
251
- row_total += value
252
- else:
253
- row.append("")
254
- row.append(f"{row_total:.4f}")
255
- data_rows.append(row)
256
-
257
- # Prepare total row
258
- total_row: "list[str]" = []
259
- grand_total = 0
260
- for name in all_fn_names:
261
- total = sum(self.totalTimes[name].get("runtimes", {}).values()) * 1000
262
- total_row.append(f"{total:.4f}")
263
- grand_total += total
264
- total_row.append(f"{grand_total:.4f}")
265
-
266
- # Combine all rows
267
- all_rows = [header] + data_rows + [total_row]
268
-
269
- # Convert to CSV string
270
- output = "\n".join([",".join(str(cell) for cell in row) for row in all_rows])
271
-
272
- if returnData:
273
- return output
274
-
275
- logging.getLogger().info(f"\n{output}")
276
-
277
- def printTimerStats(
278
- self,
279
- outputFormat: str = "table",
280
- returnData: bool = False,
281
- ) -> str | None:
282
- """
283
- Prints the current timing information into a table.
284
-
285
- outputFormat: str - choices: ["table", "json", "csv"]
286
- """
287
-
288
- if outputFormat == "table":
289
- return self.printTimerStatsTable(returnData=returnData)
290
- elif outputFormat == "json":
291
- return self.printTimerStatsJSON(returnData=returnData)
292
- elif outputFormat == "csv":
293
- return self.printTimerStatsCSV(returnData=returnData)
294
-
295
- def totalTime(self, totalTimes: dict[str, TimerProperties]) -> float:
296
- """Returns the total amount accumulated across all functions in seconds."""
297
- return sum([timer.get("total") for _name, timer in totalTimes.items()])
@@ -1,16 +0,0 @@
1
- database_wrapper/__init__.py,sha256=amoShlhRpOhrFbGeKbdq47H5omcVRd4xWG-RUX7bdIk,830
2
- database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
3
- database_wrapper/config.py,sha256=hFaefMZ_4XVacDd5n5tPNQVi_fEDbG3ZCB-awE9TPxc,334
4
- database_wrapper/db_backend.py,sha256=CNfkx-UKVmrR8dSXT5SYpPU3whCL61gxljNVpvcIKXc,5068
5
- database_wrapper/db_data_model.py,sha256=NV4wfbASIVoSLIXWlHAYxCpVN7-ADKfG6OtYEyTyd_w,12123
6
- database_wrapper/db_wrapper.py,sha256=O5H7Q8xvvzN4p7KL_NnHflY-EZCfw9TM5mYBzRuJfZA,16488
7
- database_wrapper/db_wrapper_async.py,sha256=4K-01_UtNxaRgN5qB3j1O2eBX2cOW0xi2ZJJimnqgYw,16714
8
- database_wrapper/db_wrapper_mixin.py,sha256=-k8Gna_iGE5Gz1gRsVP8sOkUbwX3djjCWeM3ZkGKT7E,10570
9
- database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- database_wrapper/utils/__init__.py,sha256=mnewmY38-837VAh4f42hvpMUBVUjOLoMtIfdZBxbkg0,134
11
- database_wrapper/utils/dataclass_addons.py,sha256=5_ZAj8h-4RtimEM-b9lo6TXi4qYVTf7KIjTtu0jzAS4,762
12
- database_wrapper/utils/timer.py,sha256=ZJpVMsQ7oHHgyuqMOxVee1fZD78kcDrP4c8gHug3xGU,8927
13
- database_wrapper-0.1.44.dist-info/METADATA,sha256=8ceRUd7R_lUMgLpnzdq78fntrH9BUo2Z-PuiAn_WzNg,3370
14
- database_wrapper-0.1.44.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
15
- database_wrapper-0.1.44.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
16
- database_wrapper-0.1.44.dist-info/RECORD,,