tiptap-python-utils 0.3.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.
Files changed (51) hide show
  1. {tiptap_python_utils-0.3.0/src/tiptap_python_utils.egg-info → tiptap_python_utils-0.4.0}/PKG-INFO +1 -1
  2. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/pyproject.toml +1 -1
  3. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/key.py +2 -0
  4. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/families.py +10 -1
  5. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/fingerprint.py +3 -0
  6. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0/src/tiptap_python_utils.egg-info}/PKG-INFO +1 -1
  7. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_mutations.py +130 -0
  8. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/LICENSE +0 -0
  9. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/MANIFEST.in +0 -0
  10. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/README.md +0 -0
  11. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/setup.cfg +0 -0
  12. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/__init__.py +0 -0
  13. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/__init__.py +0 -0
  14. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/raw.py +0 -0
  15. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/reader.py +0 -0
  16. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/writer.py +0 -0
  17. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/content.py +0 -0
  18. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/__init__.py +0 -0
  19. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/kind.py +0 -0
  20. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/policy.py +0 -0
  21. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/exceptions.py +0 -0
  22. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/__init__.py +0 -0
  23. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/base.py +0 -0
  24. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/nodes.py +0 -0
  25. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/payload.py +0 -0
  26. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/registry.py +0 -0
  27. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/py.typed +0 -0
  28. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/__init__.py +0 -0
  29. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/selection.py +0 -0
  30. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/__init__.py +0 -0
  31. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/identity.py +0 -0
  32. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/__init__.py +0 -0
  33. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/query.py +0 -0
  34. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/__init__.py +0 -0
  35. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/extract.py +0 -0
  36. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/__init__.py +0 -0
  37. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/path.py +0 -0
  38. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/types.py +0 -0
  39. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/__init__.py +0 -0
  40. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/traversal.py +0 -0
  41. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/SOURCES.txt +0 -0
  42. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/dependency_links.txt +0 -0
  43. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/requires.txt +0 -0
  44. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/top_level.txt +0 -0
  45. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_codec_raw.py +0 -0
  46. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_compat_imports.py +0 -0
  47. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_content.py +0 -0
  48. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_extract.py +0 -0
  49. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_filter.py +0 -0
  50. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_public_api.py +0 -0
  51. {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_traverser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tiptap_python_utils
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Python utilities for parsing, traversing, editing, and serializing TipTap JSON content.
5
5
  Author: tiptap_python_utils contributors
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tiptap_python_utils"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Python utilities for parsing, traversing, editing, and serializing TipTap JSON content."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -8,6 +8,8 @@ MARKS = "marks"
8
8
  ID = "id"
9
9
  TIPTAP_ID = "tiptapId"
10
10
  SHARED_ID = "sharedId"
11
+ SHARED = "shared"
12
+ PLACE = "place"
11
13
  LEVEL = "level"
12
14
  CHECKED = "checked"
13
15
  STATUS = "status"
@@ -52,13 +52,22 @@ class SharedFamilies:
52
52
  return len(self._bodies)
53
53
 
54
54
  def merge(self, target: Node) -> Node:
55
- """Return target rewritten from the canonical body, preserving local id and sharedId."""
55
+ """Return target rewritten from the canonical body.
56
+
57
+ Preserves the target's per-copy identity (local id, sharedId) and its
58
+ per-copy ``place`` discriminator. The family-identical ``shared`` core
59
+ rides along from the canonical body unchanged.
60
+ """
56
61
  canonical = self[target.shared_id]
57
62
  raw = canonical.raw()
58
63
  attrs = dict(raw.get(key.ATTRS, {}))
64
+ attrs.pop(key.PLACE, None)
59
65
  if target.id:
60
66
  attrs[key.ID] = target.id
61
67
  if target.shared_id:
62
68
  attrs[key.SHARED_ID] = target.shared_id
69
+ target_attrs = target.raw().get(key.ATTRS, {})
70
+ if key.PLACE in target_attrs:
71
+ attrs[key.PLACE] = target_attrs[key.PLACE]
63
72
  raw[key.ATTRS] = attrs
64
73
  return codec.read_node(raw)
@@ -13,6 +13,9 @@ def fingerprint(node: Node) -> str:
13
13
  attrs = dict(raw.get(key.ATTRS, {}))
14
14
  attrs.pop(key.ID, None)
15
15
  attrs.pop(key.SHARED_ID, None)
16
+ attrs.pop(key.SHARED, None)
17
+ attrs.pop(key.PLACE, None)
18
+ attrs.pop(key.TASK_CANONICAL_ID, None)
16
19
  if attrs:
17
20
  raw[key.ATTRS] = attrs
18
21
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tiptap_python_utils
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Python utilities for parsing, traversing, editing, and serializing TipTap JSON content.
5
5
  Author: tiptap_python_utils contributors
6
6
  License: MIT License
@@ -140,6 +140,136 @@ def test_sync_shared_no_op_when_no_matching_shared_id():
140
140
  assert rewritten.dump() == target.dump()
141
141
 
142
142
 
143
+ # ---------------------------------------------------------------------------
144
+ # Self-describing shared core: `shared` (family-identical) + `place` (per-copy)
145
+ # ---------------------------------------------------------------------------
146
+
147
+
148
+ def test_shared_families_ignores_shared_and_place_when_fingerprinting():
149
+ # Two members of one family, identical body text, but differing per-copy
150
+ # `place` and differing `shared` core. These keys are provenance, not body,
151
+ # so the fingerprint must ignore them and NOT flag a conflict.
152
+ content = Content.require(
153
+ json.dumps(
154
+ {
155
+ "type": "doc",
156
+ "content": [
157
+ {
158
+ "type": "paragraph",
159
+ "attrs": {
160
+ "id": "n1",
161
+ "sharedId": "shared-1",
162
+ "shared": {"id": "shared-1", "topics": ["a"]},
163
+ "place": {"context": "dated_note", "topicId": None},
164
+ },
165
+ "content": [{"type": "text", "text": "same body"}],
166
+ },
167
+ {
168
+ "type": "paragraph",
169
+ "attrs": {
170
+ "id": "n2",
171
+ "sharedId": "shared-1",
172
+ "shared": {"id": "shared-1", "topics": ["b"]},
173
+ "place": {"context": "undated_note", "topicId": "t-1"},
174
+ },
175
+ "content": [{"type": "text", "text": "same body"}],
176
+ },
177
+ ],
178
+ }
179
+ )
180
+ )
181
+
182
+ families = content.shared_families()
183
+
184
+ assert "shared-1" in families
185
+
186
+
187
+ def test_shared_families_still_rejects_real_body_divergence_despite_place():
188
+ # Differing `place` must not mask a genuine body difference: the text still
189
+ # differs, so this remains a conflict (guards against over-stripping).
190
+ content = Content.require(
191
+ json.dumps(
192
+ {
193
+ "type": "doc",
194
+ "content": [
195
+ {
196
+ "type": "paragraph",
197
+ "attrs": {
198
+ "id": "n1",
199
+ "sharedId": "shared-1",
200
+ "place": {"context": "dated_note", "topicId": None},
201
+ },
202
+ "content": [{"type": "text", "text": "one"}],
203
+ },
204
+ {
205
+ "type": "paragraph",
206
+ "attrs": {
207
+ "id": "n2",
208
+ "sharedId": "shared-1",
209
+ "place": {"context": "undated_note", "topicId": "t-1"},
210
+ },
211
+ "content": [{"type": "text", "text": "two"}],
212
+ },
213
+ ],
214
+ }
215
+ )
216
+ )
217
+
218
+ with pytest.raises(TiptapValidationError, match="Conflicting node bodies detected"):
219
+ content.shared_families()
220
+
221
+
222
+ def test_sync_shared_preserves_per_copy_place_and_carries_shared_core():
223
+ source = Content.require(
224
+ json.dumps(
225
+ {
226
+ "type": "doc",
227
+ "content": [
228
+ {
229
+ "type": "paragraph",
230
+ "attrs": {
231
+ "id": "source-id",
232
+ "sharedId": "shared-1",
233
+ "shared": {"id": "shared-1", "primaryTopic": "t-react"},
234
+ "place": {"context": "dated_note", "topicId": None},
235
+ },
236
+ "content": [{"type": "text", "text": "new"}],
237
+ }
238
+ ],
239
+ }
240
+ )
241
+ )
242
+ target = Content.require(
243
+ json.dumps(
244
+ {
245
+ "type": "doc",
246
+ "content": [
247
+ {
248
+ "type": "paragraph",
249
+ "attrs": {
250
+ "id": "target-id",
251
+ "sharedId": "shared-1",
252
+ "shared": {"id": "shared-1", "primaryTopic": "STALE"},
253
+ "place": {"context": "undated_note", "topicId": "t-react"},
254
+ },
255
+ "content": [{"type": "text", "text": "old"}],
256
+ }
257
+ ],
258
+ }
259
+ )
260
+ )
261
+
262
+ rewritten = target.sync_shared(source.shared_families())
263
+ node = json.loads(rewritten.dump())["content"][0]
264
+
265
+ # Body + family-identical `shared` core come from the canonical (source).
266
+ assert node["content"][0]["text"] == "new"
267
+ assert node["attrs"]["shared"]["primaryTopic"] == "t-react"
268
+ # Per-copy identity and `place` stay the target's own.
269
+ assert node["attrs"]["id"] == "target-id"
270
+ assert node["attrs"]["place"] == {"context": "undated_note", "topicId": "t-react"}
271
+
272
+
143
273
  # ---------------------------------------------------------------------------
144
274
  # Node.with_shared_id
145
275
  # ---------------------------------------------------------------------------