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.
- database_wrapper/__init__.py +4 -4
- database_wrapper/abc.py +3 -3
- database_wrapper/config.py +3 -3
- database_wrapper/db_backend.py +3 -3
- database_wrapper/db_data_model.py +90 -149
- database_wrapper/db_introspector.py +20 -19
- database_wrapper/db_wrapper.py +6 -8
- database_wrapper/db_wrapper_async.py +6 -8
- database_wrapper/db_wrapper_mixin.py +20 -37
- database_wrapper/serialization.py +3 -4
- database_wrapper/utils/dataclass_addons.py +3 -2
- {database_wrapper-0.2.13.dist-info → database_wrapper-0.2.16.dist-info}/METADATA +8 -8
- database_wrapper-0.2.16.dist-info/RECORD +18 -0
- database_wrapper-0.2.13.dist-info/RECORD +0 -18
- {database_wrapper-0.2.13.dist-info → database_wrapper-0.2.16.dist-info}/WHEEL +0 -0
- {database_wrapper-0.2.13.dist-info → database_wrapper-0.2.16.dist-info}/top_level.txt +0 -0
database_wrapper/__init__.py
CHANGED
|
@@ -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
|
|
13
|
-
from .
|
|
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
|
|
2
|
-
from typing import Any,
|
|
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 =
|
|
6
|
+
Row = dict[str, Any]
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class CursorABC(Protocol):
|
database_wrapper/config.py
CHANGED
|
@@ -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": "
|
|
7
|
-
"git_commit_date": "
|
|
8
|
-
"app_version": "0.2.
|
|
6
|
+
"git_commit_hash": "4baad61b81d9f178acd8de04c1a65f2053e06d89",
|
|
7
|
+
"git_commit_date": "09.01.2026 00:48",
|
|
8
|
+
"app_version": "0.2.16",
|
|
9
9
|
}
|
database_wrapper/db_backend.py
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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[
|
|
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 "
|
|
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
|
|
163
|
-
class_field = self.__dataclass_fields__.get(
|
|
164
|
-
if class_field is
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
264
|
+
@classmethod
|
|
265
|
+
def _should_store(cls, field_name: str, metadata: MetadataDict) -> bool:
|
|
272
266
|
"""
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
+
@classmethod
|
|
274
|
+
def _should_update(cls, field_name: str, metadata: MetadataDict) -> bool:
|
|
284
275
|
"""
|
|
285
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
313
|
-
|
|
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
|
-
|
|
313
|
+
value = serialize_value(value, serialize)
|
|
323
314
|
else:
|
|
324
|
-
|
|
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
|
|
337
|
-
|
|
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
|
-
|
|
338
|
+
value = serialize_value(value, serialize)
|
|
347
339
|
else:
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
362
|
-
-
|
|
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
|
-
#
|
|
394
|
-
disabled_at: datetime.datetime = field(
|
|
395
|
-
|
|
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=
|
|
399
|
-
update=
|
|
394
|
+
store=True,
|
|
395
|
+
update=True,
|
|
400
396
|
serialize=SerializeType.DATETIME,
|
|
401
397
|
),
|
|
402
398
|
)
|
|
403
399
|
|
|
404
|
-
|
|
405
|
-
|
|
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=
|
|
410
|
-
update=
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
@
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
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,
|
|
2
|
-
from
|
|
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,
|
|
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:
|
|
97
|
-
base:
|
|
98
|
-
enum_overrides: dict[str,
|
|
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
|
-
) ->
|
|
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:
|
|
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
|
|
131
|
+
ann: type[py_type] | None = py_type
|
|
131
132
|
else:
|
|
132
|
-
ann = py_type
|
|
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
|
|
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 =
|
|
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
|
-
|
|
206
|
-
|
|
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:
|
|
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 {}
|
|
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]
|
|
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 {}
|
|
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 {}
|
|
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:
|
database_wrapper/db_wrapper.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from
|
|
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]:
|
|
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:
|
|
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:
|
|
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
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from typing import Any, overload
|
|
2
3
|
|
|
3
|
-
from .common import
|
|
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]:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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"])
|
|
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"])
|
|
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"])
|
|
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"])
|
|
215
|
+
return (f"{key} != %s", filter["$not"])
|
|
224
216
|
|
|
225
217
|
elif "$gt" in filter:
|
|
226
|
-
return (f"{key} > %s", filter["$gt"])
|
|
218
|
+
return (f"{key} > %s", filter["$gt"])
|
|
227
219
|
elif "$gte" in filter:
|
|
228
|
-
return (f"{key} >= %s", filter["$gte"])
|
|
220
|
+
return (f"{key} >= %s", filter["$gte"])
|
|
229
221
|
elif "$lt" in filter:
|
|
230
|
-
return (f"{key} < %s", filter["$lt"])
|
|
222
|
+
return (f"{key} < %s", filter["$lt"])
|
|
231
223
|
elif "$lte" in filter:
|
|
232
|
-
return (f"{key} <= %s", filter["$lte"])
|
|
224
|
+
return (f"{key} <= %s", filter["$lte"])
|
|
233
225
|
elif "$is_null" in filter:
|
|
234
|
-
return (f"{key} IS NULL",)
|
|
226
|
+
return (f"{key} IS NULL",)
|
|
235
227
|
elif "$is_not_null" in filter:
|
|
236
|
-
return (f"{key} IS NOT NULL",)
|
|
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
|
|
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:
|
|
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
|
|
97
|
+
return value
|
|
99
98
|
|
|
100
99
|
return json.loads(value)
|
|
101
100
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, TypeVar
|
|
2
3
|
|
|
3
|
-
AnyDataType = TypeVar("AnyDataType", bound=
|
|
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.
|
|
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.
|
|
36
|
+
Requires-Dist: database_wrapper_pgsql==0.2.16; extra == "pgsql"
|
|
37
37
|
Provides-Extra: mysql
|
|
38
|
-
Requires-Dist: database_wrapper_mysql==0.2.
|
|
38
|
+
Requires-Dist: database_wrapper_mysql==0.2.16; extra == "mysql"
|
|
39
39
|
Provides-Extra: mssql
|
|
40
|
-
Requires-Dist: database_wrapper_mssql==0.2.
|
|
40
|
+
Requires-Dist: database_wrapper_mssql==0.2.16; extra == "mssql"
|
|
41
41
|
Provides-Extra: sqlite
|
|
42
|
-
Requires-Dist: database_wrapper_sqlite==0.2.
|
|
42
|
+
Requires-Dist: database_wrapper_sqlite==0.2.16; extra == "sqlite"
|
|
43
43
|
Provides-Extra: redis
|
|
44
|
-
Requires-Dist: database_wrapper_redis==0.2.
|
|
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:
|
|
52
|
-
Requires-Dist:
|
|
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,,
|
|
File without changes
|
|
File without changes
|