database-wrapper 0.2.13__py3-none-any.whl → 0.2.14__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.
@@ -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": "450d82cbaaf0997ef1aba44a809ce8358e3b1ccd",
7
- "git_commit_date": "06.01.2026 04:31",
8
- "app_version": "0.2.13",
6
+ "git_commit_hash": "520e71902c6f09ba35b57fd541d2e82dc5c54478",
7
+ "git_commit_date": "06.01.2026 02:41",
8
+ "app_version": "0.2.14",
9
9
  }
@@ -1,11 +1,11 @@
1
- from enum import Enum
2
1
  import re
3
2
  import json
4
3
  import datetime
5
4
  import dataclasses
6
5
 
7
6
  from dataclasses import dataclass, field, asdict
8
- from typing import Any, Callable, Literal, NotRequired, Type, TypeVar, TypedDict, cast
7
+ from enum import Enum
8
+ from typing import Any, Callable, ClassVar, Literal, NotRequired, Type, TypeVar, TypedDict, cast
9
9
 
10
10
  from .serialization import (
11
11
  SerializeType,
@@ -156,15 +156,16 @@ class DBDataModel:
156
156
  def __str__(self) -> str:
157
157
  return self.to_json_string()
158
158
 
159
- # Dict
160
159
  def dict_filter(self, pairs: list[tuple[str, Any]]) -> dict[str, Any]:
161
160
  new_dict: dict[str, Any] = {}
162
- for field in pairs:
163
- class_field = self.__dataclass_fields__.get(field[0], None)
164
- if class_field is not None:
165
- metadata = cast(MetadataDict, class_field.metadata)
166
- if not "exclude" in metadata or not metadata["exclude"]:
167
- new_dict[field[0]] = field[1]
161
+ for field_name, value in pairs:
162
+ class_field = self.__dataclass_fields__.get(field_name, None)
163
+ if class_field is None:
164
+ continue
165
+
166
+ metadata = cast(MetadataDict, class_field.metadata)
167
+ if not metadata.get("exclude", False):
168
+ new_dict[field_name] = value
168
169
 
169
170
  return new_dict
170
171
 
@@ -185,7 +186,9 @@ class DBDataModel:
185
186
  for field_name, field_obj in self.__dataclass_fields__.items():
186
187
  metadata = cast(MetadataDict, field_obj.metadata)
187
188
  assert (
188
- "db_field" in metadata and isinstance(metadata["db_field"], tuple) and len(metadata["db_field"]) == 2
189
+ "db_field" in metadata
190
+ and isinstance(metadata["db_field"], tuple)
191
+ and len(metadata["db_field"]) == 2
189
192
  ), f"db_field metadata is not set for {field_name}"
190
193
  field_type: str = metadata["db_field"][1]
191
194
  schema["properties"][field_name] = {"type": field_type}
@@ -252,45 +255,35 @@ class DBDataModel:
252
255
 
253
256
  def validate(self) -> Literal[True] | str:
254
257
  """
255
- True if the instance is valid, otherwise an error message.
258
+ True if the instance is valid, otherwise an error message.
256
259
  """
257
260
  raise NotImplementedError("`validate` is not implemented")
258
261
 
259
- def set_store(self, field_name: str, enable: bool = True) -> None:
260
- """
261
- Enable/Disable storing a field (insert into database)
262
- """
263
- if field_name in self.__dataclass_fields__:
264
- current_metadata = cast(
265
- MetadataDict,
266
- dict(self.__dataclass_fields__[field_name].metadata),
267
- )
268
- current_metadata["store"] = enable
269
- self.__dataclass_fields__[field_name].metadata = current_metadata
262
+ ############################
263
+ ### Store/update policies ###
264
+ ############################
270
265
 
271
- def set_update(self, field_name: str, enable: bool = True) -> None:
266
+ @classmethod
267
+ def _should_store(cls, field_name: str, metadata: MetadataDict) -> bool:
272
268
  """
273
- Enable/Disable updating a field (update in database)
269
+ Decide whether this field should be included in INSERT payload.
270
+ Base behavior: rely on field metadata.
271
+ Subclasses can override.
274
272
  """
275
- if field_name in self.__dataclass_fields__:
276
- current_metadata = cast(
277
- MetadataDict,
278
- dict(self.__dataclass_fields__[field_name].metadata),
279
- )
280
- current_metadata["update"] = enable
281
- self.__dataclass_fields__[field_name].metadata = current_metadata
273
+ return bool(metadata.get("store", False))
282
274
 
283
- def set_exclude(self, field_name: str, enable: bool = True) -> None:
275
+ @classmethod
276
+ def _should_update(cls, field_name: str, metadata: MetadataDict) -> bool:
284
277
  """
285
- Exclude a field from dict representation
278
+ Decide whether this field should be included in UPDATE payload.
279
+ Base behavior: rely on field metadata.
280
+ Subclasses can override.
286
281
  """
287
- if field_name in self.__dataclass_fields__:
288
- current_metadata = cast(
289
- MetadataDict,
290
- dict(self.__dataclass_fields__[field_name].metadata),
291
- )
292
- current_metadata["exclude"] = enable
293
- self.__dataclass_fields__[field_name].metadata = current_metadata
282
+ return bool(metadata.get("update", False))
283
+
284
+ @classmethod
285
+ def _should_exclude(cls, field_name: str, metadata: MetadataDict) -> bool:
286
+ return bool(metadata.get("exclude", False))
294
287
 
295
288
  ########################
296
289
  ### Database methods ###
@@ -298,19 +291,19 @@ class DBDataModel:
298
291
 
299
292
  def query_base(self) -> Any:
300
293
  """
301
- Base query for all queries
294
+ Base query for all queries
302
295
  """
303
296
  return None
304
297
 
305
298
  def store_data(self) -> dict[str, Any] | None:
306
299
  """
307
- Store data to database
300
+ Store data to database
308
301
  """
309
302
  store_data: dict[str, Any] = {}
310
303
  for field_name, field_obj in self.__dataclass_fields__.items():
311
304
  metadata = cast(MetadataDict, field_obj.metadata)
312
- if "store" in metadata and metadata["store"] == True:
313
- store_data[field_name] = getattr(self, field_name)
305
+ if self.__class__._should_store(field_name, metadata):
306
+ value = getattr(self, field_name)
314
307
 
315
308
  # If serialize is set, and serialize is a SerializeType,
316
309
  # we use our serialization function.
@@ -319,22 +312,23 @@ class DBDataModel:
319
312
  serialize = metadata.get("serialize", None)
320
313
  if serialize is not None:
321
314
  if isinstance(serialize, SerializeType):
322
- store_data[field_name] = serialize_value(store_data[field_name], serialize)
315
+ value = serialize_value(value, serialize)
323
316
  else:
324
- store_data[field_name] = serialize(store_data[field_name])
317
+ value = serialize(value)
325
318
 
319
+ store_data[field_name] = value
326
320
  return store_data
327
321
 
328
322
  def update_data(self) -> dict[str, Any] | None:
329
323
  """
330
- Update data to database
324
+ Update data to database
331
325
  """
332
326
 
333
327
  update_data: dict[str, Any] = {}
334
328
  for field_name, field_obj in self.__dataclass_fields__.items():
335
329
  metadata = cast(MetadataDict, field_obj.metadata)
336
- if "update" in metadata and metadata["update"] == True:
337
- update_data[field_name] = getattr(self, field_name)
330
+ if self.__class__._should_update(field_name, metadata):
331
+ value = getattr(self, field_name)
338
332
 
339
333
  # If serialize is set, and serialize is a SerializeType,
340
334
  # we use our serialization function.
@@ -343,30 +337,37 @@ class DBDataModel:
343
337
  serialize = metadata.get("serialize", None)
344
338
  if serialize is not None:
345
339
  if isinstance(serialize, SerializeType):
346
- update_data[field_name] = serialize_value(update_data[field_name], serialize)
340
+ value = serialize_value(value, serialize)
347
341
  else:
348
- update_data[field_name] = serialize(update_data[field_name])
342
+ value = serialize(value)
349
343
 
344
+ update_data[field_name] = value
350
345
  return update_data
351
346
 
352
347
 
353
348
  @dataclass
354
349
  class DBDefaultsDataModel(DBDataModel):
355
350
  """
356
- This class includes default fields for all database models.
351
+ DBDataModel with conventional default columns.
352
+ Subclasses can set `_defaults_config` to select which defaults are active.
357
353
 
358
354
  Attributes:
359
355
  - created_at (datetime.datetime): The timestamp of when the instance was created.
360
356
  - updated_at (datetime.datetime): The timestamp of when the instance was last updated.
361
- - enabled (bool): Whether the instance is enabled or not.
362
- - deleted (bool): Whether the instance is deleted or not.
357
+ - disabled_at (datetime.datetime): The timestamp of when the instance was disabled.
358
+ - deleted_at (datetime.datetime): The timestamp of when the instance was deleted.
359
+ - enabled (bool): Whether the instance is enabled or not. Deprecated.
360
+ - deleted (bool): Whether the instance is deleted or not. Deprecated.
363
361
  """
364
362
 
363
+ # Subclasses override this as a class attribute, e.g.:
364
+ # _defaults_config = ["created_at", "updated_at"]
365
+ _defaults_config: ClassVar[list[str]] = ["created_at", "updated_at", "disabled_at", "deleted_at"]
366
+
365
367
  ######################
366
368
  ### Default fields ###
367
369
  ######################
368
370
 
369
- #
370
371
  created_at: datetime.datetime = field(
371
372
  default_factory=datetime.datetime.now,
372
373
  metadata=MetadataDict(
@@ -376,9 +377,7 @@ class DBDefaultsDataModel(DBDataModel):
376
377
  serialize=SerializeType.DATETIME,
377
378
  ),
378
379
  )
379
- """created_at is readonly by default and should be present in all tables"""
380
380
 
381
- #
382
381
  updated_at: datetime.datetime = field(
383
382
  default_factory=datetime.datetime.now,
384
383
  metadata=MetadataDict(
@@ -388,30 +387,29 @@ class DBDefaultsDataModel(DBDataModel):
388
387
  serialize=SerializeType.DATETIME,
389
388
  ),
390
389
  )
391
- """updated_at should be present in all tables and is updated automatically"""
392
390
 
393
- # full
394
- disabled_at: datetime.datetime = field(
395
- default_factory=datetime.datetime.now,
391
+ # Important: default None, otherwise everything becomes disabled/deleted on insert.
392
+ disabled_at: datetime.datetime | None = field(
393
+ default=None,
396
394
  metadata=MetadataDict(
397
395
  db_field=("disabled_at", "timestamptz"),
398
- store=False,
399
- update=False,
396
+ store=True,
397
+ update=True,
400
398
  serialize=SerializeType.DATETIME,
401
399
  ),
402
400
  )
403
401
 
404
- #
405
- deleted_at: datetime.datetime = field(
406
- default_factory=datetime.datetime.now,
402
+ deleted_at: datetime.datetime | None = field(
403
+ default=None,
407
404
  metadata=MetadataDict(
408
405
  db_field=("deleted_at", "timestamptz"),
409
- store=False,
410
- update=False,
406
+ store=True,
407
+ update=True,
411
408
  serialize=SerializeType.DATETIME,
412
409
  ),
413
410
  )
414
411
 
412
+ # @deprecated
415
413
  enabled: bool = field(
416
414
  default=True,
417
415
  metadata=MetadataDict(
@@ -420,6 +418,8 @@ class DBDefaultsDataModel(DBDataModel):
420
418
  update=False,
421
419
  ),
422
420
  )
421
+
422
+ # @deprecated
423
423
  deleted: bool = field(
424
424
  default=False,
425
425
  metadata=MetadataDict(
@@ -429,85 +429,28 @@ class DBDefaultsDataModel(DBDataModel):
429
429
  ),
430
430
  )
431
431
 
432
- def update_data(self) -> dict[str, Any] | None:
433
- """
434
- Update data to database
435
- """
436
-
437
- # Update updated_at
438
- self.updated_at = datetime.datetime.now(datetime.UTC)
439
-
440
- return super().update_data()
441
-
442
-
443
- @dataclass
444
- class DBDefaultsDataModelV2(DBDataModel):
445
- """
446
- This class includes default fields for all database models.
447
-
448
- Attributes:
449
- - created_at (datetime.datetime): The timestamp of when the instance was created.
450
- - updated_at (datetime.datetime): The timestamp of when the instance was last updated.
451
- - enabled (bool): Whether the instance is enabled or not.
452
- - deleted (bool): Whether the instance is deleted or not.
453
- """
454
-
455
- ######################
456
- ### Default fields ###
457
- ######################
458
-
459
- #
460
- created_at: datetime.datetime = field(
461
- default_factory=datetime.datetime.now,
462
- metadata=MetadataDict(
463
- db_field=("created_at", "timestamptz"),
464
- store=True,
465
- update=False,
466
- serialize=SerializeType.DATETIME,
467
- ),
468
- )
469
- """created_at is readonly by default and should be present in all tables"""
470
-
471
- #
472
- updated_at: datetime.datetime = field(
473
- default_factory=datetime.datetime.now,
474
- metadata=MetadataDict(
475
- db_field=("updated_at", "timestamptz"),
476
- store=True,
477
- update=True,
478
- serialize=SerializeType.DATETIME,
479
- ),
480
- )
481
- """updated_at should be present in all tables and is updated automatically"""
482
-
483
- #
484
- disabled_at: datetime.datetime = field(
485
- default_factory=datetime.datetime.now,
486
- metadata=MetadataDict(
487
- db_field=("disabled_at", "timestamptz"),
488
- store=True,
489
- update=True,
490
- serialize=SerializeType.DATETIME,
491
- ),
492
- )
493
-
494
- #
495
- deleted_at: datetime.datetime = field(
496
- default_factory=datetime.datetime.now,
497
- metadata=MetadataDict(
498
- db_field=("deleted_at", "timestamptz"),
499
- store=True,
500
- update=True,
501
- serialize=SerializeType.DATETIME,
502
- ),
503
- )
432
+ ############################
433
+ ### Store/update policies ###
434
+ ############################
435
+
436
+ @classmethod
437
+ def _should_store(cls, field_name: str, metadata: MetadataDict) -> bool:
438
+ # For the 4 defaults, defer to _defaults_config.
439
+ if field_name in ("created_at", "updated_at", "disabled_at", "deleted_at"):
440
+ return field_name in cls._defaults_config
441
+ return super()._should_store(field_name, metadata)
442
+
443
+ @classmethod
444
+ def _should_update(cls, field_name: str, metadata: MetadataDict) -> bool:
445
+ # created_at is never updated
446
+ if field_name == "created_at":
447
+ return False
448
+ if field_name in ("updated_at", "disabled_at", "deleted_at"):
449
+ return field_name in cls._defaults_config
450
+ return super()._should_update(field_name, metadata)
504
451
 
505
452
  def update_data(self) -> dict[str, Any] | None:
506
- """
507
- Update data to database
508
- """
509
-
510
- # Update updated_at
511
- self.updated_at = datetime.datetime.now(datetime.UTC)
512
-
453
+ # Always refresh updated_at if present in this model
454
+ if "updated_at" in self.__dataclass_fields__ and "updated_at" in self._defaults_config:
455
+ self.updated_at = datetime.datetime.now(datetime.UTC)
513
456
  return super().update_data()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: database_wrapper
3
- Version: 0.2.13
3
+ Version: 0.2.14
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,15 +33,15 @@ 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.2.13; extra == "pgsql"
36
+ Requires-Dist: database_wrapper_pgsql==0.2.14; extra == "pgsql"
37
37
  Provides-Extra: mysql
38
- Requires-Dist: database_wrapper_mysql==0.2.13; extra == "mysql"
38
+ Requires-Dist: database_wrapper_mysql==0.2.14; extra == "mysql"
39
39
  Provides-Extra: mssql
40
- Requires-Dist: database_wrapper_mssql==0.2.13; extra == "mssql"
40
+ Requires-Dist: database_wrapper_mssql==0.2.14; extra == "mssql"
41
41
  Provides-Extra: sqlite
42
- Requires-Dist: database_wrapper_sqlite==0.2.13; extra == "sqlite"
42
+ Requires-Dist: database_wrapper_sqlite==0.2.14; extra == "sqlite"
43
43
  Provides-Extra: redis
44
- Requires-Dist: database_wrapper_redis==0.2.13; extra == "redis"
44
+ Requires-Dist: database_wrapper_redis==0.2.14; extra == "redis"
45
45
  Provides-Extra: all
46
46
  Requires-Dist: database_wrapper[mssql,mysql,pgsql,redis,sqlite]; extra == "all"
47
47
  Provides-Extra: dev
@@ -1,9 +1,9 @@
1
1
  database_wrapper/__init__.py,sha256=cGqsWr7imdMOO7vrcGye8FVul_v3-32mw4wgrfxnhJA,1320
2
2
  database_wrapper/abc.py,sha256=JiQo6Yfv7xALrrHeICJCSgmyP2gHrp16Ov83mPBTGbE,2058
3
3
  database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
4
- database_wrapper/config.py,sha256=y4Y2g4sEbD1kgqLNpxsfkDqXHurMFfaJQiellYs09PE,334
4
+ database_wrapper/config.py,sha256=WLb33Upc6bmoHHVO968FKaEby25oc4vYaJ9bHur-UKE,334
5
5
  database_wrapper/db_backend.py,sha256=pV_XGu0tR5naz7Ni6BvpqcmN9U7TdQ0bRkg7YiNZlO0,7928
6
- database_wrapper/db_data_model.py,sha256=zhD_c1H5YV2f29DjBTQ5Oln99iO8hWdtcf-ijelWQnA,17000
6
+ database_wrapper/db_data_model.py,sha256=KUSQhL1QQmxLtw-X2rXK3-YsNashhipLT4WkOs_X-SY,15624
7
7
  database_wrapper/db_introspector.py,sha256=8sWNmBeumCE0D-cbpBvpMSTbCU6dpDLxCPB8HaNf6KA,15463
8
8
  database_wrapper/db_wrapper.py,sha256=x6xsMW0epCOaj3RWWi9mj1EE1JmqM27PyS_Khvsxdvo,15231
9
9
  database_wrapper/db_wrapper_async.py,sha256=ud7kCDfsfrbp3FHfRS7FjbsVTnnf87sHsxctpNWuhnk,15500
@@ -12,7 +12,7 @@ database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  database_wrapper/serialization.py,sha256=Tti1-nA7H4g3hzqr3mE2WurxOnEjFaxBJZTOR3ONZSo,2906
13
13
  database_wrapper/utils/__init__.py,sha256=uC8YaJqfyFIZIeNdTRTbZwcOUVhmnS5eyOG-9gMs70c,96
14
14
  database_wrapper/utils/dataclass_addons.py,sha256=Og98FTL8_m07AjpAsbIdSkHQO099xt9asD3W2QasypY,759
15
- database_wrapper-0.2.13.dist-info/METADATA,sha256=sD36XRl-81M6pJOM8QESSUVgQd7SrkPg4n9Bfvms1Pw,3615
16
- database_wrapper-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- database_wrapper-0.2.13.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
18
- database_wrapper-0.2.13.dist-info/RECORD,,
15
+ database_wrapper-0.2.14.dist-info/METADATA,sha256=HP9Mnq62YnDiD1mLn-LpHBwZTLc8wqwHWvPcT4vJ-AU,3615
16
+ database_wrapper-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ database_wrapper-0.2.14.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
18
+ database_wrapper-0.2.14.dist-info/RECORD,,