fixturify 0.1.9__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.
Files changed (71) hide show
  1. fixturify/__init__.py +21 -0
  2. fixturify/_utils/__init__.py +7 -0
  3. fixturify/_utils/_constants.py +10 -0
  4. fixturify/_utils/_fixture_discovery.py +165 -0
  5. fixturify/_utils/_path_resolver.py +135 -0
  6. fixturify/http_d/__init__.py +80 -0
  7. fixturify/http_d/_config.py +214 -0
  8. fixturify/http_d/_decorator.py +267 -0
  9. fixturify/http_d/_exceptions.py +153 -0
  10. fixturify/http_d/_fixture_discovery.py +33 -0
  11. fixturify/http_d/_matcher.py +372 -0
  12. fixturify/http_d/_mock_context.py +154 -0
  13. fixturify/http_d/_models.py +205 -0
  14. fixturify/http_d/_patcher.py +524 -0
  15. fixturify/http_d/_player.py +222 -0
  16. fixturify/http_d/_recorder.py +1350 -0
  17. fixturify/http_d/_stubs/__init__.py +8 -0
  18. fixturify/http_d/_stubs/_aiohttp.py +220 -0
  19. fixturify/http_d/_stubs/_connection.py +478 -0
  20. fixturify/http_d/_stubs/_httpcore.py +269 -0
  21. fixturify/http_d/_stubs/_tornado.py +95 -0
  22. fixturify/http_d/_utils.py +194 -0
  23. fixturify/json_assert/__init__.py +13 -0
  24. fixturify/json_assert/_actual_saver.py +67 -0
  25. fixturify/json_assert/_assert.py +173 -0
  26. fixturify/json_assert/_comparator.py +183 -0
  27. fixturify/json_assert/_diff_formatter.py +265 -0
  28. fixturify/json_assert/_normalizer.py +83 -0
  29. fixturify/object_mapper/__init__.py +5 -0
  30. fixturify/object_mapper/_deserializers/__init__.py +19 -0
  31. fixturify/object_mapper/_deserializers/_base.py +186 -0
  32. fixturify/object_mapper/_deserializers/_dataclass.py +52 -0
  33. fixturify/object_mapper/_deserializers/_plain.py +55 -0
  34. fixturify/object_mapper/_deserializers/_pydantic_v1.py +38 -0
  35. fixturify/object_mapper/_deserializers/_pydantic_v2.py +41 -0
  36. fixturify/object_mapper/_deserializers/_sqlalchemy.py +72 -0
  37. fixturify/object_mapper/_deserializers/_sqlmodel.py +43 -0
  38. fixturify/object_mapper/_detectors/__init__.py +5 -0
  39. fixturify/object_mapper/_detectors/_type_detector.py +186 -0
  40. fixturify/object_mapper/_serializers/__init__.py +19 -0
  41. fixturify/object_mapper/_serializers/_base.py +260 -0
  42. fixturify/object_mapper/_serializers/_dataclass.py +55 -0
  43. fixturify/object_mapper/_serializers/_plain.py +49 -0
  44. fixturify/object_mapper/_serializers/_pydantic_v1.py +49 -0
  45. fixturify/object_mapper/_serializers/_pydantic_v2.py +49 -0
  46. fixturify/object_mapper/_serializers/_sqlalchemy.py +70 -0
  47. fixturify/object_mapper/_serializers/_sqlmodel.py +54 -0
  48. fixturify/object_mapper/mapper.py +256 -0
  49. fixturify/read_d/__init__.py +5 -0
  50. fixturify/read_d/_decorator.py +193 -0
  51. fixturify/read_d/_fixture_loader.py +88 -0
  52. fixturify/sql_d/__init__.py +7 -0
  53. fixturify/sql_d/_config.py +30 -0
  54. fixturify/sql_d/_decorator.py +373 -0
  55. fixturify/sql_d/_driver_registry.py +133 -0
  56. fixturify/sql_d/_executor.py +82 -0
  57. fixturify/sql_d/_fixture_discovery.py +55 -0
  58. fixturify/sql_d/_phase.py +10 -0
  59. fixturify/sql_d/_strategies/__init__.py +11 -0
  60. fixturify/sql_d/_strategies/_aiomysql.py +63 -0
  61. fixturify/sql_d/_strategies/_aiosqlite.py +29 -0
  62. fixturify/sql_d/_strategies/_asyncpg.py +34 -0
  63. fixturify/sql_d/_strategies/_base.py +118 -0
  64. fixturify/sql_d/_strategies/_mysql.py +70 -0
  65. fixturify/sql_d/_strategies/_psycopg.py +35 -0
  66. fixturify/sql_d/_strategies/_psycopg2.py +40 -0
  67. fixturify/sql_d/_strategies/_registry.py +109 -0
  68. fixturify/sql_d/_strategies/_sqlite.py +33 -0
  69. fixturify-0.1.9.dist-info/METADATA +122 -0
  70. fixturify-0.1.9.dist-info/RECORD +71 -0
  71. fixturify-0.1.9.dist-info/WHEEL +4 -0
@@ -0,0 +1,83 @@
1
+ """Normalizer for converting various input types to comparable format."""
2
+
3
+ import json
4
+ from typing import Any, Union
5
+
6
+ from fixturify.object_mapper import ObjectMapper
7
+
8
+
9
+ class _Normalizer:
10
+ """Converts various input types to dict/list for comparison."""
11
+
12
+ def normalize(self, data: Any) -> Union[dict, list]:
13
+ """
14
+ Convert input data to dict or list for comparison.
15
+
16
+ - Objects → ObjectMapper → dict/list
17
+ - Dict → as-is
18
+ - JSON string → json.loads → dict/list
19
+ - List of objects → ObjectMapper → list
20
+
21
+ Args:
22
+ data: Input data to normalize
23
+
24
+ Returns:
25
+ Normalized dict or list ready for comparison
26
+
27
+ Raises:
28
+ ValueError: If data cannot be normalized
29
+ """
30
+ # Handle dict - return as-is
31
+ if isinstance(data, dict):
32
+ return data
33
+
34
+ # Handle list/tuple/set - may contain objects to serialize
35
+ if isinstance(data, (list, tuple, set)):
36
+ return self._normalize_collection(data)
37
+
38
+ # Handle JSON string
39
+ if isinstance(data, str):
40
+ try:
41
+ parsed = json.loads(data)
42
+ # Only accept JSON objects or arrays, not primitives
43
+ if not isinstance(parsed, (dict, list)):
44
+ raise ValueError(
45
+ f"JSON string must contain an object or array, got: {type(parsed).__name__}"
46
+ )
47
+ return parsed
48
+ except json.JSONDecodeError:
49
+ raise ValueError(
50
+ "String is not valid JSON. If comparing a string value, "
51
+ "wrap it in a dict or compare directly."
52
+ )
53
+
54
+ # Handle objects using ObjectMapper
55
+ try:
56
+ result = ObjectMapper(data).to_json()
57
+ # Ensure result is a dict or list (not a primitive)
58
+ if not isinstance(result, (dict, list)):
59
+ raise ValueError(
60
+ f"ObjectMapper returned a primitive ({type(result).__name__}). "
61
+ f"JsonAssert expects objects that serialize to dict or list."
62
+ )
63
+ return result
64
+ except Exception as e:
65
+ raise ValueError(f"Cannot normalize data to JSON: {e}") from e
66
+
67
+ def _normalize_collection(self, collection: Any) -> list:
68
+ """Normalize a collection, serializing any objects within."""
69
+ result = []
70
+ for item in collection:
71
+ if isinstance(item, dict):
72
+ result.append(item)
73
+ elif isinstance(item, (list, tuple, set)):
74
+ result.append(self._normalize_collection(item))
75
+ elif isinstance(item, (str, int, float, bool, type(None))):
76
+ result.append(item)
77
+ else:
78
+ # Serialize object using ObjectMapper
79
+ try:
80
+ result.append(ObjectMapper(item).to_json())
81
+ except Exception as e:
82
+ raise ValueError(f"Cannot serialize collection item: {e}") from e
83
+ return result
@@ -0,0 +1,5 @@
1
+ """ObjectMapper module for bidirectional object-to-JSON mapping."""
2
+
3
+ from fixturify.object_mapper.mapper import ObjectMapper
4
+
5
+ __all__ = ["ObjectMapper"]
@@ -0,0 +1,19 @@
1
+ """Deserializer implementations for different object types."""
2
+
3
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
4
+ from fixturify.object_mapper._deserializers._dataclass import _DataclassDeserializer
5
+ from fixturify.object_mapper._deserializers._pydantic_v1 import _PydanticV1Deserializer
6
+ from fixturify.object_mapper._deserializers._pydantic_v2 import _PydanticV2Deserializer
7
+ from fixturify.object_mapper._deserializers._sqlalchemy import _SQLAlchemyDeserializer
8
+ from fixturify.object_mapper._deserializers._sqlmodel import _SQLModelDeserializer
9
+ from fixturify.object_mapper._deserializers._plain import _PlainObjectDeserializer
10
+
11
+ __all__ = [
12
+ "_BaseDeserializer",
13
+ "_DataclassDeserializer",
14
+ "_PydanticV1Deserializer",
15
+ "_PydanticV2Deserializer",
16
+ "_SQLAlchemyDeserializer",
17
+ "_SQLModelDeserializer",
18
+ "_PlainObjectDeserializer",
19
+ ]
@@ -0,0 +1,186 @@
1
+ """Base deserializer class for all deserializers."""
2
+
3
+ import sys
4
+ from abc import ABC, abstractmethod
5
+ from datetime import datetime, date, time
6
+ from enum import Enum
7
+ from typing import Any, Dict, Type, TypeVar, Union, get_type_hints
8
+ from uuid import UUID
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class _BaseDeserializer(ABC):
14
+ """Abstract base class for all deserializers."""
15
+
16
+ @abstractmethod
17
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
18
+ """
19
+ Convert a dictionary to an object of target_class.
20
+
21
+ Args:
22
+ data: Dictionary data to deserialize
23
+ target_class: The class to create an instance of
24
+
25
+ Returns:
26
+ Instance of target_class
27
+ """
28
+ pass
29
+
30
+ def _get_type_hints(self, cls: Type) -> Dict[str, Any]:
31
+ """
32
+ Get type hints for a class, resolving forward references.
33
+
34
+ Args:
35
+ cls: The class to get type hints for
36
+
37
+ Returns:
38
+ Dictionary of field names to their types
39
+ """
40
+ try:
41
+ # Try to get type hints with the class's module globals
42
+ module = sys.modules.get(cls.__module__, None)
43
+ globalns = getattr(module, "__dict__", {}) if module else {}
44
+ return get_type_hints(cls, globalns=globalns, localns=None)
45
+ except Exception:
46
+ # Fallback to __annotations__ if get_type_hints fails
47
+ return getattr(cls, "__annotations__", {})
48
+
49
+ def _convert_value(self, value: Any, target_type: Type) -> Any:
50
+ """
51
+ Convert a value to the target type.
52
+
53
+ Args:
54
+ value: The value to convert
55
+ target_type: The target type
56
+
57
+ Returns:
58
+ Converted value
59
+ """
60
+ # Handle None
61
+ if value is None:
62
+ return None
63
+
64
+ # Handle Any type
65
+ if target_type is Any:
66
+ return value
67
+
68
+ # Get the origin type for generic types
69
+ origin = getattr(target_type, "__origin__", None)
70
+ args = getattr(target_type, "__args__", ())
71
+
72
+ # Handle Optional (Union with None)
73
+ if origin is Union:
74
+ # Filter out NoneType from args
75
+ non_none_args = [arg for arg in args if arg is not type(None)]
76
+ if len(non_none_args) == 1:
77
+ # This is Optional[T]
78
+ return self._convert_value(value, non_none_args[0])
79
+ # For Union with multiple types, try each one
80
+ for arg in non_none_args:
81
+ try:
82
+ return self._convert_value(value, arg)
83
+ except (ValueError, TypeError):
84
+ continue
85
+ return value
86
+
87
+ # Handle List
88
+ if origin is list or target_type is list:
89
+ if not isinstance(value, list):
90
+ return value
91
+ item_type = args[0] if args else Any
92
+ return [self._convert_value(item, item_type) for item in value]
93
+
94
+ # Handle Set
95
+ if origin is set or target_type is set:
96
+ if not isinstance(value, (list, set)):
97
+ return value
98
+ item_type = args[0] if args else Any
99
+ return {self._convert_value(item, item_type) for item in value}
100
+
101
+ # Handle Tuple
102
+ if origin is tuple or target_type is tuple:
103
+ if not isinstance(value, (list, tuple)):
104
+ return value
105
+ if args:
106
+ # Handle Tuple[T, ...] (variable-length homogeneous tuple)
107
+ # In this case, args = (T, Ellipsis)
108
+ if len(args) >= 2 and args[-1] is ...:
109
+ item_type = args[0]
110
+ return tuple(self._convert_value(item, item_type) for item in value)
111
+ # Handle Tuple[T1, T2, ...] (fixed-length heterogeneous tuple)
112
+ return tuple(
113
+ self._convert_value(item, args[i] if i < len(args) else args[-1])
114
+ for i, item in enumerate(value)
115
+ )
116
+ return tuple(value)
117
+
118
+ # Handle Dict
119
+ if origin is dict or target_type is dict:
120
+ if not isinstance(value, dict):
121
+ return value
122
+ if len(args) >= 2:
123
+ key_type, val_type = args[0], args[1]
124
+ return {
125
+ self._convert_value(k, key_type): self._convert_value(v, val_type)
126
+ for k, v in value.items()
127
+ }
128
+ return value
129
+
130
+ # Handle datetime
131
+ if target_type is datetime:
132
+ if isinstance(value, str):
133
+ return datetime.fromisoformat(value)
134
+ return value
135
+
136
+ # Handle date
137
+ if target_type is date:
138
+ if isinstance(value, str):
139
+ return date.fromisoformat(value)
140
+ return value
141
+
142
+ # Handle time
143
+ if target_type is time:
144
+ if isinstance(value, str):
145
+ return time.fromisoformat(value)
146
+ return value
147
+
148
+ # Handle UUID
149
+ if target_type is UUID:
150
+ if isinstance(value, str):
151
+ return UUID(value)
152
+ return value
153
+
154
+ # Handle Enum
155
+ if isinstance(target_type, type) and issubclass(target_type, Enum):
156
+ return target_type(value)
157
+
158
+ # Handle primitives
159
+ if target_type in (str, int, float, bool):
160
+ if isinstance(value, target_type):
161
+ return value
162
+ try:
163
+ return target_type(value)
164
+ except (ValueError, TypeError):
165
+ return value
166
+
167
+ # Handle nested objects (dict to object)
168
+ if isinstance(value, dict) and isinstance(target_type, type):
169
+ return self._deserialize_nested(value, target_type)
170
+
171
+ return value
172
+
173
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
174
+ """
175
+ Deserialize a nested object. Override in subclasses for specific handling.
176
+
177
+ Args:
178
+ data: Dictionary data
179
+ target_class: The target class
180
+
181
+ Returns:
182
+ Instance of target_class
183
+ """
184
+ # This will be overridden by subclasses
185
+ # Default: try to instantiate with **data
186
+ return target_class(**data)
@@ -0,0 +1,52 @@
1
+ """Dataclass deserializer."""
2
+
3
+ from dataclasses import fields, is_dataclass
4
+ from typing import Any, Type, TypeVar
5
+
6
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class _DataclassDeserializer(_BaseDeserializer):
12
+ """Deserializer for dataclass objects."""
13
+
14
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
15
+ """
16
+ Deserialize a dictionary to a dataclass.
17
+
18
+ Args:
19
+ data: Dictionary data to deserialize
20
+ target_class: The dataclass to create an instance of
21
+
22
+ Returns:
23
+ Instance of target_class
24
+
25
+ Raises:
26
+ TypeError: If target_class is not a dataclass
27
+ """
28
+ if not is_dataclass(target_class):
29
+ raise TypeError(f"Expected a dataclass, got {target_class}")
30
+
31
+ # Get type hints for field type information
32
+ type_hints = self._get_type_hints(target_class)
33
+
34
+ # Build kwargs for dataclass constructor
35
+ kwargs = {}
36
+ dataclass_fields = {f.name for f in fields(target_class)}
37
+
38
+ for field_name in dataclass_fields:
39
+ if field_name in data:
40
+ value = data[field_name]
41
+ target_type = type_hints.get(field_name, Any)
42
+ kwargs[field_name] = self._convert_value(value, target_type)
43
+
44
+ return target_class(**kwargs)
45
+
46
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
47
+ """Deserialize a nested object."""
48
+ if is_dataclass(target_class):
49
+ return self.deserialize(data, target_class)
50
+
51
+ # For non-dataclass nested objects, try basic instantiation
52
+ return target_class(**data)
@@ -0,0 +1,55 @@
1
+ """Plain Python object deserializer."""
2
+
3
+ from typing import Any, Type, TypeVar
4
+
5
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _PlainObjectDeserializer(_BaseDeserializer):
11
+ """Deserializer for plain Python objects."""
12
+
13
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
14
+ """
15
+ Deserialize a dictionary to a plain Python object.
16
+
17
+ Attempts to create an instance using:
18
+ 1. __init__ with keyword arguments
19
+ 2. __new__ followed by attribute assignment
20
+
21
+ Args:
22
+ data: Dictionary data to deserialize
23
+ target_class: The class to create an instance of
24
+
25
+ Returns:
26
+ Instance of target_class
27
+ """
28
+ # Get type hints for nested object conversion
29
+ type_hints = self._get_type_hints(target_class)
30
+
31
+ # Convert values to appropriate types
32
+ converted_data = {}
33
+ for key, value in data.items():
34
+ target_type = type_hints.get(key, Any)
35
+ converted_data[key] = self._convert_value(value, target_type)
36
+
37
+ # Try to create instance with __init__
38
+ try:
39
+ return target_class(**converted_data)
40
+ except TypeError:
41
+ # __init__ doesn't accept kwargs, try manual assignment
42
+ pass
43
+
44
+ # Try creating with __new__ and manual assignment
45
+ try:
46
+ instance = object.__new__(target_class)
47
+ for key, value in converted_data.items():
48
+ setattr(instance, key, value)
49
+ return instance
50
+ except Exception as e:
51
+ raise ValueError(f"Cannot create instance of {target_class.__name__}: {e}")
52
+
53
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
54
+ """Deserialize a nested plain object."""
55
+ return self.deserialize(data, target_class)
@@ -0,0 +1,38 @@
1
+ """Pydantic v1 model deserializer."""
2
+
3
+ from typing import Type, TypeVar
4
+
5
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _PydanticV1Deserializer(_BaseDeserializer):
11
+ """Deserializer for Pydantic v1 models."""
12
+
13
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
14
+ """
15
+ Deserialize a dictionary to a Pydantic v1 model.
16
+
17
+ Pydantic v1 models accept dict data directly in their constructor
18
+ and handle validation/coercion internally.
19
+
20
+ Args:
21
+ data: Dictionary data to deserialize
22
+ target_class: The Pydantic v1 model class
23
+
24
+ Returns:
25
+ Instance of target_class
26
+ """
27
+ # Pydantic v1 handles validation and type coercion
28
+ # Just pass the data directly
29
+ return target_class(**data)
30
+
31
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
32
+ """Deserialize a nested Pydantic v1 model."""
33
+ # Check if it's a Pydantic v1 model
34
+ if hasattr(target_class, "__fields__") and hasattr(target_class, "schema"):
35
+ return self.deserialize(data, target_class)
36
+
37
+ # For non-Pydantic nested objects, try basic instantiation
38
+ return target_class(**data)
@@ -0,0 +1,41 @@
1
+ """Pydantic v2 model deserializer."""
2
+
3
+ from typing import Type, TypeVar
4
+
5
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _PydanticV2Deserializer(_BaseDeserializer):
11
+ """Deserializer for Pydantic v2 models."""
12
+
13
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
14
+ """
15
+ Deserialize a dictionary to a Pydantic v2 model.
16
+
17
+ Pydantic v2 models accept dict data directly in their constructor
18
+ and handle validation/coercion internally. Can also use model_validate().
19
+
20
+ Args:
21
+ data: Dictionary data to deserialize
22
+ target_class: The Pydantic v2 model class
23
+
24
+ Returns:
25
+ Instance of target_class
26
+ """
27
+ # Pydantic v2 has model_validate for dict -> model conversion
28
+ if hasattr(target_class, "model_validate"):
29
+ return target_class.model_validate(data)
30
+
31
+ # Fallback to constructor
32
+ return target_class(**data)
33
+
34
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
35
+ """Deserialize a nested Pydantic v2 model."""
36
+ # Check if it's a Pydantic v2 model
37
+ if hasattr(target_class, "model_fields"):
38
+ return self.deserialize(data, target_class)
39
+
40
+ # For non-Pydantic nested objects, try basic instantiation
41
+ return target_class(**data)
@@ -0,0 +1,72 @@
1
+ """SQLAlchemy model deserializer."""
2
+
3
+ from typing import Any, Type, TypeVar
4
+
5
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _SQLAlchemyDeserializer(_BaseDeserializer):
11
+ """Deserializer for SQLAlchemy models."""
12
+
13
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
14
+ """
15
+ Deserialize a dictionary to a SQLAlchemy model.
16
+
17
+ Args:
18
+ data: Dictionary data to deserialize
19
+ target_class: The SQLAlchemy model class
20
+
21
+ Returns:
22
+ Instance of target_class
23
+ """
24
+ # Get column information from mapper
25
+ mapper = getattr(target_class, "__mapper__", None)
26
+ if mapper is None:
27
+ # Fallback to simple instantiation
28
+ return target_class(**data)
29
+
30
+ # Get type hints for nested object conversion
31
+ type_hints = self._get_type_hints(target_class)
32
+
33
+ # Build kwargs, filtering to only column names
34
+ column_names = {col.key for col in mapper.columns}
35
+ kwargs = {}
36
+
37
+ for key, value in data.items():
38
+ if key in column_names:
39
+ target_type = type_hints.get(key, Any)
40
+ kwargs[key] = self._convert_value(value, target_type)
41
+
42
+ # Create instance
43
+ instance = target_class(**kwargs)
44
+
45
+ # Handle relationships if present in data
46
+ for relationship in mapper.relationships:
47
+ rel_name = relationship.key
48
+ if rel_name in data:
49
+ rel_data = data[rel_name]
50
+ rel_class = relationship.mapper.class_
51
+
52
+ if isinstance(rel_data, list):
53
+ rel_instances = [
54
+ self._deserialize_nested(item, rel_class) for item in rel_data
55
+ ]
56
+ setattr(instance, rel_name, rel_instances)
57
+ elif isinstance(rel_data, dict):
58
+ rel_instance = self._deserialize_nested(rel_data, rel_class)
59
+ setattr(instance, rel_name, rel_instance)
60
+
61
+ return instance
62
+
63
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
64
+ """Deserialize a nested SQLAlchemy model."""
65
+ # Check if it's a SQLAlchemy model
66
+ if hasattr(target_class, "__tablename__") and hasattr(
67
+ target_class, "__mapper__"
68
+ ):
69
+ return self.deserialize(data, target_class)
70
+
71
+ # For non-SQLAlchemy nested objects, try basic instantiation
72
+ return target_class(**data)
@@ -0,0 +1,43 @@
1
+ """SQLModel model deserializer."""
2
+
3
+ from typing import Type, TypeVar
4
+
5
+ from fixturify.object_mapper._deserializers._base import _BaseDeserializer
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _SQLModelDeserializer(_BaseDeserializer):
11
+ """Deserializer for SQLModel models."""
12
+
13
+ def deserialize(self, data: dict, target_class: Type[T]) -> T:
14
+ """
15
+ Deserialize a dictionary to a SQLModel model.
16
+
17
+ SQLModel combines Pydantic v2 and SQLAlchemy. We use Pydantic's
18
+ model_validate for deserialization as it handles validation.
19
+
20
+ Args:
21
+ data: Dictionary data to deserialize
22
+ target_class: The SQLModel model class
23
+
24
+ Returns:
25
+ Instance of target_class
26
+ """
27
+ # SQLModel uses Pydantic v2's model_validate
28
+ if hasattr(target_class, "model_validate"):
29
+ return target_class.model_validate(data)
30
+
31
+ # Fallback to constructor
32
+ return target_class(**data)
33
+
34
+ def _deserialize_nested(self, data: dict, target_class: Type[T]) -> T:
35
+ """Deserialize a nested SQLModel model."""
36
+ # Check if it's a SQLModel model
37
+ if hasattr(target_class, "__tablename__") and hasattr(
38
+ target_class, "model_fields"
39
+ ):
40
+ return self.deserialize(data, target_class)
41
+
42
+ # For non-SQLModel nested objects, try basic instantiation
43
+ return target_class(**data)
@@ -0,0 +1,5 @@
1
+ """Type detection utilities."""
2
+
3
+ from fixturify.object_mapper._detectors._type_detector import _TypeDetector, ObjectType
4
+
5
+ __all__ = ["_TypeDetector", "ObjectType"]