voltwire-db-types-json 0.0.1__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.
- voltwire_db_types_json-0.0.1/.gitignore +221 -0
- voltwire_db_types_json-0.0.1/PKG-INFO +69 -0
- voltwire_db_types_json-0.0.1/README.md +58 -0
- voltwire_db_types_json-0.0.1/pyproject.toml +22 -0
- voltwire_db_types_json-0.0.1/src/voltwire/db/types/json/__init__.py +8 -0
- voltwire_db_types_json-0.0.1/src/voltwire/db/types/json/py.typed +0 -0
- voltwire_db_types_json-0.0.1/src/voltwire/db/types/json/types.py +83 -0
- voltwire_db_types_json-0.0.1/tests/test_json_types.py +64 -0
- voltwire_db_types_json-0.0.1/tests/test_sample.py +6 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
.idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
|
|
220
|
+
# Local tool state
|
|
221
|
+
.omc/
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: voltwire-db-types-json
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Custom SQLAlchemy JSONB column types backed by Pydantic models
|
|
5
|
+
Author-email: Hermann Steidel <hsteidel.software@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: <4.0,>=3.13
|
|
8
|
+
Requires-Dist: pydantic<3,>=2.9
|
|
9
|
+
Requires-Dist: sqlalchemy<3,>=2.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
<img src="https://raw.githubusercontent.com/hsteidel/voltwire/main/assets/icons/db-types-json.svg" alt="" width="56" height="56" align="left">
|
|
13
|
+
|
|
14
|
+
# voltwire-db-types-json
|
|
15
|
+
|
|
16
|
+
Custom SQLAlchemy column types for PostgreSQL JSONB columns, backed by Pydantic models. Handles serialisation, deserialisation, and validation transparently — your columns read and write Pydantic model instances directly.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install voltwire-db-types-json
|
|
22
|
+
# or with Poetry:
|
|
23
|
+
poetry add voltwire-db-types-json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Types
|
|
27
|
+
|
|
28
|
+
### `JSONBList[T]` — JSONB array as a list of Pydantic models
|
|
29
|
+
|
|
30
|
+
Returns an empty list when the column value is `NULL`.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pydantic import BaseModel
|
|
34
|
+
from sqlalchemy.orm import mapped_column, Mapped
|
|
35
|
+
from voltwire.db.types.json import JSONBList
|
|
36
|
+
|
|
37
|
+
class Tag(BaseModel):
|
|
38
|
+
name: str
|
|
39
|
+
colour: str
|
|
40
|
+
|
|
41
|
+
class Article(Base):
|
|
42
|
+
__tablename__ = "articles"
|
|
43
|
+
tags: Mapped[list[Tag]] = mapped_column(JSONBList(Tag), default=list)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `JSONBObject[T]` — JSONB object as a single Pydantic model
|
|
47
|
+
|
|
48
|
+
Returns `None` when the column value is `NULL` or validation fails (logs an error, does not raise).
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from voltwire.db.types.json import JSONBObject
|
|
52
|
+
|
|
53
|
+
class Address(BaseModel):
|
|
54
|
+
street: str
|
|
55
|
+
city: str
|
|
56
|
+
|
|
57
|
+
class User(Base):
|
|
58
|
+
__tablename__ = "users"
|
|
59
|
+
address: Mapped[Address | None] = mapped_column(JSONBObject(Address), nullable=True)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Logging
|
|
63
|
+
|
|
64
|
+
Validation errors in `JSONBObject` are logged to the `voltwire.db.types.json.types` logger namespace and return `None` rather than raising — this prevents a single bad row from crashing the application.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import logging
|
|
68
|
+
logging.getLogger("voltwire.db.types.json").setLevel(logging.DEBUG)
|
|
69
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/hsteidel/voltwire/main/assets/icons/db-types-json.svg" alt="" width="56" height="56" align="left">
|
|
2
|
+
|
|
3
|
+
# voltwire-db-types-json
|
|
4
|
+
|
|
5
|
+
Custom SQLAlchemy column types for PostgreSQL JSONB columns, backed by Pydantic models. Handles serialisation, deserialisation, and validation transparently — your columns read and write Pydantic model instances directly.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install voltwire-db-types-json
|
|
11
|
+
# or with Poetry:
|
|
12
|
+
poetry add voltwire-db-types-json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Types
|
|
16
|
+
|
|
17
|
+
### `JSONBList[T]` — JSONB array as a list of Pydantic models
|
|
18
|
+
|
|
19
|
+
Returns an empty list when the column value is `NULL`.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from pydantic import BaseModel
|
|
23
|
+
from sqlalchemy.orm import mapped_column, Mapped
|
|
24
|
+
from voltwire.db.types.json import JSONBList
|
|
25
|
+
|
|
26
|
+
class Tag(BaseModel):
|
|
27
|
+
name: str
|
|
28
|
+
colour: str
|
|
29
|
+
|
|
30
|
+
class Article(Base):
|
|
31
|
+
__tablename__ = "articles"
|
|
32
|
+
tags: Mapped[list[Tag]] = mapped_column(JSONBList(Tag), default=list)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `JSONBObject[T]` — JSONB object as a single Pydantic model
|
|
36
|
+
|
|
37
|
+
Returns `None` when the column value is `NULL` or validation fails (logs an error, does not raise).
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from voltwire.db.types.json import JSONBObject
|
|
41
|
+
|
|
42
|
+
class Address(BaseModel):
|
|
43
|
+
street: str
|
|
44
|
+
city: str
|
|
45
|
+
|
|
46
|
+
class User(Base):
|
|
47
|
+
__tablename__ = "users"
|
|
48
|
+
address: Mapped[Address | None] = mapped_column(JSONBObject(Address), nullable=True)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Logging
|
|
52
|
+
|
|
53
|
+
Validation errors in `JSONBObject` are logged to the `voltwire.db.types.json.types` logger namespace and return `None` rather than raising — this prevents a single bad row from crashing the application.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import logging
|
|
57
|
+
logging.getLogger("voltwire.db.types.json").setLevel(logging.DEBUG)
|
|
58
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "voltwire-db-types-json"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Custom SQLAlchemy JSONB column types backed by Pydantic models"
|
|
5
|
+
authors = [{name = "Hermann Steidel", email = "hsteidel.software@gmail.com"}]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.13,<4.0"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"sqlalchemy>=2.0,<3",
|
|
11
|
+
"pydantic>=2.9,<3",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[dependency-groups]
|
|
15
|
+
dev = ["pytest>=8.0"]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
build-backend = "hatchling.build"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/voltwire"]
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ValidationError
|
|
5
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
6
|
+
from sqlalchemy.types import TypeDecorator
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T", bound=BaseModel)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JSONBList(TypeDecorator, Generic[T]):
|
|
14
|
+
"""SQLAlchemy column type for a JSONB array, deserialized as a list of Pydantic models.
|
|
15
|
+
|
|
16
|
+
Returns an empty list when the column value is ``NULL``.
|
|
17
|
+
|
|
18
|
+
Example::
|
|
19
|
+
|
|
20
|
+
class Tag(BaseModel):
|
|
21
|
+
name: str
|
|
22
|
+
colour: str
|
|
23
|
+
|
|
24
|
+
class Article(Base):
|
|
25
|
+
tags: Mapped[list[Tag]] = mapped_column(JSONBList(Tag), default=list)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
impl = JSONB
|
|
29
|
+
|
|
30
|
+
def __init__(self, item_class: type[T]):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.item_class = item_class
|
|
33
|
+
|
|
34
|
+
def process_result_value(self, value, dialect) -> list[T]:
|
|
35
|
+
if value is None:
|
|
36
|
+
return []
|
|
37
|
+
return [self.item_class.model_validate(item) for item in value]
|
|
38
|
+
|
|
39
|
+
def process_bind_param(self, value: list[T] | None, dialect):
|
|
40
|
+
if value is None:
|
|
41
|
+
return None
|
|
42
|
+
return [item.model_dump(mode="json") for item in value]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class JSONBObject(TypeDecorator, Generic[T]):
|
|
46
|
+
"""SQLAlchemy column type for a JSONB object, deserialised as a single Pydantic model.
|
|
47
|
+
|
|
48
|
+
Returns ``None`` when the column value is ``NULL`` or validation fails (logs an error).
|
|
49
|
+
|
|
50
|
+
Example::
|
|
51
|
+
|
|
52
|
+
class Address(BaseModel):
|
|
53
|
+
street: str
|
|
54
|
+
city: str
|
|
55
|
+
|
|
56
|
+
class User(Base):
|
|
57
|
+
address: Mapped[Address | None] = mapped_column(JSONBObject(Address), nullable=True)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
impl = JSONB
|
|
61
|
+
cache_ok = True
|
|
62
|
+
|
|
63
|
+
def __init__(self, item_class: type[T]):
|
|
64
|
+
super().__init__()
|
|
65
|
+
self.item_class = item_class
|
|
66
|
+
|
|
67
|
+
def process_result_value(self, value, dialect) -> T | None:
|
|
68
|
+
if value is None:
|
|
69
|
+
return None
|
|
70
|
+
try:
|
|
71
|
+
return self.item_class.model_validate(value)
|
|
72
|
+
except ValidationError as e:
|
|
73
|
+
logger.error(
|
|
74
|
+
"JSONBObject validation failed for %s - returning None to prevent crash: %s",
|
|
75
|
+
self.item_class.__name__,
|
|
76
|
+
e,
|
|
77
|
+
)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def process_bind_param(self, value: T | None, dialect):
|
|
81
|
+
if value is None:
|
|
82
|
+
return None
|
|
83
|
+
return value.model_dump(mode="json")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from voltwire.db.types.json import JSONBList, JSONBObject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# --- Fixtures ---
|
|
7
|
+
|
|
8
|
+
class Tag(BaseModel):
|
|
9
|
+
name: str
|
|
10
|
+
colour: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Address(BaseModel):
|
|
14
|
+
street: str
|
|
15
|
+
city: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# --- JSONBList ---
|
|
19
|
+
|
|
20
|
+
class TestJSONBList:
|
|
21
|
+
def test_process_result_value_deserialises_list(self):
|
|
22
|
+
col = JSONBList(Tag)
|
|
23
|
+
result = col.process_result_value([{"name": "python", "colour": "blue"}], dialect=None)
|
|
24
|
+
assert result == [Tag(name="python", colour="blue")]
|
|
25
|
+
|
|
26
|
+
def test_process_result_value_returns_empty_list_for_null(self):
|
|
27
|
+
col = JSONBList(Tag)
|
|
28
|
+
assert col.process_result_value(None, dialect=None) == []
|
|
29
|
+
|
|
30
|
+
def test_process_bind_param_serialises_list(self):
|
|
31
|
+
col = JSONBList(Tag)
|
|
32
|
+
result = col.process_bind_param([Tag(name="python", colour="blue")], dialect=None)
|
|
33
|
+
assert result == [{"name": "python", "colour": "blue"}]
|
|
34
|
+
|
|
35
|
+
def test_process_bind_param_returns_none_for_null(self):
|
|
36
|
+
col = JSONBList(Tag)
|
|
37
|
+
assert col.process_bind_param(None, dialect=None) is None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# --- JSONBObject ---
|
|
41
|
+
|
|
42
|
+
class TestJSONBObject:
|
|
43
|
+
def test_process_result_value_deserialises_object(self):
|
|
44
|
+
col = JSONBObject(Address)
|
|
45
|
+
result = col.process_result_value({"street": "1 Main St", "city": "Springfield"}, dialect=None)
|
|
46
|
+
assert result == Address(street="1 Main St", city="Springfield")
|
|
47
|
+
|
|
48
|
+
def test_process_result_value_returns_none_for_null(self):
|
|
49
|
+
col = JSONBObject(Address)
|
|
50
|
+
assert col.process_result_value(None, dialect=None) is None
|
|
51
|
+
|
|
52
|
+
def test_process_result_value_returns_none_on_validation_error(self):
|
|
53
|
+
col = JSONBObject(Address)
|
|
54
|
+
result = col.process_result_value({"bad": "data"}, dialect=None)
|
|
55
|
+
assert result is None
|
|
56
|
+
|
|
57
|
+
def test_process_bind_param_serialises_object(self):
|
|
58
|
+
col = JSONBObject(Address)
|
|
59
|
+
result = col.process_bind_param(Address(street="1 Main St", city="Springfield"), dialect=None)
|
|
60
|
+
assert result == {"street": "1 Main St", "city": "Springfield"}
|
|
61
|
+
|
|
62
|
+
def test_process_bind_param_returns_none_for_null(self):
|
|
63
|
+
col = JSONBObject(Address)
|
|
64
|
+
assert col.process_bind_param(None, dialect=None) is None
|