ab-pydantic-patch 1.3.2__tar.gz → 1.3.3__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 (60) hide show
  1. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/PKG-INFO +1 -1
  2. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/pyproject.toml +1 -1
  3. ab_pydantic_patch-1.3.3/src/ab_core/pydantic_patch/orm_clone.py +103 -0
  4. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/.gitignore +0 -0
  5. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/LICENSE +0 -0
  6. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/README.md +0 -0
  7. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/__init__.py +0 -0
  8. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/__init__.py +0 -0
  9. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/cache.py +0 -0
  10. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/classproperty.py +0 -0
  11. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/computed_field_type_hints.py +0 -0
  12. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/config.py +0 -0
  13. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/errors.py +0 -0
  14. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/field_type_hints.py +0 -0
  15. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/fields.py +0 -0
  16. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/forward_references.py +0 -0
  17. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/operation.py +0 -0
  18. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/orm_type_hints.py +0 -0
  19. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/payload.py +0 -0
  20. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/payload_types.py +0 -0
  21. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/transform.py +0 -0
  22. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/type_hints.py +0 -0
  23. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/core/types.py +0 -0
  24. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/__init__.py +0 -0
  25. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/pydantic_examples/__init__.py +0 -0
  26. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/pydantic_examples/pydantic_computed_fields.py +0 -0
  27. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/pydantic_examples/pydantic_discriminated_union_api_schema.py +0 -0
  28. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/__init__.py +0 -0
  29. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/__init__.py +0 -0
  30. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project.py +0 -0
  31. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project_milestone.py +0 -0
  32. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project_task.py +0 -0
  33. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/quote_line_item.py +0 -0
  34. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/task_comment.py +0 -0
  35. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/user.py +0 -0
  36. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/sqlmodel_computed_fields.py +0 -0
  37. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/sqlmodel_patch.py +0 -0
  38. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/sqlmodel_self_referencing_tree.py +0 -0
  39. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/omit/__init__.py +0 -0
  40. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/omit/api.py +0 -0
  41. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/omit/config.py +0 -0
  42. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/omit/operation.py +0 -0
  43. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/orm_patch.py +0 -0
  44. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/partial/__init__.py +0 -0
  45. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/partial/api.py +0 -0
  46. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/partial/config.py +0 -0
  47. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/partial/operation.py +0 -0
  48. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/patch/__init__.py +0 -0
  49. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/patch/api.py +0 -0
  50. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/patch/config.py +0 -0
  51. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/patch/operation.py +0 -0
  52. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/pick/__init__.py +0 -0
  53. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/pick/api.py +0 -0
  54. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/pick/config.py +0 -0
  55. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/pick/operation.py +0 -0
  56. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/pydantic_jsonb.py +0 -0
  57. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/required/__init__.py +0 -0
  58. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/required/api.py +0 -0
  59. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/required/config.py +0 -0
  60. {ab_pydantic_patch-1.3.2 → ab_pydantic_patch-1.3.3}/src/ab_core/pydantic_patch/required/operation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ab-pydantic-patch
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: Python Pydantic support of TypeScript-style utility types, including Partial, Required, Pick, and Omit. Useful for PATCH endpoints driven from BaseModel / SQLModel classes.
5
5
  Author-email: Matt Coulter <mattcoul7@gmail.com>
6
6
  License-File: LICENSE
@@ -28,7 +28,7 @@ description = "Python Pydantic support of TypeScript-style utility types, includ
28
28
  name = "ab-pydantic-patch"
29
29
  readme = "README.md"
30
30
  requires-python = ">=3.12,<4.0"
31
- version = "1.3.2"
31
+ version = "1.3.3"
32
32
 
33
33
  [project.optional-dependencies]
34
34
  orm = [
@@ -0,0 +1,103 @@
1
+ """Utility for cloning SQLModel ORM objects without copying SQLAlchemy instance state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from sqlalchemy import inspect
8
+ from sqlalchemy.orm.attributes import NO_VALUE
9
+ from sqlmodel import SQLModel
10
+
11
+
12
+ def recursive_clone_scalar[T: SQLModel](
13
+ obj: T,
14
+ *,
15
+ include_primary_keys: bool = False,
16
+ include_foreign_keys: bool = True,
17
+ _memo: dict[int, SQLModel] | None = None,
18
+ ) -> T:
19
+ """Clone a SQLModel ORM object graph without copying SQLAlchemy instance state.
20
+
21
+ - Copies mapped column values.
22
+ - Copies already-loaded relationships.
23
+ - Recursively clones related SQLModel objects.
24
+ - Handles cycles/shared objects via memo.
25
+ - Does not use model_dump(), so relationships are preserved.
26
+ - Does not copy _sa_instance_state.
27
+ """
28
+ if _memo is None:
29
+ _memo = {}
30
+
31
+ obj_id = id(obj)
32
+ if obj_id in _memo:
33
+ return _memo[obj_id] # type: ignore[return-value]
34
+
35
+ state = inspect(obj)
36
+ mapper = state.mapper
37
+ cls = type(obj)
38
+
39
+ column_values: dict[str, Any] = {}
40
+
41
+ primary_key_column_keys = {column.key for column in mapper.primary_key}
42
+
43
+ foreign_key_column_keys = {column.key for column in mapper.columns if column.foreign_keys}
44
+
45
+ for column_attr in mapper.column_attrs:
46
+ key = column_attr.key
47
+
48
+ if not include_primary_keys and key in primary_key_column_keys:
49
+ continue
50
+
51
+ if not include_foreign_keys and key in foreign_key_column_keys:
52
+ continue
53
+
54
+ attr_state = state.attrs[key]
55
+ value = attr_state.loaded_value
56
+
57
+ if value is NO_VALUE:
58
+ # Avoid triggering lazy loads / missing state.
59
+ continue
60
+
61
+ column_values[key] = value
62
+
63
+ clone = cls(**column_values)
64
+ _memo[obj_id] = clone
65
+
66
+ for relationship in mapper.relationships:
67
+ key = relationship.key
68
+
69
+ attr_state = state.attrs[key]
70
+ value = attr_state.loaded_value
71
+
72
+ if value is NO_VALUE:
73
+ # Relationship was not populated, so don't accidentally lazy-load it.
74
+ continue
75
+
76
+ if value is None:
77
+ setattr(clone, key, None)
78
+ continue
79
+
80
+ if relationship.uselist:
81
+ cloned_items = [
82
+ recursive_clone_scalar(
83
+ item,
84
+ include_primary_keys=include_primary_keys,
85
+ include_foreign_keys=include_foreign_keys,
86
+ _memo=_memo,
87
+ )
88
+ for item in value
89
+ ]
90
+
91
+ setattr(clone, key, cloned_items)
92
+
93
+ else:
94
+ cloned_related = recursive_clone_scalar(
95
+ value,
96
+ include_primary_keys=include_primary_keys,
97
+ include_foreign_keys=include_foreign_keys,
98
+ _memo=_memo,
99
+ )
100
+
101
+ setattr(clone, key, cloned_related)
102
+
103
+ return clone