pydantic-json-patch 0.1.2__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.2 → pydantic_json_patch-0.2.0}/PKG-INFO +44 -2
- {pydantic_json_patch-0.1.2 → pydantic_json_patch-0.2.0}/README.md +43 -1
- {pydantic_json_patch-0.1.2 → pydantic_json_patch-0.2.0}/pyproject.toml +6 -1
- {pydantic_json_patch-0.1.2 → pydantic_json_patch-0.2.0}/src/pydantic_json_patch/__init__.py +1 -3
- {pydantic_json_patch-0.1.2 → pydantic_json_patch-0.2.0}/src/pydantic_json_patch/models.py +18 -18
- {pydantic_json_patch-0.1.2 → 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
|
|
@@ -129,16 +129,58 @@ and list the models along with the other schemas:
|
|
|
129
129
|
|
|
130
130
|
[![Screenshot of Swagger UI schema list][swagger-schemas]][swagger-schemas]
|
|
131
131
|
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
This project uses [uv] for managing dependencies.
|
|
135
|
+
Having installed uv, you can set the project up for local development with:
|
|
136
|
+
|
|
137
|
+
```shell
|
|
138
|
+
uv sync
|
|
139
|
+
uv run pre-commit install
|
|
140
|
+
```
|
|
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
|
+
|
|
132
169
|
[ci-badge]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml/badge.svg
|
|
133
170
|
[ci-page]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml
|
|
134
171
|
[coverage-badge]: https://coveralls.io/repos/github/textbook/pydantic_json_patch/badge.svg?branch=main
|
|
135
172
|
[coverage-page]: https://coveralls.io/github/textbook/pydantic_json_patch?branch=main
|
|
136
173
|
[fastapi]: https://fastapi.tiangolo.com/
|
|
174
|
+
[isort]: https://pycqa.github.io/isort/
|
|
137
175
|
[json patch]: https://datatracker.ietf.org/doc/html/rfc6902/
|
|
138
176
|
[json pointer]: https://datatracker.ietf.org/doc/html/rfc6901/
|
|
139
177
|
[pydantic]: https://docs.pydantic.dev/latest/
|
|
140
178
|
[pypi]: https://pypi.org/
|
|
141
179
|
[pypi-badge]: https://img.shields.io/pypi/v/pydantic-json-patch?logo=python&logoColor=white&label=PyPI
|
|
142
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/
|
|
143
183
|
[swagger-example]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-example.png?raw=true
|
|
144
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/
|
|
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
|
|
@@ -97,16 +97,58 @@ and list the models along with the other schemas:
|
|
|
97
97
|
|
|
98
98
|
[![Screenshot of Swagger UI schema list][swagger-schemas]][swagger-schemas]
|
|
99
99
|
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
This project uses [uv] for managing dependencies.
|
|
103
|
+
Having installed uv, you can set the project up for local development with:
|
|
104
|
+
|
|
105
|
+
```shell
|
|
106
|
+
uv sync
|
|
107
|
+
uv run pre-commit install
|
|
108
|
+
```
|
|
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
|
+
|
|
100
137
|
[ci-badge]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml/badge.svg
|
|
101
138
|
[ci-page]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml
|
|
102
139
|
[coverage-badge]: https://coveralls.io/repos/github/textbook/pydantic_json_patch/badge.svg?branch=main
|
|
103
140
|
[coverage-page]: https://coveralls.io/github/textbook/pydantic_json_patch?branch=main
|
|
104
141
|
[fastapi]: https://fastapi.tiangolo.com/
|
|
142
|
+
[isort]: https://pycqa.github.io/isort/
|
|
105
143
|
[json patch]: https://datatracker.ietf.org/doc/html/rfc6902/
|
|
106
144
|
[json pointer]: https://datatracker.ietf.org/doc/html/rfc6901/
|
|
107
145
|
[pydantic]: https://docs.pydantic.dev/latest/
|
|
108
146
|
[pypi]: https://pypi.org/
|
|
109
147
|
[pypi-badge]: https://img.shields.io/pypi/v/pydantic-json-patch?logo=python&logoColor=white&label=PyPI
|
|
110
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/
|
|
111
151
|
[swagger-example]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-example.png?raw=true
|
|
112
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/
|
|
154
|
+
[uv]: https://docs.astral.sh/uv/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pydantic_json_patch"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Pydantic models for implementing JSON Patch."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -47,6 +47,8 @@ dev = [
|
|
|
47
47
|
"fastapi>=0.128.2",
|
|
48
48
|
"fastapi-cli>=0.0.20",
|
|
49
49
|
"httpx>=0.28.1",
|
|
50
|
+
"isort>=7.0.0",
|
|
51
|
+
"pre-commit>=4.5.1",
|
|
50
52
|
"pytest>=9.0.2",
|
|
51
53
|
"ruff>=0.15.0",
|
|
52
54
|
"ty>=0.0.15",
|
|
@@ -58,5 +60,8 @@ omit = ["tests/**"]
|
|
|
58
60
|
[tool.coverage.lcov]
|
|
59
61
|
output = "coverage/lcov.info"
|
|
60
62
|
|
|
63
|
+
[tool.isort]
|
|
64
|
+
profile = "black"
|
|
65
|
+
|
|
61
66
|
[tool.pytest.ini_options]
|
|
62
67
|
python_classes = [] # avoid trying to run TestOp as a test
|
|
@@ -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,15 +1,13 @@
|
|
|
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
|
-
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, model_validator
|
|
6
6
|
import typing_extensions as tx
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, model_validator
|
|
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
|