deev 2.1.2__tar.gz → 2.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {deev-2.1.2/src/deev.egg-info → deev-2.1.4}/PKG-INFO +1 -1
  2. {deev-2.1.2 → deev-2.1.4}/pyproject.toml +1 -1
  3. {deev-2.1.2 → deev-2.1.4}/src/deev/__init__.py +2 -2
  4. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/async_mongo_table_adapter.py +5 -1
  5. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/mongo_table_adapter.py +5 -1
  6. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/utils.py +21 -1
  7. {deev-2.1.2 → deev-2.1.4}/src/deev/translation/utils.py +75 -19
  8. {deev-2.1.2 → deev-2.1.4/src/deev.egg-info}/PKG-INFO +1 -1
  9. {deev-2.1.2 → deev-2.1.4}/LICENSE +0 -0
  10. {deev-2.1.2 → deev-2.1.4}/README.md +0 -0
  11. {deev-2.1.2 → deev-2.1.4}/setup.cfg +0 -0
  12. {deev-2.1.2 → deev-2.1.4}/src/deev/SKILL.md +0 -0
  13. {deev-2.1.2 → deev-2.1.4}/src/deev/_immutable_mixin.py +0 -0
  14. {deev-2.1.2 → deev-2.1.4}/src/deev/_migration_data.py +0 -0
  15. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/__init__.py +0 -0
  16. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/async_clickhouse_proxy_connection.py +0 -0
  17. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/async_clickhouse_proxy_cursor.py +0 -0
  18. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/async_clickhouse_table_adapter.py +0 -0
  19. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/async_clickhouse_transaction_context.py +0 -0
  20. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_ddl_generator.py +0 -0
  21. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_proxy_connection.py +0 -0
  22. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_proxy_cursor.py +0 -0
  23. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_table_adapter.py +0 -0
  24. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_transaction_context.py +0 -0
  25. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/clickhouse_type_mapper.py +0 -0
  26. {deev-2.1.2 → deev-2.1.4}/src/deev/clickhouse/utils.py +0 -0
  27. {deev-2.1.2 → deev-2.1.4}/src/deev/common/__init__.py +0 -0
  28. {deev-2.1.2 → deev-2.1.4}/src/deev/common/async_db_adapter.py +0 -0
  29. {deev-2.1.2 → deev-2.1.4}/src/deev/common/async_db_connection.py +0 -0
  30. {deev-2.1.2 → deev-2.1.4}/src/deev/common/async_db_cursor.py +0 -0
  31. {deev-2.1.2 → deev-2.1.4}/src/deev/common/async_db_table_adapter.py +0 -0
  32. {deev-2.1.2 → deev-2.1.4}/src/deev/common/async_db_transaction_context.py +0 -0
  33. {deev-2.1.2 → deev-2.1.4}/src/deev/common/connection_string.py +0 -0
  34. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_adapter.py +0 -0
  35. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_connection.py +0 -0
  36. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_context.py +0 -0
  37. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_cursor.py +0 -0
  38. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_error.py +0 -0
  39. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_migrator.py +0 -0
  40. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_parameters.py +0 -0
  41. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_table_adapter.py +0 -0
  42. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_transaction_context.py +0 -0
  43. {deev-2.1.2 → deev-2.1.4}/src/deev/common/db_type_mapper.py +0 -0
  44. {deev-2.1.2 → deev-2.1.4}/src/deev/common/description_field.py +0 -0
  45. {deev-2.1.2 → deev-2.1.4}/src/deev/common/noop_cursor.py +0 -0
  46. {deev-2.1.2 → deev-2.1.4}/src/deev/common/tlc_parser.py +0 -0
  47. {deev-2.1.2 → deev-2.1.4}/src/deev/db_migrate.py +0 -0
  48. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/__init__.py +0 -0
  49. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/entity_field_spec.py +0 -0
  50. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/entity_spec.py +0 -0
  51. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/index_options.py +0 -0
  52. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/index_order.py +0 -0
  53. {deev-2.1.2 → deev-2.1.4}/src/deev/entities/utils.py +0 -0
  54. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/__init__.py +0 -0
  55. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/async_mongo_proxy_connection.py +0 -0
  56. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/async_mongo_proxy_cursor.py +0 -0
  57. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/async_mongo_transaction_context.py +0 -0
  58. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/mongo_proxy_connection.py +0 -0
  59. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/mongo_proxy_cursor.py +0 -0
  60. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/mongo_transaction_context.py +0 -0
  61. {deev-2.1.2 → deev-2.1.4}/src/deev/mongodb/mongo_type_mapper.py +0 -0
  62. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/__init__.py +0 -0
  63. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/async_mssql_proxy_connection.py +0 -0
  64. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/async_mssql_proxy_cursor.py +0 -0
  65. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/async_mssql_table_adapter.py +0 -0
  66. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/async_mssql_transaction_context.py +0 -0
  67. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_ddl_generator.py +0 -0
  68. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_proxy_connection.py +0 -0
  69. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_proxy_cursor.py +0 -0
  70. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_table_adapter.py +0 -0
  71. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_transaction_context.py +0 -0
  72. {deev-2.1.2 → deev-2.1.4}/src/deev/mssql/mssql_type_mapper.py +0 -0
  73. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/__init__.py +0 -0
  74. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/async_mysql_proxy_connection.py +0 -0
  75. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/async_mysql_proxy_cursor.py +0 -0
  76. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/async_mysql_table_adapter.py +0 -0
  77. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/async_mysql_transaction_context.py +0 -0
  78. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_ddl_generator.py +0 -0
  79. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_proxy_connection.py +0 -0
  80. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_proxy_cursor.py +0 -0
  81. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_table_adapter.py +0 -0
  82. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_transaction_context.py +0 -0
  83. {deev-2.1.2 → deev-2.1.4}/src/deev/mysql/mysql_type_mapper.py +0 -0
  84. {deev-2.1.2 → deev-2.1.4}/src/deev/py.typed +0 -0
  85. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/__init__.py +0 -0
  86. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/async_sqlite_proxy_connection.py +0 -0
  87. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/async_sqlite_proxy_cursor.py +0 -0
  88. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/async_sqlite_table_adapter.py +0 -0
  89. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/async_sqlite_transaction_context.py +0 -0
  90. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_ddl_generator.py +0 -0
  91. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_proxy_connection.py +0 -0
  92. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_proxy_cursor.py +0 -0
  93. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_table_adapter.py +0 -0
  94. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_transaction_context.py +0 -0
  95. {deev-2.1.2 → deev-2.1.4}/src/deev/sqlite/sqlite_type_mapper.py +0 -0
  96. {deev-2.1.2 → deev-2.1.4}/src/deev/translation/__init__.py +0 -0
  97. {deev-2.1.2 → deev-2.1.4}/src/deev/translation/deev_json_decoder.py +0 -0
  98. {deev-2.1.2 → deev-2.1.4}/src/deev/translation/deev_json_encoder.py +0 -0
  99. {deev-2.1.2 → deev-2.1.4}/src/deev/utils.py +0 -0
  100. {deev-2.1.2 → deev-2.1.4}/src/deev/validation/__init__.py +0 -0
  101. {deev-2.1.2 → deev-2.1.4}/src/deev/validation/utils.py +0 -0
  102. {deev-2.1.2 → deev-2.1.4}/src/deev/validation/validation_error.py +0 -0
  103. {deev-2.1.2 → deev-2.1.4}/src/deev.egg-info/SOURCES.txt +0 -0
  104. {deev-2.1.2 → deev-2.1.4}/src/deev.egg-info/dependency_links.txt +0 -0
  105. {deev-2.1.2 → deev-2.1.4}/src/deev.egg-info/entry_points.txt +0 -0
  106. {deev-2.1.2 → deev-2.1.4}/src/deev.egg-info/requires.txt +0 -0
  107. {deev-2.1.2 → deev-2.1.4}/src/deev.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deev
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: ..an entity framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deev"
3
- version = "2.1.2"
3
+ version = "2.1.4"
4
4
  description = "..an entity framework for Python."
5
5
  keywords = ["DB", "database", "entity framework", "mapper", "sqlite", "mysql", "mongodb", "clickhouse"]
6
6
  authors = [
@@ -21,8 +21,8 @@ from .translation import hydrate, splat
21
21
  from .utils import connect
22
22
 
23
23
 
24
- __version__ = '2.1.2'
25
- __commit__ = 'f71bbe9'
24
+ __version__ = '2.1.4'
25
+ __commit__ = '41ff1f5'
26
26
  __all__ = [
27
27
  '__version__',
28
28
  '__commit__',
@@ -15,6 +15,7 @@ from ..common.db_parameters import DbParameters
15
15
  from ..common.db_type_mapper import DbTypeMapper
16
16
  from ..entities import EntitySpec, get_entity_spec
17
17
  from ..translation import hydrate, splat, to_pyobject
18
+ from ..translation.utils import to_bsonobject
18
19
  from .utils import parse_sql_where
19
20
  from .mongo_type_mapper import MongoTypeMapper
20
21
 
@@ -218,7 +219,7 @@ class AsyncMongoTableAdapter(AsyncDbTableAdapter[TEntity]):
218
219
  else dict[str, Any]()
219
220
  )
220
221
  if kwargs:
221
- for k, v in kwargs.items():
222
+ for k, v in to_bsonobject(kwargs).items():
222
223
  data[k] = v
223
224
  primary_key = {
224
225
  k: v
@@ -248,6 +249,7 @@ class AsyncMongoTableAdapter(AsyncDbTableAdapter[TEntity]):
248
249
  Reads a record from the specified table with the key represented by `kwargs`.
249
250
  """
250
251
  await self.__deferred_init()
252
+ kwargs = to_bsonobject(kwargs)
251
253
  primary_key = {
252
254
  k: v
253
255
  for k, v in kwargs.items()
@@ -274,6 +276,7 @@ class AsyncMongoTableAdapter(AsyncDbTableAdapter[TEntity]):
274
276
 
275
277
  async def delete(self, **kwargs: Any) -> None:
276
278
  await self.__deferred_init()
279
+ kwargs = to_bsonobject(kwargs)
277
280
  primary_key = {
278
281
  k: v
279
282
  for k, v in kwargs.items()
@@ -284,6 +287,7 @@ class AsyncMongoTableAdapter(AsyncDbTableAdapter[TEntity]):
284
287
 
285
288
  async def exists(self, **kwargs: Any) -> bool:
286
289
  await self.__deferred_init()
290
+ kwargs = to_bsonobject(kwargs)
287
291
  primary_key = {
288
292
  k: v
289
293
  for k, v in kwargs.items()
@@ -13,6 +13,7 @@ from ..common.db_parameters import DbParameters
13
13
  from ..common.db_type_mapper import DbTypeMapper
14
14
  from ..entities import EntitySpec, get_entity_spec
15
15
  from ..translation import hydrate, splat, to_pyobject
16
+ from ..translation.utils import to_bsonobject
16
17
  from .utils import parse_sql_where
17
18
  from .mongo_type_mapper import MongoTypeMapper
18
19
 
@@ -205,7 +206,7 @@ class MongoTableAdapter(Generic[TEntity]):
205
206
  else dict[str, Any]()
206
207
  )
207
208
  if kwargs:
208
- for k, v in kwargs.items():
209
+ for k, v in to_bsonobject(kwargs).items():
209
210
  data[k] = v
210
211
  primary_key = {
211
212
  k: v
@@ -237,6 +238,7 @@ class MongoTableAdapter(Generic[TEntity]):
237
238
  Reads a record from the specified table with the key represented by `kwargs`.
238
239
  """
239
240
  self.__deferred_init()
241
+ kwargs = to_bsonobject(kwargs)
240
242
  primary_key = {
241
243
  k: v
242
244
  for k, v in kwargs.items()
@@ -264,6 +266,7 @@ class MongoTableAdapter(Generic[TEntity]):
264
266
 
265
267
  def delete(self, **kwargs: Any) -> None:
266
268
  self.__deferred_init()
269
+ kwargs = to_bsonobject(kwargs)
267
270
  primary_key = {
268
271
  k: v
269
272
  for k, v in kwargs.items()
@@ -274,6 +277,7 @@ class MongoTableAdapter(Generic[TEntity]):
274
277
 
275
278
  def exists(self, **kwargs: Any) -> bool:
276
279
  self.__deferred_init()
280
+ kwargs = to_bsonobject(kwargs)
277
281
  primary_key = {
278
282
  k: v
279
283
  for k, v in kwargs.items()
@@ -9,6 +9,26 @@ from typing import (
9
9
  )
10
10
 
11
11
  from ..common.db_error import DbError
12
+ from ..translation.utils import to_bsonobject
13
+
14
+
15
+ def _serialize_param(value: Any) -> Any:
16
+ """Serialize a query parameter value for MongoDB storage format.
17
+
18
+ Converts ``datetime.date`` to midnight datetime, ``datetime.time`` to ISO string,
19
+ and passes through all other types unchanged (datetime, str, int, etc. are
20
+ already compatible with MongoDB storage).
21
+ """
22
+ if isinstance(value, dict) or isinstance(value, list):
23
+ return value # MongoDB operators like {'$gte': ...} pass through
24
+ return to_bsonobject(value)
25
+
26
+
27
+ def _resolve_param(value: Any) -> Any:
28
+ """Resolve a resolved parameter value, applying serialization."""
29
+ if isinstance(value, tuple):
30
+ return tuple(_serialize_param(v) for v in value)
31
+ return _serialize_param(value)
12
32
 
13
33
 
14
34
  def parse_sql_where(where_clause: str | None, parameters: tuple[Any, ...]) -> dict[str, Any]:
@@ -31,7 +51,7 @@ def parse_sql_where(where_clause: str | None, parameters: tuple[Any, ...]) -> di
31
51
  nonlocal param_idx
32
52
  if param_idx >= len(parameters):
33
53
  raise DbError('WHERE clause has more placeholders than provided parameters.')
34
- val = parameters[param_idx]
54
+ val = _resolve_param(parameters[param_idx])
35
55
  param_idx += 1
36
56
  return val
37
57
 
@@ -106,10 +106,13 @@ def to_pyobject(value: Any, hint: type) -> Any:
106
106
 
107
107
  Handles type coercion for ``int``, ``float``, ``bool``, ``UUID``, ``datetime``,
108
108
  ``date``, ``time``, ``timedelta``, ``Decimal``, ``Enum``, and complex types
109
- (``dict``, ``list``, ``set``, ``tuple``) deserialized from JSON.
109
+ (``dict``, ``list``, ``set``, ``tuple``) deserialized from JSON or BSON.
110
+
111
+ For container types with element hints (e.g. ``list[date]``, ``dict[str, time]``),
112
+ each element is recursively converted using the appropriate element type hint.
110
113
 
111
114
  :param value: The raw value from the database.
112
- :param hint: The type hint for the field (e.g. ``Optional[str]``).
115
+ :param hint: The type hint for the field (e.g. ``Optional[str]`` or ``list[date]``).
113
116
  :return: The value converted to the appropriate Python type.
114
117
  """
115
118
  if value in (None, NoneType, 'null', 'NULL'):
@@ -132,6 +135,8 @@ def to_pyobject(value: Any, hint: type) -> Any:
132
135
  if value.tzinfo is None:
133
136
  return value.replace(tzinfo=timezone.utc)
134
137
  return value
138
+ elif hint == date and isinstance(value, datetime):
139
+ return value.date()
135
140
  elif hint == date and isinstance(value, str):
136
141
  return date.fromisoformat(value)
137
142
  elif hint == time and isinstance(value, str):
@@ -139,6 +144,8 @@ def to_pyobject(value: Any, hint: type) -> Any:
139
144
  if tm.tzinfo is not None:
140
145
  return tm.replace(tzinfo=timezone.utc)
141
146
  return tm
147
+ elif hint == time and isinstance(value, time):
148
+ return value
142
149
  elif hint == timedelta and isinstance(value, int):
143
150
  return timedelta(microseconds=value)
144
151
  elif hint == Decimal:
@@ -158,6 +165,33 @@ def to_pyobject(value: Any, hint: type) -> Any:
158
165
  if not isinstance(d, org):
159
166
  d = hint(d) # type: ignore[call-arg]
160
167
  return d
168
+ elif org in (list, tuple, set):
169
+ args = get_args(hint)
170
+ if args:
171
+ element_hint = args[0]
172
+ if isinstance(value, (list, tuple, set)):
173
+ if element_hint is not Any:
174
+ if org is tuple:
175
+ return tuple(to_pyobject(v, element_hint) for v in value)
176
+ elif org is set:
177
+ return {to_pyobject(v, element_hint) for v in value}
178
+ else:
179
+ return [to_pyobject(v, element_hint) for v in value]
180
+ else:
181
+ return value
182
+ else:
183
+ return value
184
+ elif org in (Mapping, dict):
185
+ args = get_args(hint)
186
+ if len(args) >= 2:
187
+ key_hint, value_hint = args[0], args[1]
188
+ if isinstance(value, dict):
189
+ if key_hint is not Any and value_hint is not Any:
190
+ return {to_pyobject(k, key_hint): to_pyobject(v, value_hint) for k, v in value.items()}
191
+ else:
192
+ return value
193
+ else:
194
+ return value
161
195
  else:
162
196
  return value
163
197
 
@@ -192,41 +226,63 @@ def _to_json_value(value: Any) -> Any:
192
226
  return value
193
227
 
194
228
 
195
- def to_bsonobject(value: Any) -> Any:
229
+ def to_bsonobject(value: Any, hint: type | None = None) -> Any:
196
230
  """
197
231
  Convert a value for MongoDB BSON storage.
198
232
 
199
- Similar to :func:`_to_json_value` but with these differences:
200
- - UUIDs are left as Python ``UUID`` objects (PyMongo handles native BSON binary)
233
+ Unlike :func:`_to_json_value`, this leaves types that BSON supports natively
234
+ as their Python equivalents — PyMongo handles the rest:
235
+
236
+ - ``datetime.datetime`` is passed through as-is (BSON native Date, 8 bytes)
237
+ - ``datetime.date`` is converted to a midnight ``datetime`` for storage
238
+ - ``datetime.time`` is converted to an ISO 8601 string ("HH:MM:SS.ffffff")
239
+ - ``uuid.UUID`` is left as a Python ``UUID`` object (BSON binary)
201
240
  - ``set`` values are converted to ``list`` (BSON arrays)
202
- - ``Enum`` values are converted to their ``.value`` attribute
203
- - ``Enum`` is not present in ``_to_json_value``
241
+ - ``list``/``tuple``/``dict`` are recursively serialized element-by-element
242
+
243
+ When ``hint`` is provided, container element hints are extracted and used
244
+ to convert each element (e.g. ``list[date]`` converts each element via
245
+ ``to_bsonobject(v, date)`` rather than relying on runtime ``isinstance``).
204
246
 
205
247
  :param value: The Python value to convert.
248
+ :param hint: Optional type hint (e.g. ``list[date]``).
206
249
  :return: A BSON-compatible value.
207
250
  """
208
251
  if value in (None, NoneType, 'null', 'NULL'):
209
252
  return None
210
253
  if isinstance(value, datetime):
211
- if value.tzinfo is not None:
212
- return _utc_z(value)
213
- else:
214
- return value.isoformat()
254
+ return value
215
255
  elif isinstance(value, date):
216
- return value.isoformat()
256
+ return datetime(value.year, value.month, value.day)
217
257
  elif isinstance(value, time):
218
- if value.tzinfo is not None:
219
- return _utc_z_time(value)
220
- else:
221
- return value.isoformat()
258
+ return value.isoformat()
222
259
  elif isinstance(value, Decimal):
223
260
  return str(value)
224
261
  # UUID: leave as UUID object — PyMongo handles native BSON binary
225
262
  elif isinstance(value, set):
226
- return list(value)
263
+ return [to_bsonobject(v) for v in value]
227
264
  elif isinstance(value, Enum):
228
265
  return value.value
229
- # For lists, tuples, dicts: return as-is (preserves nested type round-trips)
266
+ elif isinstance(value, timedelta):
267
+ return value.days * 86_400_000_000 + value.seconds * 1_000_000 + value.microseconds
268
+ elif isinstance(value, list):
269
+ args = get_args(hint) if hint else ()
270
+ if args:
271
+ element_hint = args[0]
272
+ return [to_bsonobject(v, element_hint) for v in value]
273
+ return [to_bsonobject(v) for v in value]
274
+ elif isinstance(value, tuple):
275
+ args = get_args(hint) if hint else ()
276
+ if args:
277
+ element_hint = args[0]
278
+ return tuple(to_bsonobject(v, element_hint) for v in value)
279
+ return tuple(to_bsonobject(v) for v in value)
280
+ elif isinstance(value, dict):
281
+ args = get_args(hint) if hint else ()
282
+ if len(args) >= 2:
283
+ key_hint, value_hint = args[0], args[1]
284
+ return {to_bsonobject(k, key_hint): to_bsonobject(v, value_hint) for k, v in value.items()}
285
+ return {k: to_bsonobject(v) for k, v in value.items()}
230
286
  return value
231
287
 
232
288
 
@@ -347,7 +403,7 @@ def splat(entity: object, attrs: list[str] | None = None, to_sql: bool = False,
347
403
  elif to_sql:
348
404
  result[attr_name] = to_sqlobject(attr_value, attr_hint)
349
405
  elif to_bson:
350
- result[attr_name] = to_bsonobject(attr_value)
406
+ result[attr_name] = to_bsonobject(attr_value, attr_hint)
351
407
  else:
352
408
  result[attr_name] = _to_json_value(attr_value)
353
409
  elif field_spec.nullable:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deev
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: ..an entity framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes