pydantic-json-patch 0.2.0__tar.gz → 0.4.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.2.0
3
+ Version: 0.4.0
4
4
  Summary: Pydantic models for implementing JSON Patch.
5
5
  Author: Jonathan Sharpe
6
6
  Author-email: Jonathan Sharpe <mail@jonrshar.pe>
@@ -68,6 +68,15 @@ AddOp(op='add', path='/foo/bar', value=123)
68
68
  '{"op":"add","path":"/foo/bar","value":123}'
69
69
  ```
70
70
 
71
+ The operations that take a value (`AddOp`, `ReplaceOp`, and `TestOp`) are generic, so you can parameterize them with a specific value type:
72
+
73
+ ```python
74
+ >>> from pydantic_json_patch import ReplaceOp
75
+ >>> op = ReplaceOp[str].create(path="/foo/bar", value="hello")
76
+ >>> op
77
+ ReplaceOp[str](op='replace', path='/foo/bar', value='hello')
78
+ ```
79
+
71
80
  Additionally, there are two compound models:
72
81
 
73
82
  - `Operation` is the union of all the operators; and
@@ -36,6 +36,15 @@ AddOp(op='add', path='/foo/bar', value=123)
36
36
  '{"op":"add","path":"/foo/bar","value":123}'
37
37
  ```
38
38
 
39
+ The operations that take a value (`AddOp`, `ReplaceOp`, and `TestOp`) are generic, so you can parameterize them with a specific value type:
40
+
41
+ ```python
42
+ >>> from pydantic_json_patch import ReplaceOp
43
+ >>> op = ReplaceOp[str].create(path="/foo/bar", value="hello")
44
+ >>> op
45
+ ReplaceOp[str](op='replace', path='/foo/bar', value='hello')
46
+ ```
47
+
39
48
  Additionally, there are two compound models:
40
49
 
41
50
  - `Operation` is the union of all the operators; and
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pydantic_json_patch"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "Pydantic models for implementing JSON Patch."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,13 +1,14 @@
1
1
  import re
2
2
  import typing as tp
3
3
  from collections.abc import Sequence
4
- from functools import cached_property
4
+ from functools import cached_property, lru_cache
5
5
 
6
6
  import typing_extensions as tx
7
7
  from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, model_validator
8
8
 
9
9
  _JSON_POINTER = re.compile(r"^(?:/(?:[^/~]|~[01])+)*$")
10
10
 
11
+ T = tx.TypeVar("T", default=tp.Any)
11
12
 
12
13
  # region base models
13
14
 
@@ -21,8 +22,7 @@ class _BaseOp(BaseModel):
21
22
  @classmethod
22
23
  def create(cls, *, path: str | Sequence[str], **kwargs) -> tx.Self:
23
24
  (op,) = tp.get_args(cls.model_fields["op"].annotation)
24
- pointer = path if isinstance(path, str) else cls._dump_pointer(path)
25
- return cls(op=op, path=pointer, **kwargs)
25
+ return cls(op=op, path=cls._dump_pointer(path), **kwargs)
26
26
 
27
27
  op: tp.Literal["add", "copy", "move", "remove", "replace", "test"]
28
28
  """The operation being represented."""
@@ -35,25 +35,33 @@ class _BaseOp(BaseModel):
35
35
  """The decoded tokens in the 'path' JSON pointer."""
36
36
  return self._load_pointer(self.path)
37
37
 
38
+ @classmethod
39
+ def _dump_pointer(cls, pointer: Sequence[str]) -> str:
40
+ if isinstance(pointer, str):
41
+ return pointer
42
+ return "/".join(["", *(cls._encode_token(token) for token in pointer)])
43
+
38
44
  @staticmethod
39
- def _dump_pointer(pointer: Sequence[str]) -> str:
40
- return "/".join(
41
- ["", *(token.replace("~", "~0").replace("/", "~1") for token in pointer)]
42
- )
45
+ @lru_cache
46
+ def _decode_token(token: str) -> str:
47
+ return token.replace("~1", "/").replace("~0", "~")
43
48
 
44
49
  @staticmethod
45
- def _load_pointer(pointer: str) -> tuple[str, ...]:
46
- return tuple(
47
- token.replace("~1", "/").replace("~0", "~")
48
- for token in pointer.split("/")[1:]
49
- )
50
+ @lru_cache
51
+ def _encode_token(token: str) -> str:
52
+ return token.replace("~", "~0").replace("/", "~1")
53
+
54
+ @classmethod
55
+ @lru_cache
56
+ def _load_pointer(cls, pointer: str) -> tuple[str, ...]:
57
+ return tuple(cls._decode_token(token) for token in pointer.split("/")[1:])
50
58
 
51
59
 
52
60
  class _FromOp(_BaseOp):
53
61
  @classmethod
54
62
  def create(
55
63
  cls, *, path: str | Sequence[str], from_: str | Sequence[str]
56
- ) -> tx.Self: # type: ignore[invalid-method-override]
64
+ ) -> tx.Self: # ty: ignore[invalid-method-override] -- deliberately narrows **kwargs to named params
57
65
  pointer = from_ if isinstance(from_, str) else cls._dump_pointer(from_)
58
66
  return super().create(path=path, **{"from": pointer})
59
67
 
@@ -78,12 +86,12 @@ class _FromOp(_BaseOp):
78
86
  return self._load_pointer(self.from_)
79
87
 
80
88
 
81
- class _ValueOp(_BaseOp):
89
+ class _ValueOp(_BaseOp, tp.Generic[T]):
82
90
  @classmethod
83
- def create(cls, *, path: str | Sequence[str], value: tp.Any) -> tx.Self: # type: ignore[invalid-method-override]
91
+ def create(cls, *, path: str | Sequence[str], value: T) -> tx.Self: # ty: ignore[invalid-method-override] -- deliberately narrows **kwargs to named params
84
92
  return super().create(path=path, value=value)
85
93
 
86
- value: tp.Any = Field(examples=[42])
94
+ value: T = Field(examples=[42])
87
95
  """The value to use in the operation."""
88
96
 
89
97
 
@@ -92,7 +100,7 @@ class _ValueOp(_BaseOp):
92
100
  # region public models
93
101
 
94
102
 
95
- class AddOp(_ValueOp):
103
+ class AddOp(_ValueOp[T], tp.Generic[T]):
96
104
  op: tp.Literal["add"]
97
105
 
98
106
 
@@ -106,17 +114,17 @@ class MoveOp(_FromOp):
106
114
 
107
115
  class RemoveOp(_BaseOp):
108
116
  @classmethod
109
- def create(cls, *, path: str | Sequence[str]) -> tx.Self: # type: ignore[invalid-method-override]
117
+ def create(cls, *, path: str | Sequence[str]) -> tx.Self: # ty: ignore[invalid-method-override] -- deliberately narrows **kwargs to named params
110
118
  return super().create(path=path)
111
119
 
112
120
  op: tp.Literal["remove"]
113
121
 
114
122
 
115
- class ReplaceOp(_ValueOp):
123
+ class ReplaceOp(_ValueOp[T], tp.Generic[T]):
116
124
  op: tp.Literal["replace"]
117
125
 
118
126
 
119
- class TestOp(_ValueOp):
127
+ class TestOp(_ValueOp[T], tp.Generic[T]):
120
128
  op: tp.Literal["test"]
121
129
 
122
130