database-wrapper 0.2.13__py3-none-any.whl → 0.2.16__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.
@@ -6,16 +6,16 @@ database_wrapper package - Base for database wrappers
6
6
 
7
7
  import logging
8
8
 
9
- from .abc import ConnectionABC, CursorABC, CursorAsyncABC, ConnectionAsyncABC
10
9
  from . import utils
10
+ from .abc import ConnectionABC, ConnectionAsyncABC, CursorABC, CursorAsyncABC
11
+ from .common import DataModelType, NoParam, OrderByItem
11
12
  from .db_backend import DatabaseBackend
12
- from .db_data_model import MetadataDict, DBDataModel, DBDefaultsDataModel
13
- from .common import OrderByItem, DataModelType, NoParam
13
+ from .db_data_model import DBDataModel, DBDefaultsDataModel, MetadataDict
14
+ from .db_introspector import ColumnMetaIntrospector, DBIntrospector
14
15
  from .db_wrapper import DBWrapper
15
16
  from .db_wrapper_async import DBWrapperAsync
16
17
  from .serialization import SerializeType
17
18
  from .utils.dataclass_addons import ignore_unknown_kwargs
18
- from .db_introspector import ColumnMetaIntrospector, DBIntrospector
19
19
 
20
20
  # Set the logger to a quiet default, can be enabled if needed
21
21
  logger = logging.getLogger("database_wrapper")
database_wrapper/abc.py CHANGED
@@ -1,9 +1,9 @@
1
- from typing import Dict, Any, Literal
2
- from typing import Any, Protocol, Sequence
1
+ from collections.abc import Sequence
2
+ from typing import Any, Literal, Protocol
3
3
 
4
4
  from psycopg.sql import Composable
5
5
 
6
- Row = Dict[str, Any]
6
+ Row = dict[str, Any]
7
7
 
8
8
 
9
9
  class CursorABC(Protocol):
@@ -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": "4baad61b81d9f178acd8de04c1a65f2053e06d89",
7
+ "git_commit_date": "09.01.2026 00:48",
8
+ "app_version": "0.2.16",
9
9
  }
@@ -1,9 +1,9 @@
1
1
  import logging
2
2
  import socket
3
-
4
- from typing import Any, Coroutine
5
- from threading import Event
3
+ from collections.abc import Coroutine
6
4
  from contextvars import ContextVar
5
+ from threading import Event
6
+ from typing import Any
7
7
 
8
8
 
9
9
  class DatabaseBackend:
@@ -1,11 +1,11 @@
1
- from enum import Enum
2
- import re
3
- import json
4
- import datetime
5
1
  import dataclasses
6
-
7
- from dataclasses import dataclass, field, asdict
8
- from typing import Any, Callable, Literal, NotRequired, Type, TypeVar, TypedDict, cast
2
+ import datetime
3
+ import json
4
+ import re
5
+ from collections.abc import Callable
6
+ from dataclasses import asdict, dataclass, field
7
+ from enum import Enum
8
+ from typing import Any, ClassVar, Literal, NotRequired, TypedDict, TypeVar, cast
9
9
 
10
10
  from .serialization import (
11
11
  SerializeType,
@@ -24,7 +24,7 @@ class MetadataDict(TypedDict):
24
24
  exclude: NotRequired[bool]
25
25
  serialize: NotRequired[Callable[[Any], Any] | SerializeType | None]
26
26
  deserialize: NotRequired[Callable[[Any], Any] | None]
27
- enum_class: NotRequired[Type[Enum] | None]
27
+ enum_class: NotRequired[type[Enum] | None]
28
28
  timezone: NotRequired[str | datetime.tzinfo | None]
29
29
 
30
30
 
@@ -151,20 +151,21 @@ class DBDataModel:
151
151
 
152
152
  # String - representation
153
153
  def __repr__(self) -> str:
154
- return "<%s %s>" % (self.__class__.__name__, self.__dict__)
154
+ return f"<{self.__class__.__name__} {self.__dict__}>"
155
155
 
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
 
@@ -256,41 +257,31 @@ class DBDataModel:
256
257
  """
257
258
  raise NotImplementedError("`validate` is not implemented")
258
259
 
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
260
+ ############################
261
+ ### Store/update policies ###
262
+ ############################
270
263
 
271
- def set_update(self, field_name: str, enable: bool = True) -> None:
264
+ @classmethod
265
+ def _should_store(cls, field_name: str, metadata: MetadataDict) -> bool:
272
266
  """
273
- Enable/Disable updating a field (update in database)
267
+ Decide whether this field should be included in INSERT payload.
268
+ Base behavior: rely on field metadata.
269
+ Subclasses can override.
274
270
  """
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
271
+ return bool(metadata.get("store", False))
282
272
 
283
- def set_exclude(self, field_name: str, enable: bool = True) -> None:
273
+ @classmethod
274
+ def _should_update(cls, field_name: str, metadata: MetadataDict) -> bool:
284
275
  """
285
- Exclude a field from dict representation
276
+ Decide whether this field should be included in UPDATE payload.
277
+ Base behavior: rely on field metadata.
278
+ Subclasses can override.
286
279
  """
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
280
+ return bool(metadata.get("update", False))
281
+
282
+ @classmethod
283
+ def _should_exclude(cls, field_name: str, metadata: MetadataDict) -> bool:
284
+ return bool(metadata.get("exclude", False))
294
285
 
295
286
  ########################
296
287
  ### Database methods ###
@@ -309,8 +300,8 @@ class DBDataModel:
309
300
  store_data: dict[str, Any] = {}
310
301
  for field_name, field_obj in self.__dataclass_fields__.items():
311
302
  metadata = cast(MetadataDict, field_obj.metadata)
312
- if "store" in metadata and metadata["store"] == True:
313
- store_data[field_name] = getattr(self, field_name)
303
+ if self.__class__._should_store(field_name, metadata):
304
+ value = getattr(self, field_name)
314
305
 
315
306
  # If serialize is set, and serialize is a SerializeType,
316
307
  # we use our serialization function.
@@ -319,10 +310,11 @@ class DBDataModel:
319
310
  serialize = metadata.get("serialize", None)
320
311
  if serialize is not None:
321
312
  if isinstance(serialize, SerializeType):
322
- store_data[field_name] = serialize_value(store_data[field_name], serialize)
313
+ value = serialize_value(value, serialize)
323
314
  else:
324
- store_data[field_name] = serialize(store_data[field_name])
315
+ value = serialize(value)
325
316
 
317
+ store_data[field_name] = value
326
318
  return store_data
327
319
 
328
320
  def update_data(self) -> dict[str, Any] | None:
@@ -333,8 +325,8 @@ class DBDataModel:
333
325
  update_data: dict[str, Any] = {}
334
326
  for field_name, field_obj in self.__dataclass_fields__.items():
335
327
  metadata = cast(MetadataDict, field_obj.metadata)
336
- if "update" in metadata and metadata["update"] == True:
337
- update_data[field_name] = getattr(self, field_name)
328
+ if self.__class__._should_update(field_name, metadata):
329
+ value = getattr(self, field_name)
338
330
 
339
331
  # If serialize is set, and serialize is a SerializeType,
340
332
  # we use our serialization function.
@@ -343,30 +335,37 @@ class DBDataModel:
343
335
  serialize = metadata.get("serialize", None)
344
336
  if serialize is not None:
345
337
  if isinstance(serialize, SerializeType):
346
- update_data[field_name] = serialize_value(update_data[field_name], serialize)
338
+ value = serialize_value(value, serialize)
347
339
  else:
348
- update_data[field_name] = serialize(update_data[field_name])
340
+ value = serialize(value)
349
341
 
342
+ update_data[field_name] = value
350
343
  return update_data
351
344
 
352
345
 
353
346
  @dataclass
354
347
  class DBDefaultsDataModel(DBDataModel):
355
348
  """
356
- This class includes default fields for all database models.
349
+ DBDataModel with conventional default columns.
350
+ Subclasses can set `_defaults_config` to select which defaults are active.
357
351
 
358
352
  Attributes:
359
353
  - created_at (datetime.datetime): The timestamp of when the instance was created.
360
354
  - 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.
355
+ - disabled_at (datetime.datetime): The timestamp of when the instance was disabled.
356
+ - deleted_at (datetime.datetime): The timestamp of when the instance was deleted.
357
+ - enabled (bool): Whether the instance is enabled or not. Deprecated.
358
+ - deleted (bool): Whether the instance is deleted or not. Deprecated.
363
359
  """
364
360
 
361
+ # Subclasses override this as a class attribute, e.g.:
362
+ # _defaults_config = ["created_at", "updated_at"]
363
+ _defaults_config: ClassVar[list[str]] = ["created_at", "updated_at", "disabled_at", "deleted_at"]
364
+
365
365
  ######################
366
366
  ### Default fields ###
367
367
  ######################
368
368
 
369
- #
370
369
  created_at: datetime.datetime = field(
371
370
  default_factory=datetime.datetime.now,
372
371
  metadata=MetadataDict(
@@ -376,9 +375,7 @@ class DBDefaultsDataModel(DBDataModel):
376
375
  serialize=SerializeType.DATETIME,
377
376
  ),
378
377
  )
379
- """created_at is readonly by default and should be present in all tables"""
380
378
 
381
- #
382
379
  updated_at: datetime.datetime = field(
383
380
  default_factory=datetime.datetime.now,
384
381
  metadata=MetadataDict(
@@ -388,30 +385,29 @@ class DBDefaultsDataModel(DBDataModel):
388
385
  serialize=SerializeType.DATETIME,
389
386
  ),
390
387
  )
391
- """updated_at should be present in all tables and is updated automatically"""
392
388
 
393
- # full
394
- disabled_at: datetime.datetime = field(
395
- default_factory=datetime.datetime.now,
389
+ # Important: default None, otherwise everything becomes disabled/deleted on insert.
390
+ disabled_at: datetime.datetime | None = field(
391
+ default=None,
396
392
  metadata=MetadataDict(
397
393
  db_field=("disabled_at", "timestamptz"),
398
- store=False,
399
- update=False,
394
+ store=True,
395
+ update=True,
400
396
  serialize=SerializeType.DATETIME,
401
397
  ),
402
398
  )
403
399
 
404
- #
405
- deleted_at: datetime.datetime = field(
406
- default_factory=datetime.datetime.now,
400
+ deleted_at: datetime.datetime | None = field(
401
+ default=None,
407
402
  metadata=MetadataDict(
408
403
  db_field=("deleted_at", "timestamptz"),
409
- store=False,
410
- update=False,
404
+ store=True,
405
+ update=True,
411
406
  serialize=SerializeType.DATETIME,
412
407
  ),
413
408
  )
414
409
 
410
+ # @deprecated
415
411
  enabled: bool = field(
416
412
  default=True,
417
413
  metadata=MetadataDict(
@@ -420,6 +416,8 @@ class DBDefaultsDataModel(DBDataModel):
420
416
  update=False,
421
417
  ),
422
418
  )
419
+
420
+ # @deprecated
423
421
  deleted: bool = field(
424
422
  default=False,
425
423
  metadata=MetadataDict(
@@ -429,85 +427,28 @@ class DBDefaultsDataModel(DBDataModel):
429
427
  ),
430
428
  )
431
429
 
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
- )
430
+ ############################
431
+ ### Store/update policies ###
432
+ ############################
433
+
434
+ @classmethod
435
+ def _should_store(cls, field_name: str, metadata: MetadataDict) -> bool:
436
+ # For the 4 defaults, defer to _defaults_config.
437
+ if field_name in ("created_at", "updated_at", "disabled_at", "deleted_at"):
438
+ return field_name in cls._defaults_config
439
+ return super()._should_store(field_name, metadata)
440
+
441
+ @classmethod
442
+ def _should_update(cls, field_name: str, metadata: MetadataDict) -> bool:
443
+ # created_at is never updated
444
+ if field_name == "created_at":
445
+ return False
446
+ if field_name in ("updated_at", "disabled_at", "deleted_at"):
447
+ return field_name in cls._defaults_config
448
+ return super()._should_update(field_name, metadata)
504
449
 
505
450
  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
-
451
+ # Always refresh updated_at if present in this model
452
+ if "updated_at" in self.__dataclass_fields__ and "updated_at" in self._defaults_config:
453
+ self.updated_at = datetime.datetime.now(datetime.UTC)
513
454
  return super().update_data()
@@ -1,8 +1,9 @@
1
- from dataclasses import MISSING, dataclass, make_dataclass, field, fields as dc_fields
2
- from datetime import datetime, date
1
+ from dataclasses import MISSING, dataclass, field, make_dataclass
2
+ from dataclasses import fields as dc_fields
3
+ from datetime import date, datetime
3
4
  from enum import Enum
4
5
  from pathlib import Path
5
- from typing import Any, Optional, Type, Union, get_origin, get_args
6
+ from typing import Any, Union, get_args, get_origin
6
7
 
7
8
  from database_wrapper import DBDefaultsDataModel, MetadataDict, SerializeType
8
9
 
@@ -93,12 +94,12 @@ class DBIntrospector:
93
94
  self,
94
95
  table_name: str,
95
96
  *,
96
- class_name: Optional[str] = None,
97
- base: Type[DBDefaultsDataModel] = DBDefaultsDataModel,
98
- enum_overrides: dict[str, Type[Enum]] | None = None,
97
+ class_name: str | None = None,
98
+ base: type[DBDefaultsDataModel] = DBDefaultsDataModel,
99
+ enum_overrides: dict[str, type[Enum]] | None = None,
99
100
  defaults_for_nullable: bool = True,
100
101
  include_id_field: bool = True,
101
- ) -> Type[DBDefaultsDataModel]:
102
+ ) -> type[DBDefaultsDataModel]:
102
103
  (schema, table) = self.get_schema_table_name(table_name)
103
104
  cols = self.get_table_columns(schema, table)
104
105
  if not cols:
@@ -115,7 +116,7 @@ class DBIntrospector:
115
116
  continue
116
117
 
117
118
  # Enums
118
- enum_class: Optional[Type[Enum]] = None
119
+ enum_class: type[Enum] | None = None
119
120
  if c.enum_labels:
120
121
  enum_class = enum_overrides.get(c.col_name)
121
122
  if not enum_class:
@@ -127,14 +128,14 @@ class DBIntrospector:
127
128
 
128
129
  # Optional typing if nullable
129
130
  if c.is_nullable:
130
- ann = Optional[py_type] # type: ignore
131
+ ann: type[py_type] | None = py_type
131
132
  else:
132
- ann = py_type # type: ignore
133
+ ann = py_type
133
134
 
134
135
  # Default value choice
135
136
  default = None
136
137
  default_factory = None
137
- if c.is_nullable == False:
138
+ if not c.is_nullable:
138
139
  # give some sane defaults for common not-nullables that aren't id/serial
139
140
  if py_type is bool:
140
141
  default = False
@@ -156,7 +157,7 @@ class DBIntrospector:
156
157
  elif py_type is set:
157
158
  default_factory = set
158
159
  elif py_type is bytes:
159
- default = bytes()
160
+ default = b""
160
161
  else:
161
162
  # Leave unset so dataclass enforces passing it explicitly
162
163
  default = None if defaults_for_nullable else None
@@ -202,15 +203,15 @@ class DBIntrospector:
202
203
  def _tableName(self) -> str:
203
204
  return table
204
205
 
205
- setattr(cls, "schemaName", property(_schemaName))
206
- setattr(cls, "tableName", property(_tableName))
206
+ cls.schemaName = property(_schemaName)
207
+ cls.tableName = property(_tableName)
207
208
 
208
209
  return cls
209
210
 
210
211
  # TODO: Need to improve handling of imports for external classes, including enums.
211
212
  def render_dataclass_source(
212
213
  self,
213
- cls: Type,
214
+ cls: type,
214
215
  table_name: str,
215
216
  *,
216
217
  extra_imports: list[str] | None = None,
@@ -242,7 +243,7 @@ class DBIntrospector:
242
243
  dynamic_enums: list[tuple[str, list[tuple[str, Any]]]] = []
243
244
  external_enum_imports: set[tuple[str, str]] = set()
244
245
  for f in dc_fields(cls):
245
- md: MetadataDict = dict(f.metadata) if f.metadata else {} # type: ignore
246
+ md: MetadataDict = dict(f.metadata) if f.metadata else {}
246
247
  enum_class = md.get("enum_class")
247
248
  if enum_class:
248
249
  mod = getattr(enum_class, "__module__", "")
@@ -250,7 +251,7 @@ class DBIntrospector:
250
251
  # If it's the built-in Enum module (meaning we made it dynamically),
251
252
  # embed it. Otherwise, import from its module.
252
253
  if mod == "enum":
253
- members = [(m.name, m.value) for m in enum_class] # type: ignore
254
+ members = [(m.name, m.value) for m in enum_class]
254
255
  dynamic_enums.append((name, members))
255
256
  else:
256
257
  external_enum_imports.add((mod, name))
@@ -298,7 +299,7 @@ class DBIntrospector:
298
299
 
299
300
  # Render fields (skip the inherited ones we know exist on base if they aren't present here)
300
301
  for f in dc_fields(cls):
301
- md: MetadataDict = dict(f.metadata) if f.metadata else {} # type: ignore
302
+ md: MetadataDict = dict(f.metadata) if f.metadata else {}
302
303
  # We always render all fields that exist in this dataclass (your make_dataclass created them)
303
304
  db_field = md.get("db_field", (f.name, "Any"))
304
305
  if not (isinstance(db_field, tuple) and len(db_field) == 2):
@@ -357,7 +358,7 @@ class DBIntrospector:
357
358
  lines.append(" out: dict[str, Any] = {}")
358
359
  lines.append(" # Explicitly list each field (stable order)")
359
360
  for f in dc_fields(cls):
360
- md: MetadataDict = dict(f.metadata) if f.metadata else {} # type: ignore
361
+ md: MetadataDict = dict(f.metadata) if f.metadata else {}
361
362
  serialize = md.get("serialize")
362
363
  key = f.name
363
364
  if serialize == SerializeType.DATETIME:
@@ -1,8 +1,9 @@
1
- from typing import Generator, Any, overload
1
+ from collections.abc import Generator
2
+ from typing import Any, overload
2
3
 
4
+ from .common import DataModelType, OrderByItem
3
5
  from .db_data_model import DBDataModel
4
6
  from .db_wrapper_mixin import DBWrapperMixin
5
- from .common import OrderByItem, DataModelType
6
7
 
7
8
 
8
9
  class DBWrapper(DBWrapperMixin):
@@ -230,8 +231,7 @@ class DBWrapper(DBWrapperMixin):
230
231
  )
231
232
 
232
233
  @overload
233
- def insert(self, records: DataModelType) -> tuple[int, int]: # type: ignore
234
- ...
234
+ def insert(self, records: DataModelType) -> tuple[int, int]: ...
235
235
 
236
236
  @overload
237
237
  def insert(self, records: list[DataModelType]) -> list[tuple[int, int]]: ...
@@ -335,8 +335,7 @@ class DBWrapper(DBWrapperMixin):
335
335
  return affected_rows
336
336
 
337
337
  @overload
338
- def update(self, records: DataModelType) -> int: # type: ignore
339
- ...
338
+ def update(self, records: DataModelType) -> int: ...
340
339
 
341
340
  @overload
342
341
  def update(self, records: list[DataModelType]) -> list[int]: ...
@@ -441,8 +440,7 @@ class DBWrapper(DBWrapperMixin):
441
440
  return affected_rows
442
441
 
443
442
  @overload
444
- def delete(self, records: DataModelType) -> int: # type: ignore
445
- ...
443
+ def delete(self, records: DataModelType) -> int: ...
446
444
 
447
445
  @overload
448
446
  def delete(self, records: list[DataModelType]) -> list[int]: ...
@@ -1,6 +1,7 @@
1
- from typing import AsyncGenerator, Any, overload
1
+ from collections.abc import AsyncGenerator
2
+ from typing import Any, overload
2
3
 
3
- from .common import OrderByItem, DataModelType
4
+ from .common import DataModelType, OrderByItem
4
5
  from .db_data_model import DBDataModel
5
6
  from .db_wrapper_mixin import DBWrapperMixin
6
7
 
@@ -232,8 +233,7 @@ class DBWrapperAsync(DBWrapperMixin):
232
233
  )
233
234
 
234
235
  @overload
235
- async def insert(self, records: DataModelType) -> tuple[int, int]: # type: ignore
236
- ...
236
+ async def insert(self, records: DataModelType) -> tuple[int, int]: ...
237
237
 
238
238
  @overload
239
239
  async def insert(self, records: list[DataModelType]) -> list[tuple[int, int]]: ...
@@ -336,8 +336,7 @@ class DBWrapperAsync(DBWrapperMixin):
336
336
  return affected_rows
337
337
 
338
338
  @overload
339
- async def update(self, records: DataModelType) -> int: # type: ignore
340
- ...
339
+ async def update(self, records: DataModelType) -> int: ...
341
340
 
342
341
  @overload
343
342
  async def update(self, records: list[DataModelType]) -> list[int]: ...
@@ -442,8 +441,7 @@ class DBWrapperAsync(DBWrapperMixin):
442
441
  return affected_rows
443
442
 
444
443
  @overload
445
- async def delete(self, records: DataModelType) -> int: # type: ignore
446
- ...
444
+ async def delete(self, records: DataModelType) -> int: ...
447
445
 
448
446
  @overload
449
447
  async def delete(self, records: list[DataModelType]) -> list[int]: ...
@@ -1,8 +1,7 @@
1
1
  import logging
2
+ from typing import Any, cast
2
3
 
3
- from typing import Type, cast, Any
4
-
5
- from .common import OrderByItem, NoParam, DataModelType
4
+ from .common import DataModelType, NoParam, OrderByItem
6
5
 
7
6
 
8
7
  class DBWrapperMixin:
@@ -113,7 +112,7 @@ class DBWrapperMixin:
113
112
 
114
113
  def turn_data_into_model(
115
114
  self,
116
- empty_data_class: Type[DataModelType],
115
+ empty_data_class: type[DataModelType],
117
116
  db_data: dict[str, Any],
118
117
  ) -> DataModelType:
119
118
  """
@@ -170,11 +169,8 @@ class DBWrapperMixin:
170
169
  if order_by is None:
171
170
  return None
172
171
 
173
- order_list = [
174
- f"{item[0]} {item[1] if len(item) > 1 and item[1] != None else 'ASC'}"
175
- for item in order_by
176
- ]
177
- return "ORDER BY %s" % ", ".join(order_list)
172
+ order_list = [f"{item[0]} {item[1] if len(item) > 1 and item[1] is not None else 'ASC'}" for item in order_by]
173
+ return "ORDER BY {}".format(", ".join(order_list))
178
174
 
179
175
  def limit_query(self, offset: int = 0, limit: int = 100) -> Any | None:
180
176
  """
@@ -204,36 +200,32 @@ class DBWrapperMixin:
204
200
  elif "$ends_with" in filter:
205
201
  return (f"{key} LIKE %s", f"%{filter['$ends_with']}")
206
202
  elif "$min" in filter and "$max" not in filter:
207
- return (f"{key} >= %s", filter["$min"]) # type: ignore
203
+ return (f"{key} >= %s", filter["$min"])
208
204
  elif "$max" in filter and "$min" not in filter:
209
- return (f"{key} <= %s", filter["$max"]) # type: ignore
205
+ return (f"{key} <= %s", filter["$max"])
210
206
  elif "$min" in filter and "$max" in filter:
211
- return (f"{key} BETWEEN %s AND %s", filter["$min"], filter["$max"]) # type: ignore
207
+ return (f"{key} BETWEEN %s AND %s", filter["$min"], filter["$max"])
212
208
  elif "$in" in filter:
213
209
  in_filter_1: list[Any] = cast(list[Any], filter["$in"])
214
- return (
215
- f"{key} IN (%s)" % ",".join(["%s"] * len(in_filter_1)),
216
- ) + tuple(in_filter_1)
210
+ return (f"{key} IN (%s)" % ",".join(["%s"] * len(in_filter_1)),) + tuple(in_filter_1)
217
211
  elif "$not_in" in filter:
218
212
  in_filter_2: list[Any] = cast(list[Any], filter["$in"])
219
- return (
220
- f"{key} NOT IN (%s)" % ",".join(["%s"] * len(in_filter_2)),
221
- ) + tuple(in_filter_2)
213
+ return (f"{key} NOT IN (%s)" % ",".join(["%s"] * len(in_filter_2)),) + tuple(in_filter_2)
222
214
  elif "$not" in filter:
223
- return (f"{key} != %s", filter["$not"]) # type: ignore
215
+ return (f"{key} != %s", filter["$not"])
224
216
 
225
217
  elif "$gt" in filter:
226
- return (f"{key} > %s", filter["$gt"]) # type: ignore
218
+ return (f"{key} > %s", filter["$gt"])
227
219
  elif "$gte" in filter:
228
- return (f"{key} >= %s", filter["$gte"]) # type: ignore
220
+ return (f"{key} >= %s", filter["$gte"])
229
221
  elif "$lt" in filter:
230
- return (f"{key} < %s", filter["$lt"]) # type: ignore
222
+ return (f"{key} < %s", filter["$lt"])
231
223
  elif "$lte" in filter:
232
- return (f"{key} <= %s", filter["$lte"]) # type: ignore
224
+ return (f"{key} <= %s", filter["$lte"])
233
225
  elif "$is_null" in filter:
234
- return (f"{key} IS NULL",) # type: ignore
226
+ return (f"{key} IS NULL",)
235
227
  elif "$is_not_null" in filter:
236
- return (f"{key} IS NOT NULL",) # type: ignore
228
+ return (f"{key} IS NOT NULL",)
237
229
 
238
230
  raise NotImplementedError("Filter type not supported")
239
231
  elif type(filter) is str or type(filter) is int or type(filter) is float:
@@ -244,13 +236,9 @@ class DBWrapperMixin:
244
236
  NoParam,
245
237
  )
246
238
  else:
247
- raise NotImplementedError(
248
- f"Filter type not supported: {key} = {type(filter)}"
249
- )
239
+ raise NotImplementedError(f"Filter type not supported: {key} = {type(filter)}")
250
240
 
251
- def create_filter(
252
- self, filter: dict[str, Any] | None
253
- ) -> tuple[Any, tuple[Any, ...]]:
241
+ def create_filter(self, filter: dict[str, Any] | None) -> tuple[Any, tuple[Any, ...]]:
254
242
  if filter is None or len(filter) == 0:
255
243
  return ("", tuple())
256
244
 
@@ -287,12 +275,7 @@ class DBWrapperMixin:
287
275
 
288
276
  columns = ", ".join(keys)
289
277
  values_placeholder = ", ".join(["%s"] * len(values))
290
- return (
291
- f"INSERT INTO {table_identifier} "
292
- f"({columns}) "
293
- f"VALUES ({values_placeholder}) "
294
- f"RETURNING {return_key}"
295
- )
278
+ return f"INSERT INTO {table_identifier} ({columns}) VALUES ({values_placeholder}) RETURNING {return_key}"
296
279
 
297
280
  def _format_update_query(
298
281
  self,
@@ -1,9 +1,8 @@
1
1
  import datetime
2
2
  import json
3
-
4
3
  from decimal import Decimal
5
4
  from enum import Enum
6
- from typing import Any, Type
5
+ from typing import Any
7
6
  from zoneinfo import ZoneInfo
8
7
 
9
8
 
@@ -61,7 +60,7 @@ def serialize_value(value: Any, s_type: SerializeType) -> Any:
61
60
  def deserialize_value(
62
61
  value: Any,
63
62
  s_type: SerializeType,
64
- enum_class: Type[Enum] | None = None,
63
+ enum_class: type[Enum] | None = None,
65
64
  timezone: str | datetime.tzinfo | None = None,
66
65
  ) -> Any:
67
66
  if s_type == SerializeType.DATETIME:
@@ -95,7 +94,7 @@ def deserialize_value(
95
94
 
96
95
  if s_type == SerializeType.JSON:
97
96
  if isinstance(value, dict) or isinstance(value, list) or value is None:
98
- return value # type: ignore
97
+ return value
99
98
 
100
99
  return json.loads(value)
101
100
 
@@ -1,6 +1,7 @@
1
- from typing import Any, Callable, Type, TypeVar
1
+ from collections.abc import Callable
2
+ from typing import Any, TypeVar
2
3
 
3
- AnyDataType = TypeVar("AnyDataType", bound=Type[Any])
4
+ AnyDataType = TypeVar("AnyDataType", bound=type[Any])
4
5
 
5
6
 
6
7
  def ignore_unknown_kwargs() -> Callable[[AnyDataType], AnyDataType]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: database_wrapper
3
- Version: 0.2.13
3
+ Version: 0.2.16
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,23 +33,23 @@ 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.16; 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.16; 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.16; 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.16; 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.16; 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
48
48
  Requires-Dist: ast-comments>=1.1.2; extra == "dev"
49
49
  Requires-Dist: codespell>=2.2; extra == "dev"
50
50
  Requires-Dist: build>=1.2.1; extra == "dev"
51
- Requires-Dist: black>=24.1.0; extra == "dev"
52
- Requires-Dist: mypy>=1.9.0; extra == "dev"
51
+ Requires-Dist: pyrefly; extra == "dev"
52
+ Requires-Dist: ruff; extra == "dev"
53
53
  Requires-Dist: types-setuptools>=61.0.0; extra == "dev"
54
54
  Requires-Dist: types-pymssql>=2.1.0; extra == "dev"
55
55
  Requires-Dist: types-mysqlclient>=2.2.0; extra == "dev"
@@ -0,0 +1,18 @@
1
+ database_wrapper/__init__.py,sha256=aV6eFoz03yDZr-ba0O59yECowlmfNYVUkKqSE-BYz8w,1320
2
+ database_wrapper/abc.py,sha256=zerWfTTUuYqFJoCTO4HGsCPETz0Zk4re2wKB_4x2pv4,2056
3
+ database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
4
+ database_wrapper/config.py,sha256=6VYDhm4VbLAX6Owddnlj2x--z7-wC3FHStm7GVEtBVU,334
5
+ database_wrapper/db_backend.py,sha256=2JhNlPhFfo6LeweCwIuM-SfvB1Ga9Bst4sXiUmrFKEM,7954
6
+ database_wrapper/db_data_model.py,sha256=JhpkgBcJn1vrQG3AnjQloU7fk907wdNkTXLDug_7pGA,15590
7
+ database_wrapper/db_introspector.py,sha256=ny6m56Izsd4LX-yUbJ0mWDA7GhjIBD8XiauxXwxzNfo,15349
8
+ database_wrapper/db_wrapper.py,sha256=Cxl8Pw_-CRygf85h5_v-dgh5YM0MSJ-vVhTzJocNFQg,15186
9
+ database_wrapper/db_wrapper_async.py,sha256=DQpUHwUaKVAJ11gUVSWTucsNbm2I5rx1LWRCcH1mDRc,15455
10
+ database_wrapper/db_wrapper_mixin.py,sha256=KKsu-XGi5kxEoAZXImhQEMVCwxMivzmbTV0nxsWCQg4,9688
11
+ database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ database_wrapper/serialization.py,sha256=X9N7YPZfy_D09yr61zsdgbmxUb_0cuq5Dbu8a-txd6s,2883
13
+ database_wrapper/utils/__init__.py,sha256=uC8YaJqfyFIZIeNdTRTbZwcOUVhmnS5eyOG-9gMs70c,96
14
+ database_wrapper/utils/dataclass_addons.py,sha256=UDztvz_UwYmZ8Jk_UzZjfy8YbwmPIutT1XzWaep-ht0,780
15
+ database_wrapper-0.2.16.dist-info/METADATA,sha256=5BCw_Wmosf3hdyo5zryAI9H71MkFTR0UteQj-7xP8f8,3602
16
+ database_wrapper-0.2.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ database_wrapper-0.2.16.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
18
+ database_wrapper-0.2.16.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- database_wrapper/__init__.py,sha256=cGqsWr7imdMOO7vrcGye8FVul_v3-32mw4wgrfxnhJA,1320
2
- database_wrapper/abc.py,sha256=JiQo6Yfv7xALrrHeICJCSgmyP2gHrp16Ov83mPBTGbE,2058
3
- database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
4
- database_wrapper/config.py,sha256=y4Y2g4sEbD1kgqLNpxsfkDqXHurMFfaJQiellYs09PE,334
5
- database_wrapper/db_backend.py,sha256=pV_XGu0tR5naz7Ni6BvpqcmN9U7TdQ0bRkg7YiNZlO0,7928
6
- database_wrapper/db_data_model.py,sha256=zhD_c1H5YV2f29DjBTQ5Oln99iO8hWdtcf-ijelWQnA,17000
7
- database_wrapper/db_introspector.py,sha256=8sWNmBeumCE0D-cbpBvpMSTbCU6dpDLxCPB8HaNf6KA,15463
8
- database_wrapper/db_wrapper.py,sha256=x6xsMW0epCOaj3RWWi9mj1EE1JmqM27PyS_Khvsxdvo,15231
9
- database_wrapper/db_wrapper_async.py,sha256=ud7kCDfsfrbp3FHfRS7FjbsVTnnf87sHsxctpNWuhnk,15500
10
- database_wrapper/db_wrapper_mixin.py,sha256=QCB9qjWLxeFY5f2_apJVL1rtp75spvZ1HuEApBkzID8,10071
11
- database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- database_wrapper/serialization.py,sha256=Tti1-nA7H4g3hzqr3mE2WurxOnEjFaxBJZTOR3ONZSo,2906
13
- database_wrapper/utils/__init__.py,sha256=uC8YaJqfyFIZIeNdTRTbZwcOUVhmnS5eyOG-9gMs70c,96
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,,