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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic_json_patch
3
- Version: 0.1.3
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 tuples of tokens, and will encode them appropriately:
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 tuples of tokens, and will encode them appropriately:
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pydantic_json_patch"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "Pydantic models for implementing JSON Patch."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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, Op, RemoveOp, ReplaceOp, TestOp, Tokens
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 | Tokens, **kwargs) -> tx.Self:
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: 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) -> Tokens:
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: Tokens) -> str:
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) -> Tokens:
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(cls, *, path: str | Tokens, from_: str | Tokens) -> tx.Self: # type: ignore[invalid-method-override]
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) -> Tokens:
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, tp.Generic[T]):
81
+ class _ValueOp(_BaseOp):
82
82
  @classmethod
83
- def create(cls, *, path: str | Tokens, value: T) -> tx.Self: # type: ignore[invalid-method-override]
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: T = Field(examples=[42])
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, tp.Generic[T]):
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 | Tokens) -> tx.Self: # type: ignore[invalid-method-override]
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, tp.Generic[T]):
115
+ class ReplaceOp(_ValueOp):
116
116
  op: tp.Literal["replace"]
117
117
 
118
118
 
119
- class TestOp(_ValueOp, tp.Generic[T]):
119
+ class TestOp(_ValueOp):
120
120
  op: tp.Literal["test"]
121
121
 
122
122