pydantic-json-patch 0.1.3__tar.gz → 0.2.0__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.
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/PKG-INFO +33 -2
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/README.md +32 -1
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/pyproject.toml +1 -1
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/src/pydantic_json_patch/__init__.py +1 -3
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/src/pydantic_json_patch/models.py +17 -17
- {pydantic_json_patch-0.1.3 → pydantic_json_patch-0.2.0}/src/pydantic_json_patch/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic_json_patch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Pydantic models for implementing JSON Patch.
|
|
5
5
|
Author: Jonathan Sharpe
|
|
6
6
|
Author-email: Jonathan Sharpe <mail@jonrshar.pe>
|
|
@@ -90,7 +90,7 @@ CopyOp(op='copy', path='/foo/bar~1new', from_='/foo/bar~0old')
|
|
|
90
90
|
('foo', 'bar~old')
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
Similarly, the `create` factory methods can accept
|
|
93
|
+
Similarly, the `create` factory methods can accept sequences of tokens, and will encode them appropriately:
|
|
94
94
|
|
|
95
95
|
```python
|
|
96
96
|
>>> from pydantic_json_patch import TestOp
|
|
@@ -139,17 +139,48 @@ uv sync
|
|
|
139
139
|
uv run pre-commit install
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
The pre-commit hooks will ensure that the code style checks (using [isort] and [ruff]) are applied.
|
|
143
|
+
|
|
144
|
+
### Testing
|
|
145
|
+
|
|
146
|
+
The test suite uses [pytest] and can be run with:
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
uv run pytest
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Additionally, there is [ty] type-checking that can be run with:
|
|
153
|
+
|
|
154
|
+
```shell
|
|
155
|
+
uv run ty check
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### FastAPI
|
|
159
|
+
|
|
160
|
+
You can preview the FastAPI/Swagger documentation by running:
|
|
161
|
+
|
|
162
|
+
```shell
|
|
163
|
+
uv run fastapi dev tests/app.py
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
and visiting the Documentation link that's logged in the console.
|
|
167
|
+
This will auto-restart as you make changes.
|
|
168
|
+
|
|
142
169
|
[ci-badge]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml/badge.svg
|
|
143
170
|
[ci-page]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml
|
|
144
171
|
[coverage-badge]: https://coveralls.io/repos/github/textbook/pydantic_json_patch/badge.svg?branch=main
|
|
145
172
|
[coverage-page]: https://coveralls.io/github/textbook/pydantic_json_patch?branch=main
|
|
146
173
|
[fastapi]: https://fastapi.tiangolo.com/
|
|
174
|
+
[isort]: https://pycqa.github.io/isort/
|
|
147
175
|
[json patch]: https://datatracker.ietf.org/doc/html/rfc6902/
|
|
148
176
|
[json pointer]: https://datatracker.ietf.org/doc/html/rfc6901/
|
|
149
177
|
[pydantic]: https://docs.pydantic.dev/latest/
|
|
150
178
|
[pypi]: https://pypi.org/
|
|
151
179
|
[pypi-badge]: https://img.shields.io/pypi/v/pydantic-json-patch?logo=python&logoColor=white&label=PyPI
|
|
152
180
|
[pypi-page]: https://pypi.org/project/pydantic-json-patch/
|
|
181
|
+
[pytest]: https://docs.pytest.org/en/stable/
|
|
182
|
+
[ruff]: https://docs.astral.sh/ruff/
|
|
153
183
|
[swagger-example]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-example.png?raw=true
|
|
154
184
|
[swagger-schemas]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-schemas.png?raw=true
|
|
185
|
+
[ty]: https://docs.astral.sh/ty/
|
|
155
186
|
[uv]: https://docs.astral.sh/uv/
|
|
@@ -58,7 +58,7 @@ CopyOp(op='copy', path='/foo/bar~1new', from_='/foo/bar~0old')
|
|
|
58
58
|
('foo', 'bar~old')
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Similarly, the `create` factory methods can accept
|
|
61
|
+
Similarly, the `create` factory methods can accept sequences of tokens, and will encode them appropriately:
|
|
62
62
|
|
|
63
63
|
```python
|
|
64
64
|
>>> from pydantic_json_patch import TestOp
|
|
@@ -107,17 +107,48 @@ uv sync
|
|
|
107
107
|
uv run pre-commit install
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
The pre-commit hooks will ensure that the code style checks (using [isort] and [ruff]) are applied.
|
|
111
|
+
|
|
112
|
+
### Testing
|
|
113
|
+
|
|
114
|
+
The test suite uses [pytest] and can be run with:
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
uv run pytest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Additionally, there is [ty] type-checking that can be run with:
|
|
121
|
+
|
|
122
|
+
```shell
|
|
123
|
+
uv run ty check
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### FastAPI
|
|
127
|
+
|
|
128
|
+
You can preview the FastAPI/Swagger documentation by running:
|
|
129
|
+
|
|
130
|
+
```shell
|
|
131
|
+
uv run fastapi dev tests/app.py
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
and visiting the Documentation link that's logged in the console.
|
|
135
|
+
This will auto-restart as you make changes.
|
|
136
|
+
|
|
110
137
|
[ci-badge]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml/badge.svg
|
|
111
138
|
[ci-page]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml
|
|
112
139
|
[coverage-badge]: https://coveralls.io/repos/github/textbook/pydantic_json_patch/badge.svg?branch=main
|
|
113
140
|
[coverage-page]: https://coveralls.io/github/textbook/pydantic_json_patch?branch=main
|
|
114
141
|
[fastapi]: https://fastapi.tiangolo.com/
|
|
142
|
+
[isort]: https://pycqa.github.io/isort/
|
|
115
143
|
[json patch]: https://datatracker.ietf.org/doc/html/rfc6902/
|
|
116
144
|
[json pointer]: https://datatracker.ietf.org/doc/html/rfc6901/
|
|
117
145
|
[pydantic]: https://docs.pydantic.dev/latest/
|
|
118
146
|
[pypi]: https://pypi.org/
|
|
119
147
|
[pypi-badge]: https://img.shields.io/pypi/v/pydantic-json-patch?logo=python&logoColor=white&label=PyPI
|
|
120
148
|
[pypi-page]: https://pypi.org/project/pydantic-json-patch/
|
|
149
|
+
[pytest]: https://docs.pytest.org/en/stable/
|
|
150
|
+
[ruff]: https://docs.astral.sh/ruff/
|
|
121
151
|
[swagger-example]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-example.png?raw=true
|
|
122
152
|
[swagger-schemas]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-schemas.png?raw=true
|
|
153
|
+
[ty]: https://docs.astral.sh/ty/
|
|
123
154
|
[uv]: https://docs.astral.sh/uv/
|
|
@@ -3,7 +3,7 @@ from importlib.metadata import version
|
|
|
3
3
|
|
|
4
4
|
from pydantic import Discriminator
|
|
5
5
|
|
|
6
|
-
from .models import AddOp, CopyOp, MoveOp,
|
|
6
|
+
from .models import AddOp, CopyOp, MoveOp, RemoveOp, ReplaceOp, TestOp
|
|
7
7
|
|
|
8
8
|
__version__ = version(__name__)
|
|
9
9
|
|
|
@@ -18,10 +18,8 @@ __all__ = [
|
|
|
18
18
|
"CopyOp",
|
|
19
19
|
"JsonPatch",
|
|
20
20
|
"MoveOp",
|
|
21
|
-
"Op",
|
|
22
21
|
"Operation",
|
|
23
22
|
"RemoveOp",
|
|
24
23
|
"ReplaceOp",
|
|
25
24
|
"TestOp",
|
|
26
|
-
"Tokens",
|
|
27
25
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import typing as tp
|
|
3
|
+
from collections.abc import Sequence
|
|
3
4
|
from functools import cached_property
|
|
4
5
|
|
|
5
6
|
import typing_extensions as tx
|
|
@@ -7,9 +8,6 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, model_validat
|
|
|
7
8
|
|
|
8
9
|
_JSON_POINTER = re.compile(r"^(?:/(?:[^/~]|~[01])+)*$")
|
|
9
10
|
|
|
10
|
-
T = tp.TypeVar("T")
|
|
11
|
-
Op: tp.TypeAlias = tp.Literal["add", "copy", "move", "remove", "replace", "test"]
|
|
12
|
-
Tokens: tp.TypeAlias = tuple[str, ...]
|
|
13
11
|
|
|
14
12
|
# region base models
|
|
15
13
|
|
|
@@ -21,30 +19,30 @@ class _BaseOp(BaseModel):
|
|
|
21
19
|
)
|
|
22
20
|
|
|
23
21
|
@classmethod
|
|
24
|
-
def create(cls, *, path: str |
|
|
22
|
+
def create(cls, *, path: str | Sequence[str], **kwargs) -> tx.Self:
|
|
25
23
|
(op,) = tp.get_args(cls.model_fields["op"].annotation)
|
|
26
24
|
pointer = path if isinstance(path, str) else cls._dump_pointer(path)
|
|
27
25
|
return cls(op=op, path=pointer, **kwargs)
|
|
28
26
|
|
|
29
|
-
op:
|
|
27
|
+
op: tp.Literal["add", "copy", "move", "remove", "replace", "test"]
|
|
30
28
|
"""The operation being represented."""
|
|
31
29
|
|
|
32
30
|
path: str = Field(examples=["/a/b/c"], pattern=_JSON_POINTER)
|
|
33
31
|
"""A JSON pointer representing the path to apply the operation to."""
|
|
34
32
|
|
|
35
33
|
@cached_property
|
|
36
|
-
def path_tokens(self) ->
|
|
34
|
+
def path_tokens(self) -> tuple[str, ...]:
|
|
37
35
|
"""The decoded tokens in the 'path' JSON pointer."""
|
|
38
36
|
return self._load_pointer(self.path)
|
|
39
37
|
|
|
40
38
|
@staticmethod
|
|
41
|
-
def _dump_pointer(pointer:
|
|
39
|
+
def _dump_pointer(pointer: Sequence[str]) -> str:
|
|
42
40
|
return "/".join(
|
|
43
41
|
["", *(token.replace("~", "~0").replace("/", "~1") for token in pointer)]
|
|
44
42
|
)
|
|
45
43
|
|
|
46
44
|
@staticmethod
|
|
47
|
-
def _load_pointer(pointer: str) ->
|
|
45
|
+
def _load_pointer(pointer: str) -> tuple[str, ...]:
|
|
48
46
|
return tuple(
|
|
49
47
|
token.replace("~1", "/").replace("~0", "~")
|
|
50
48
|
for token in pointer.split("/")[1:]
|
|
@@ -53,7 +51,9 @@ class _BaseOp(BaseModel):
|
|
|
53
51
|
|
|
54
52
|
class _FromOp(_BaseOp):
|
|
55
53
|
@classmethod
|
|
56
|
-
def create(
|
|
54
|
+
def create(
|
|
55
|
+
cls, *, path: str | Sequence[str], from_: str | Sequence[str]
|
|
56
|
+
) -> tx.Self: # type: ignore[invalid-method-override]
|
|
57
57
|
pointer = from_ if isinstance(from_, str) else cls._dump_pointer(from_)
|
|
58
58
|
return super().create(path=path, **{"from": pointer})
|
|
59
59
|
|
|
@@ -73,17 +73,17 @@ class _FromOp(_BaseOp):
|
|
|
73
73
|
return data
|
|
74
74
|
|
|
75
75
|
@cached_property
|
|
76
|
-
def from_tokens(self) ->
|
|
76
|
+
def from_tokens(self) -> tuple[str, ...]:
|
|
77
77
|
"""The decoded tokens in the 'from' JSON pointer."""
|
|
78
78
|
return self._load_pointer(self.from_)
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
class _ValueOp(_BaseOp
|
|
81
|
+
class _ValueOp(_BaseOp):
|
|
82
82
|
@classmethod
|
|
83
|
-
def create(cls, *, path: str |
|
|
83
|
+
def create(cls, *, path: str | Sequence[str], value: tp.Any) -> tx.Self: # type: ignore[invalid-method-override]
|
|
84
84
|
return super().create(path=path, value=value)
|
|
85
85
|
|
|
86
|
-
value:
|
|
86
|
+
value: tp.Any = Field(examples=[42])
|
|
87
87
|
"""The value to use in the operation."""
|
|
88
88
|
|
|
89
89
|
|
|
@@ -92,7 +92,7 @@ class _ValueOp(_BaseOp, tp.Generic[T]):
|
|
|
92
92
|
# region public models
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
class AddOp(_ValueOp
|
|
95
|
+
class AddOp(_ValueOp):
|
|
96
96
|
op: tp.Literal["add"]
|
|
97
97
|
|
|
98
98
|
|
|
@@ -106,17 +106,17 @@ class MoveOp(_FromOp):
|
|
|
106
106
|
|
|
107
107
|
class RemoveOp(_BaseOp):
|
|
108
108
|
@classmethod
|
|
109
|
-
def create(cls, *, path: str |
|
|
109
|
+
def create(cls, *, path: str | Sequence[str]) -> tx.Self: # type: ignore[invalid-method-override]
|
|
110
110
|
return super().create(path=path)
|
|
111
111
|
|
|
112
112
|
op: tp.Literal["remove"]
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
class ReplaceOp(_ValueOp
|
|
115
|
+
class ReplaceOp(_ValueOp):
|
|
116
116
|
op: tp.Literal["replace"]
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
class TestOp(_ValueOp
|
|
119
|
+
class TestOp(_ValueOp):
|
|
120
120
|
op: tp.Literal["test"]
|
|
121
121
|
|
|
122
122
|
|
|
File without changes
|