patchdiff 0.3.3__tar.gz → 0.3.5__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.
@@ -0,0 +1,103 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ tags:
8
+ - 'v*'
9
+ pull_request:
10
+ branches:
11
+ - master
12
+
13
+ jobs:
14
+ test:
15
+ name: Lint and test on ${{ matrix.name }}
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ include:
21
+ - name: Linux py38
22
+ pyversion: '3.8'
23
+ - name: Linux py39
24
+ pyversion: '3.9'
25
+ - name: Linux py310
26
+ pyversion: '3.10'
27
+ - name: Linux py311
28
+ pyversion: '3.11'
29
+ - name: Linux py312
30
+ pyversion: '3.12'
31
+ - name: Linux py313
32
+ pyversion: '3.13'
33
+ - name: Linux py314
34
+ pyversion: '3.14'
35
+ steps:
36
+ - uses: actions/checkout@v5
37
+ - name: Install uv
38
+ uses: astral-sh/setup-uv@v6
39
+ - name: Set up Python ${{ matrix.pyversion }}
40
+ uses: actions/setup-python@v5
41
+ with:
42
+ python-version: ${{ matrix.pyversion }}
43
+ - name: Install dependencies
44
+ run: uv sync
45
+ - name: Lint
46
+ run: uv run ruff check
47
+ - name: Format
48
+ run: uv run ruff format --check
49
+ - name: Test
50
+ run: uv run pytest -v --cov=patchdiff --cov-report=term-missing
51
+
52
+ build:
53
+ name: Build and test wheel
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v5
57
+ - name: Install uv
58
+ uses: astral-sh/setup-uv@v6
59
+ - name: Set up Python 3.9
60
+ uses: actions/setup-python@v5
61
+ with:
62
+ python-version: '3.9'
63
+ - name: Install dependencies
64
+ run: uv sync
65
+ - name: Build wheel
66
+ run: uv build
67
+ - name: Twine check
68
+ run: uvx twine check dist/*
69
+ - name: Upload wheel artifact
70
+ uses: actions/upload-artifact@v4
71
+ with:
72
+ path: dist
73
+ name: dist
74
+
75
+ publish:
76
+ name: Publish to Github and Pypi
77
+ runs-on: ubuntu-latest
78
+ needs: [test, build]
79
+ if: success() && startsWith(github.ref, 'refs/tags/v')
80
+ environment:
81
+ name: pypi
82
+ url: https://pypi.org/p/patchdiff
83
+ permissions:
84
+ id-token: write
85
+ contents: write
86
+ steps:
87
+ - uses: actions/checkout@v5
88
+ - name: Download wheel artifact
89
+ uses: actions/download-artifact@v4
90
+ with:
91
+ name: dist
92
+ path: dist
93
+ - name: Release
94
+ uses: softprops/action-gh-release@v2
95
+ with:
96
+ token: ${{ secrets.GITHUB_TOKEN }}
97
+ files: |
98
+ dist/*.tar.gz
99
+ dist/*.whl
100
+ draft: true
101
+ prerelease: false
102
+ - name: Publish to PyPI
103
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,6 @@
1
+ *.egg-info/
2
+ __pycache__
3
+ .vscode
4
+ .coverage
5
+ dist
6
+ uv.lock
@@ -1,20 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: patchdiff
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: MIT
5
- Home-page: https://github.com/Korijn/patchdiff
6
- Author: Korijn van Golen
7
- Author-email: korijn@gmail.com
8
- Requires-Python: >=3.7
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.7
12
- Classifier: Programming Language :: Python :: 3.8
13
- Classifier: Programming Language :: Python :: 3.9
5
+ Project-URL: Homepage, https://github.com/fork-tongue/patchdiff
6
+ Author-email: Korijn van Golen <korijn@gmail.com>, Berend Klein Haneveld <berendkleinhaneveld@gmail.com>
7
+ Requires-Python: >=3.8
14
8
  Description-Content-Type: text/markdown
15
9
 
16
10
  [![PyPI version](https://badge.fury.io/py/patchdiff.svg)](https://badge.fury.io/py/patchdiff)
17
- [![CI status](https://github.com/Korijn/patchdiff/workflows/CI/badge.svg)](https://github.com/Korijn/patchdiff/actions)
11
+ [![CI status](https://github.com/fork-tongue/patchdiff/workflows/CI/badge.svg)](https://github.com/fork-tongue/patchdiff/actions)
18
12
 
19
13
  # Patchdiff 🔍
20
14
 
@@ -58,4 +52,3 @@ print(to_json(ops, indent=4))
58
52
  # }
59
53
  # ]
60
54
  ```
61
-
@@ -1,5 +1,5 @@
1
1
  [![PyPI version](https://badge.fury.io/py/patchdiff.svg)](https://badge.fury.io/py/patchdiff)
2
- [![CI status](https://github.com/Korijn/patchdiff/workflows/CI/badge.svg)](https://github.com/Korijn/patchdiff/actions)
2
+ [![CI status](https://github.com/fork-tongue/patchdiff/workflows/CI/badge.svg)](https://github.com/fork-tongue/patchdiff/actions)
3
3
 
4
4
  # Patchdiff 🔍
5
5
 
@@ -2,4 +2,4 @@ from .apply import apply, iapply
2
2
  from .diff import diff
3
3
  from .serialize import to_json
4
4
 
5
- __version__ = "0.3.3"
5
+ __version__ = "0.3.4"
@@ -71,13 +71,13 @@ def diff_lists(input: List, output: List, ptr: Pointer) -> Tuple[List, List]:
71
71
  "path": ptr.append(idx_token),
72
72
  "value": op["value"],
73
73
  }
74
- return [ops + [full_op], padding + 1]
74
+ return [[*ops, full_op], padding + 1]
75
75
  elif op["op"] == "remove":
76
76
  full_op = {
77
77
  "op": "remove",
78
78
  "path": ptr.append(op["idx"] + padding),
79
79
  }
80
- return [ops + [full_op], padding - 1]
80
+ return [[*ops, full_op], padding - 1]
81
81
  else:
82
82
  replace_ptr = ptr.append(op["idx"] + padding)
83
83
  replace_ops, _ = diff(op["original"], op["value"], replace_ptr)
@@ -96,7 +96,7 @@ def diff_dicts(input: Dict, output: Dict, ptr: Pointer) -> Tuple[List, List]:
96
96
  output_keys = set(output.keys())
97
97
  for key in input_keys - output_keys:
98
98
  ops.append({"op": "remove", "path": ptr.append(key)})
99
- rops.insert(0, {"op": "add", "path": ptr.append(key), "value": output[key]})
99
+ rops.insert(0, {"op": "add", "path": ptr.append(key), "value": input[key]})
100
100
  for key in output_keys - input_keys:
101
101
  ops.append(
102
102
  {
@@ -1,9 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
  from typing import Any, Hashable, List, Tuple
3
5
 
4
6
  from .types import Diffable
5
7
 
6
-
7
8
  tilde0_re = re.compile("~0")
8
9
  tilde1_re = re.compile("~1")
9
10
  tilde_re = re.compile("~")
@@ -19,21 +20,21 @@ def escape(token: str) -> str:
19
20
 
20
21
 
21
22
  class Pointer:
22
- def __init__(self, tokens: List[Hashable] = None) -> None:
23
+ def __init__(self, tokens: List[Hashable] | None = None) -> None:
23
24
  if tokens is None:
24
25
  tokens = []
25
- self.tokens = tokens
26
+ self.tokens = tuple(tokens)
26
27
 
27
28
  @staticmethod
28
29
  def from_str(path: str) -> "Pointer":
29
- tokens = [unescape(t) for t in path.split("/")]
30
+ tokens = [unescape(t) for t in path.split("/")[1:]]
30
31
  return Pointer(tokens)
31
32
 
32
33
  def __str__(self) -> str:
33
34
  return "/" + "/".join(escape(str(t)) for t in self.tokens)
34
35
 
35
36
  def __repr__(self) -> str:
36
- return f"Pointer({repr(self.tokens)})"
37
+ return f"Pointer({list(self.tokens)!r})"
37
38
 
38
39
  def __hash__(self) -> int:
39
40
  return hash(self.tokens)
@@ -62,4 +63,4 @@ class Pointer:
62
63
 
63
64
  def append(self, token: Hashable) -> "Pointer":
64
65
  """append, creating new Pointer"""
65
- return Pointer(self.tokens + [token])
66
+ return Pointer((*self.tokens, token))
@@ -0,0 +1,3 @@
1
+ from typing import Dict, List, Set, Union
2
+
3
+ Diffable = Union[Dict, List, Set]
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "patchdiff"
3
+ version = "0.3.5"
4
+ description = "MIT"
5
+ authors = [
6
+ { name = "Korijn van Golen", email = "korijn@gmail.com" },
7
+ { name = "Berend Klein Haneveld", email = "berendkleinhaneveld@gmail.com" },
8
+ ]
9
+ requires-python = ">=3.8"
10
+ readme = "README.md"
11
+
12
+ [project.urls]
13
+ Homepage = "https://github.com/fork-tongue/patchdiff"
14
+
15
+ [dependency-groups]
16
+ dev = [
17
+ "ruff",
18
+ "pytest",
19
+ "pytest-cov",
20
+ "pytest-watch",
21
+ ]
22
+
23
+ [tool.ruff.lint]
24
+ extend-select = [
25
+ "F", # Pyflakes (default)
26
+ "I", # isort imports
27
+ "N", # pep8-naming
28
+ "T10", # flake8-debugger
29
+ "T20", # flake8-print
30
+ "RUF", # ruff
31
+ ]
32
+
33
+ [tool.ruff.lint.per-file-ignores]
34
+ "patchdiff/__init__.py" = ["F401"]
35
+
36
+ [build-system]
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
@@ -0,0 +1,111 @@
1
+ from patchdiff import apply, diff
2
+
3
+
4
+ def test_apply():
5
+ a = {
6
+ "a": [5, 7, 9, {"a", "b", "c"}],
7
+ "b": 6,
8
+ }
9
+ b = {
10
+ "a": [5, 2, 9, {"b", "c"}],
11
+ "b": 6,
12
+ "c": 7,
13
+ }
14
+
15
+ ops, rops = diff(a, b)
16
+
17
+ c = apply(a, ops)
18
+ assert c == b
19
+
20
+ d = apply(b, rops)
21
+ assert a == d
22
+
23
+
24
+ def test_apply_list():
25
+ a = [1, 5, 9, "sdfsdf", "fff"]
26
+ b = ["sdf", 5, 9, "c"]
27
+
28
+ ops, rops = diff(a, b)
29
+
30
+ c = apply(a, ops)
31
+ assert c == b
32
+
33
+ d = apply(b, rops)
34
+ assert a == d
35
+
36
+
37
+ def test_add_remove_list():
38
+ a = []
39
+ b = [1]
40
+
41
+ ops, rops = diff(a, b)
42
+
43
+ c = apply(a, ops)
44
+ assert c == b
45
+
46
+ d = apply(b, rops)
47
+ assert a == d
48
+
49
+
50
+ def test_add_remove_list_extended():
51
+ a = []
52
+ b = [1, 2, 3]
53
+
54
+ ops, rops = diff(a, b)
55
+
56
+ c = apply(a, ops)
57
+ assert c == b
58
+
59
+ d = apply(b, rops)
60
+ assert a == d
61
+
62
+
63
+ def test_insertion_in_list_front():
64
+ a = [1, 2]
65
+ b = [3, 1, 2]
66
+ ops, rops = diff(a, b)
67
+
68
+ c = apply(a, ops)
69
+ assert c == b
70
+
71
+ d = apply(b, rops)
72
+ assert a == d
73
+
74
+
75
+ def test_add_remove_list_extended_inverse():
76
+ a = [1, 2, 3]
77
+ b = []
78
+
79
+ ops, rops = diff(a, b)
80
+
81
+ c = apply(a, ops)
82
+ assert c == b
83
+
84
+ d = apply(b, rops)
85
+ assert a == d
86
+
87
+
88
+ def test_add_remove_list_extended_inverse_leaving_start():
89
+ a = [1, 2, 3, 4]
90
+ b = [1]
91
+
92
+ ops, rops = diff(a, b)
93
+
94
+ c = apply(a, ops)
95
+ assert c == b
96
+
97
+ d = apply(b, rops)
98
+ assert a == d
99
+
100
+
101
+ def test_add_remove_list_extended_inverse_leaving_end():
102
+ a = [1, 2, 3, 4]
103
+ b = [4]
104
+
105
+ ops, rops = diff(a, b)
106
+
107
+ c = apply(a, ops)
108
+ assert c == b
109
+
110
+ d = apply(b, rops)
111
+ assert a == d
@@ -0,0 +1,170 @@
1
+ from patchdiff import diff
2
+ from patchdiff.pointer import Pointer
3
+
4
+
5
+ def test_basic_list_insertion():
6
+ a = []
7
+ b = [1]
8
+ ops, rops = diff(a, b)
9
+
10
+ assert ops == [{"op": "add", "path": Pointer(["-"]), "value": 1}]
11
+ assert rops == [{"op": "remove", "path": Pointer([0])}]
12
+
13
+
14
+ def test_basic_list_deletion():
15
+ a = [1]
16
+ b = []
17
+ ops, rops = diff(a, b)
18
+
19
+ assert ops == [{"op": "remove", "path": Pointer([0])}]
20
+ assert rops == [{"op": "add", "path": Pointer(["-"]), "value": 1}]
21
+
22
+
23
+ def test_basic_list_insertion_half_way():
24
+ a = [1, 3]
25
+ b = [1, 2, 3]
26
+ ops, rops = diff(a, b)
27
+
28
+ assert ops == [{"op": "add", "path": Pointer([1]), "value": 2}]
29
+ assert rops == [{"op": "remove", "path": Pointer([1])}]
30
+
31
+
32
+ def test_basic_list_deletion_half_way():
33
+ a = [1, 2, 3]
34
+ b = [1, 3]
35
+ ops, rops = diff(a, b)
36
+
37
+ assert ops == [{"op": "remove", "path": Pointer([1])}]
38
+ assert rops == [{"op": "add", "path": Pointer([1]), "value": 2}]
39
+
40
+
41
+ def test_basic_list_multiple_insertion():
42
+ a = []
43
+ b = [1, 2, 3]
44
+ ops, rops = diff(a, b)
45
+
46
+ assert ops == [
47
+ {"op": "add", "path": Pointer(["-"]), "value": 1},
48
+ {"op": "add", "path": Pointer(["-"]), "value": 2},
49
+ {"op": "add", "path": Pointer(["-"]), "value": 3},
50
+ ]
51
+ assert rops == [
52
+ {"op": "remove", "path": Pointer([0])},
53
+ {"op": "remove", "path": Pointer([0])},
54
+ {"op": "remove", "path": Pointer([0])},
55
+ ]
56
+
57
+
58
+ def test_basic_list_multiple_deletion():
59
+ a = [1, 2, 3]
60
+ b = []
61
+ ops, rops = diff(a, b)
62
+
63
+ assert ops == [
64
+ {"op": "remove", "path": Pointer([0])},
65
+ {"op": "remove", "path": Pointer([0])},
66
+ {"op": "remove", "path": Pointer([0])},
67
+ ]
68
+ assert rops == [
69
+ {"op": "add", "path": Pointer(["-"]), "value": 1},
70
+ {"op": "add", "path": Pointer(["-"]), "value": 2},
71
+ {"op": "add", "path": Pointer(["-"]), "value": 3},
72
+ ]
73
+
74
+
75
+ def test_list():
76
+ a = [1, 5, 9, "sdfsdf", "fff"]
77
+ b = ["sdf", 5, 9, "c"]
78
+ ops, rops = diff(a, b)
79
+
80
+ assert ops == [
81
+ {"op": "replace", "path": Pointer([0]), "value": "sdf"},
82
+ {"op": "replace", "path": Pointer([3]), "value": "c"},
83
+ {"op": "remove", "path": Pointer([4])},
84
+ ]
85
+ assert rops == [
86
+ {"op": "replace", "path": Pointer([0]), "value": 1},
87
+ {"op": "replace", "path": Pointer([3]), "value": "sdfsdf"},
88
+ {"op": "add", "path": Pointer(["-"]), "value": "fff"},
89
+ ]
90
+
91
+
92
+ def test_list_begin():
93
+ a = [1, 2]
94
+ b = [3, 1, 2]
95
+ ops, rops = diff(a, b)
96
+
97
+ assert ops == [{"op": "add", "path": Pointer([0]), "value": 3}]
98
+ assert rops == [{"op": "remove", "path": Pointer([0])}]
99
+
100
+
101
+ def test_list_end():
102
+ a = [1, 2, 3]
103
+ b = [1, 2, 3, 4]
104
+ ops, rops = diff(a, b)
105
+
106
+ assert ops == [{"op": "add", "path": Pointer(["-"]), "value": 4}]
107
+ assert rops == [{"op": "remove", "path": Pointer([3])}]
108
+
109
+
110
+ def test_dicts():
111
+ a = {"a": 5, "b": 6}
112
+ b = {"a": 3, "b": 6, "c": 7}
113
+ ops, rops = diff(a, b)
114
+
115
+ assert ops == [
116
+ {"op": "add", "path": Pointer(["c"]), "value": 7},
117
+ {"op": "replace", "path": Pointer(["a"]), "value": 3},
118
+ ]
119
+ assert rops == [
120
+ {"op": "replace", "path": Pointer(["a"]), "value": 5},
121
+ {"op": "remove", "path": Pointer(["c"])},
122
+ ]
123
+
124
+
125
+ def test_dicts_remove_item():
126
+ a = {"a": 3, "b": 6}
127
+ b = {"a": 3}
128
+ ops, rops = diff(a, b)
129
+
130
+ assert ops == [{"op": "remove", "path": Pointer(["b"])}]
131
+ assert rops == [{"op": "add", "path": Pointer(["b"]), "value": 6}]
132
+
133
+
134
+ def test_sets():
135
+ a = {"a", "b"}
136
+ b = {"a", "c"}
137
+ ops, rops = diff(a, b)
138
+
139
+ assert ops == [
140
+ {"op": "remove", "path": Pointer(["b"])},
141
+ {"op": "add", "path": Pointer(["-"]), "value": "c"},
142
+ ]
143
+ assert rops == [
144
+ {"op": "remove", "path": Pointer(["c"])},
145
+ {"op": "add", "path": Pointer(["-"]), "value": "b"},
146
+ ]
147
+
148
+
149
+ def test_mixed():
150
+ a = {
151
+ "a": [5, 7, 9, {"a", "b", "c"}],
152
+ "b": 6,
153
+ }
154
+ b = {
155
+ "a": [5, 2, 9, {"b", "c"}],
156
+ "b": 6,
157
+ "c": 7,
158
+ }
159
+ ops, rops = diff(a, b)
160
+
161
+ assert ops == [
162
+ {"op": "add", "path": Pointer(["c"]), "value": 7},
163
+ {"op": "replace", "path": Pointer(["a", 1]), "value": 2},
164
+ {"op": "remove", "path": Pointer(["a", 3, "a"])},
165
+ ]
166
+ assert rops == [
167
+ {"op": "replace", "path": Pointer(["a", 1]), "value": 7},
168
+ {"op": "add", "path": Pointer(["a", 3, "-"]), "value": "a"},
169
+ {"op": "remove", "path": Pointer(["c"])},
170
+ ]
@@ -0,0 +1,46 @@
1
+ from patchdiff.pointer import Pointer
2
+
3
+
4
+ def test_pointer_get():
5
+ obj = [1, 5, {"foo": 1, "bar": [1, 2, 3]}, "sdfsdf", "fff"]
6
+ assert Pointer([1]).evaluate(obj)[2] == 5
7
+ assert Pointer([2, "bar", 1]).evaluate(obj)[2] == 2
8
+
9
+
10
+ def test_pointer_str():
11
+ assert str(Pointer([1])) == "/1"
12
+ assert str(Pointer(["foo", "bar", "-"])) == "/foo/bar/-"
13
+
14
+
15
+ def test_pointer_repr():
16
+ assert repr(Pointer([1])) == "Pointer([1])"
17
+ assert repr(Pointer(["foo", "bar", "-"])) == "Pointer(['foo', 'bar', '-'])"
18
+
19
+
20
+ def test_pointer_from_str():
21
+ assert Pointer.from_str("/1") == Pointer(["1"])
22
+ assert Pointer.from_str("/foo/bar/-") == Pointer(["foo", "bar", "-"])
23
+
24
+
25
+ def test_pointer_hash():
26
+ assert hash(Pointer([1])) == hash((1,))
27
+ assert hash(Pointer(["foo", "bar", "-"])) == hash(("foo", "bar", "-"))
28
+
29
+
30
+ def test_pointer_set():
31
+ # hash supports comparison operators for use as keys and set elements
32
+ # so we exercise that as well
33
+ unique_pointers = [Pointer([1]), Pointer(["2", "3"])]
34
+ duplicated_pointers = unique_pointers + unique_pointers
35
+ assert len(set(duplicated_pointers)) == len(unique_pointers)
36
+
37
+
38
+ def test_pointer_eq():
39
+ assert Pointer([1]) != [1]
40
+ assert Pointer([1]) != Pointer(["1"])
41
+ assert Pointer([1]) != Pointer([0])
42
+ assert Pointer([1]) == Pointer([1])
43
+
44
+
45
+ def test_pointer_append():
46
+ assert Pointer([1]).append("foo") == Pointer([1, "foo"])
@@ -0,0 +1,86 @@
1
+ """
2
+ Tests that check that patchdiff apply and diff work
3
+ on proxied objects.
4
+ """
5
+
6
+ from collections import UserDict, UserList
7
+ from collections.abc import Set
8
+
9
+ from patchdiff import apply, diff
10
+
11
+
12
+ class SetProxy(Set):
13
+ """
14
+ Custom proxy class that works like UserDict and UserList by
15
+ storing the original data under the 'data' attribute.
16
+ """
17
+
18
+ def __init__(self, *args, **kwargs):
19
+ self.data = set(*args, **kwargs)
20
+
21
+ def __contains__(self, *args, **kwargs):
22
+ return self.data.__contains__(*args, **kwargs)
23
+
24
+ def __iter__(self, *args, **kwargs):
25
+ return self.data.__iter__(*args, **kwargs)
26
+
27
+ def __len__(self, *args, **kwargs):
28
+ return self.data.__len__(*args, **kwargs)
29
+
30
+ def __getattribute__(self, attr):
31
+ # Redirect __class__ to super() to make sure
32
+ # isinstance(obj, set) will fail
33
+ if attr in {"data", "_from_iterable", "__class__"}:
34
+ return super().__getattribute__(attr)
35
+ return self.data.__getattribute__(attr)
36
+
37
+
38
+ def test_proxy_dict():
39
+ data = {"foo": "bar"}
40
+ obj = UserDict(data)
41
+ assert not isinstance(obj, dict)
42
+
43
+ old = obj.copy()
44
+ obj["foo"] = "baz"
45
+
46
+ assert old["foo"] == "bar"
47
+ assert obj["foo"] == "baz"
48
+
49
+ ops, reverse_ops = diff(old, obj)
50
+
51
+ assert apply(old, ops) == obj
52
+ assert apply(obj, reverse_ops) == old
53
+
54
+
55
+ def test_proxy_list():
56
+ data = [1, 2]
57
+ obj = UserList(data)
58
+ assert not isinstance(obj, list)
59
+
60
+ old = obj.copy()
61
+ obj[1] = 3
62
+
63
+ assert old[1] == 2
64
+ assert obj[1] == 3
65
+
66
+ ops, reverse_ops = diff(old, obj)
67
+
68
+ assert apply(old, ops) == obj
69
+ assert apply(obj, reverse_ops) == old
70
+
71
+
72
+ def test_proxy_set():
73
+ data = {"a", "b"}
74
+ obj = SetProxy(data)
75
+ assert not isinstance(obj, set)
76
+
77
+ old = obj.copy()
78
+ obj.add("c")
79
+
80
+ assert "c" not in old
81
+ assert "c" in obj
82
+
83
+ ops, reverse_ops = diff(old, obj)
84
+
85
+ assert apply(old, ops) == obj
86
+ assert apply(obj, reverse_ops) == old
@@ -0,0 +1,31 @@
1
+ from patchdiff import diff, to_json
2
+
3
+
4
+ def test_to_json():
5
+ a = {
6
+ "a": [5, 7, 9, {"a", "b", "c"}],
7
+ "b": 6,
8
+ }
9
+ b = {"a": [5, 2, 9, {"b", "c"}], "b": 6, "c": 7}
10
+
11
+ ops, _ = diff(a, b)
12
+
13
+ assert (
14
+ to_json(ops, indent=4)
15
+ == """[
16
+ {
17
+ "op": "add",
18
+ "path": "/c",
19
+ "value": 7
20
+ },
21
+ {
22
+ "op": "replace",
23
+ "path": "/a/1",
24
+ "value": 2
25
+ },
26
+ {
27
+ "op": "remove",
28
+ "path": "/a/3/a"
29
+ }
30
+ ]"""
31
+ )
@@ -1,3 +0,0 @@
1
- from typing import Dict, List, Set, Tuple, Union
2
-
3
- Diffable = Union[Dict, List, Set, Tuple]
@@ -1,23 +0,0 @@
1
- [tool.poetry]
2
- name = "patchdiff"
3
- version = "0.3.3"
4
- description = "MIT"
5
- authors = ["Korijn van Golen <korijn@gmail.com>"]
6
- homepage = "https://github.com/Korijn/patchdiff"
7
- readme = "README.md"
8
-
9
- [tool.poetry.dependencies]
10
- python = ">=3.7"
11
-
12
- [tool.poetry.dev-dependencies]
13
- flake8 = "*"
14
- black = "*"
15
- flake8-black = "*"
16
- flake8-import-order = "*"
17
- flake8-print = "*"
18
- pytest = "*"
19
- twine = "*"
20
-
21
- [build-system]
22
- requires = ["poetry-core>=1.0.0"]
23
- build-backend = "poetry.core.masonry.api"
patchdiff-0.3.3/setup.py DELETED
@@ -1,26 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['patchdiff']
6
-
7
- package_data = \
8
- {'': ['*']}
9
-
10
- setup_kwargs = {
11
- 'name': 'patchdiff',
12
- 'version': '0.3.3',
13
- 'description': 'MIT',
14
- 'long_description': '[![PyPI version](https://badge.fury.io/py/patchdiff.svg)](https://badge.fury.io/py/patchdiff)\n[![CI status](https://github.com/Korijn/patchdiff/workflows/CI/badge.svg)](https://github.com/Korijn/patchdiff/actions)\n\n# Patchdiff 🔍\n\nBased on [rfc6902](https://github.com/chbrown/rfc6902) this library provides a simple API to generate bi-directional diffs between composite python datastructures composed out of lists, sets, tuples and dicts. The diffs are jsonpatch compliant, and can optionally be serialized to json format. Patchdiff can also be used to apply lists of patches to objects, both in-place or on a deepcopy of the input.\n\n## Install\n\n`pip install patchdiff`\n\n## Quick-start\n\n```python\nfrom patchdiff import apply, diff, iapply, to_json\n\ninput = {"a": [5, 7, 9, {"a", "b", "c"}], "b": 6}\noutput = {"a": [5, 2, 9, {"b", "c"}], "b": 6, "c": 7}\n\nops, reverse_ops = diff(input, output)\n\nassert apply(input, ops) == output\nassert apply(output, reverse_ops) == input\n\niapply(input, ops) # apply in-place\nassert input == output\n\nprint(to_json(ops, indent=4))\n# [\n# {\n# "op": "add",\n# "path": "/c",\n# "value": 7\n# },\n# {\n# "op": "replace",\n# "path": "/a/1",\n# "value": 2\n# },\n# {\n# "op": "remove",\n# "path": "/a/3/a"\n# }\n# ]\n```\n',
15
- 'author': 'Korijn van Golen',
16
- 'author_email': 'korijn@gmail.com',
17
- 'maintainer': None,
18
- 'maintainer_email': None,
19
- 'url': 'https://github.com/Korijn/patchdiff',
20
- 'packages': packages,
21
- 'package_data': package_data,
22
- 'python_requires': '>=3.7',
23
- }
24
-
25
-
26
- setup(**setup_kwargs)
File without changes