ab-pydantic-patch 1.2.1__tar.gz → 1.2.2__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.
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/PKG-INFO +1 -1
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/pyproject.toml +1 -1
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/orm_patch.py +12 -6
- ab_pydantic_patch-1.2.2/src/ab_core/pydantic_patch/pydantic_jsonb.py +98 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/.gitignore +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/LICENSE +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/README.md +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/cache.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/classproperty.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/computed_field_type_hints.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/errors.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/field_type_hints.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/fields.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/forward_references.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/operation.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/orm_type_hints.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/payload.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/payload_types.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/transform.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/type_hints.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/types.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/pydantic_examples/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/pydantic_examples/pydantic_computed_fields.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/pydantic_examples/pydantic_discriminated_union_api_schema.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/app_broken.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/app_resolved.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project_milestone.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/project_task.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/task_comment.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/models/user.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/sqlmodel_examples/sqlmodel_computed_fields.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/api.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/operation.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/api.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/operation.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/api.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/operation.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/api.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/operation.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/__init__.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/api.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/config.py +0 -0
- {ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/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.2.
|
|
3
|
+
Version: 1.2.2
|
|
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.2.
|
|
31
|
+
version = "1.2.2"
|
|
32
32
|
|
|
33
33
|
[project.optional-dependencies]
|
|
34
34
|
orm = [
|
|
@@ -27,6 +27,16 @@ def _provided_values(model: BaseModel) -> dict[str, object]:
|
|
|
27
27
|
return {name: getattr(model, name) for name in model.model_fields_set}
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _patch_scalar_value(instance: Any, key: str, value: Any) -> None:
|
|
31
|
+
current_value = getattr(instance, key, None)
|
|
32
|
+
|
|
33
|
+
if isinstance(current_value, BaseModel) and isinstance(value, BaseModel):
|
|
34
|
+
recursive_patch_scalar(current_value, value)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
setattr(instance, key, value)
|
|
38
|
+
|
|
39
|
+
|
|
30
40
|
def _mapper_for(model_cls: type[Any]) -> Any | None:
|
|
31
41
|
if sa_inspect is None:
|
|
32
42
|
return None
|
|
@@ -92,15 +102,11 @@ def _recursive_patch_pydantic_scalar(
|
|
|
92
102
|
|
|
93
103
|
current_value = getattr(instance, key, None)
|
|
94
104
|
|
|
95
|
-
if isinstance(current_value, BaseModel) and isinstance(value, BaseModel):
|
|
96
|
-
recursive_patch_scalar(current_value, value)
|
|
97
|
-
continue
|
|
98
|
-
|
|
99
105
|
if isinstance(current_value, list) and isinstance(value, list):
|
|
100
106
|
setattr(instance, key, value)
|
|
101
107
|
continue
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
_patch_scalar_value(instance, key, value)
|
|
104
110
|
|
|
105
111
|
|
|
106
112
|
def _recursive_patch_orm_scalar(
|
|
@@ -123,7 +129,7 @@ def _recursive_patch_orm_scalar(
|
|
|
123
129
|
if relationship is None:
|
|
124
130
|
if key not in scalar_attributes:
|
|
125
131
|
continue
|
|
126
|
-
|
|
132
|
+
_patch_scalar_value(orm_instance, key, value)
|
|
127
133
|
continue
|
|
128
134
|
|
|
129
135
|
if value is None:
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Support Pydantic Models as a JSON Serialisable Column in Postgres.
|
|
2
|
+
|
|
3
|
+
As per: https://github.com/fastapi/sqlmodel/issues/63
|
|
4
|
+
|
|
5
|
+
This is relatively common requirement that sqlmodel has not implemented for us :(
|
|
6
|
+
|
|
7
|
+
Basically, we don't always want to use relationships and overcomplicate the ORM. Instead,
|
|
8
|
+
we just wwant to serialise the pydantic dependency as JSON withhin that column.
|
|
9
|
+
|
|
10
|
+
However, if we use JSONB directly, it requires that we define
|
|
11
|
+
|
|
12
|
+
supplier_address: dict = Field(
|
|
13
|
+
default_factory=SupplierAddress,
|
|
14
|
+
description="Supplier business address extracted from the quote.",
|
|
15
|
+
sa_column=Column(JSONB, nullable=False),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
Which isn't clean, and no long type-driven.
|
|
19
|
+
|
|
20
|
+
To preserve the type driven nature for these cases, PydanticJSONB is our friend:
|
|
21
|
+
|
|
22
|
+
supplier_address: SupplierAddress = Field(
|
|
23
|
+
default_factory=SupplierAddress,
|
|
24
|
+
description="Supplier business address extracted from the quote.",
|
|
25
|
+
sa_column=Column(PydanticJSONB(SupplierAddress), nullable=False),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
TODO: This does cause some incompaibility with alembic (I think), so we will need
|
|
29
|
+
to address that once alembic is added to this codebase. It looks something like this:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
33
|
+
from knowledgeburst.fencing.quote_stateful.pydantic_jsonb import PydanticJSONB
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from alembic.autogenerate.api import AutogenContext
|
|
37
|
+
|
|
38
|
+
def render_item(
|
|
39
|
+
type_: str,
|
|
40
|
+
obj: Any, # noqa: ANN401
|
|
41
|
+
autogen_context: AutogenContext,
|
|
42
|
+
) -> str | Literal[False]:
|
|
43
|
+
if type_ == "type" and isinstance(obj, PydanticJSONB):
|
|
44
|
+
autogen_context.imports.add("import sqlalchemy as sa")
|
|
45
|
+
autogen_context.imports.add("from sqlalchemy.dialects import postgresql")
|
|
46
|
+
return "postgresql.JSONB(astext_type=sa.Text())"
|
|
47
|
+
return False
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
In that case, we probably want to move this module into a more central db module. Right now
|
|
51
|
+
it is just coupled with fencing as we are keeping the fencing module as independent as possible.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from typing import Any, cast, override
|
|
55
|
+
|
|
56
|
+
from pydantic import TypeAdapter
|
|
57
|
+
from sqlalchemy import Dialect, TypeDecorator
|
|
58
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PydanticJSONB[T](TypeDecorator):
|
|
62
|
+
"""A SQLAlchemy TypeDecorator that serializes/deserializes Pydantic models to/from JSONB.
|
|
63
|
+
|
|
64
|
+
This allows storing Pydantic models in PostgreSQL JSONB columns while maintaining
|
|
65
|
+
type safety and validation.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
impl = JSONB
|
|
69
|
+
cache_ok = True
|
|
70
|
+
|
|
71
|
+
def __init__(self, pydantic_type: type[T]) -> None:
|
|
72
|
+
"""Initialize the PydanticJSONB type decorator.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
pydantic_type: The Pydantic model class to serialize/deserialize.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
super().__init__()
|
|
79
|
+
self.adapter = TypeAdapter(pydantic_type)
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
def process_bind_param(self, value: T | None, dialect: Dialect) -> Any:
|
|
83
|
+
if value is None:
|
|
84
|
+
return None
|
|
85
|
+
return self.adapter.dump_python(value, mode="json")
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
def process_result_value(self, value: Any, dialect: Dialect) -> T | None:
|
|
89
|
+
if value is None:
|
|
90
|
+
return None
|
|
91
|
+
return self.adapter.validate_python(value)
|
|
92
|
+
|
|
93
|
+
def coerce_compared_value(self, op: Any, value: Any) -> Any:
|
|
94
|
+
"""Coerce the compared value for SQL operations.
|
|
95
|
+
|
|
96
|
+
Delegates to the underlying JSONB implementation for comparison operations.
|
|
97
|
+
"""
|
|
98
|
+
return cast(JSONB, self.impl).coerce_compared_value(op, value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/__init__.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/cache.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/classproperty.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/errors.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/operation.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/payload.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/payload_types.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/transform.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/type_hints.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/core/types.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/examples/__init__.py
RENAMED
|
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
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/omit/operation.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/__init__.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/api.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/partial/operation.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/patch/operation.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/pick/operation.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/__init__.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/api.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/config.py
RENAMED
|
File without changes
|
{ab_pydantic_patch-1.2.1 → ab_pydantic_patch-1.2.2}/src/ab_core/pydantic_patch/required/operation.py
RENAMED
|
File without changes
|