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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic_json_patch
3
- Version: 0.1.2
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
@@ -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 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
@@ -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.1.2"
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, 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,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 | 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