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.
- {tiptap_python_utils-0.3.0/src/tiptap_python_utils.egg-info → tiptap_python_utils-0.4.0}/PKG-INFO +1 -1
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/pyproject.toml +1 -1
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/key.py +2 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/families.py +10 -1
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/fingerprint.py +3 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0/src/tiptap_python_utils.egg-info}/PKG-INFO +1 -1
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_mutations.py +130 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/LICENSE +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/MANIFEST.in +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/README.md +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/setup.cfg +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/raw.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/reader.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/writer.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/content.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/kind.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/policy.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/exceptions.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/base.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/nodes.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/payload.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/registry.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/py.typed +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/selection.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/identity.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/query.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/extract.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/path.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/types.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/__init__.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/traversal.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/SOURCES.txt +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/dependency_links.txt +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/requires.txt +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/top_level.txt +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_codec_raw.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_compat_imports.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_content.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_extract.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_filter.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_public_api.py +0 -0
- {tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/tests/test_traverser.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tiptap_python_utils"
|
|
7
|
-
version = "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"
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/families.py
RENAMED
|
@@ -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
|
|
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:
|
|
@@ -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
|
# ---------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/raw.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/reader.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/codec/writer.py
RENAMED
|
File without changes
|
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/kind.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/contract/policy.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/exceptions.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/base.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/nodes.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/payload.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/model/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/select/selection.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/shared/identity.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tasks/query.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/text/extract.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/tree/path.py
RENAMED
|
File without changes
|
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/__init__.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils/walk/traversal.py
RENAMED
|
File without changes
|
{tiptap_python_utils-0.3.0 → tiptap_python_utils-0.4.0}/src/tiptap_python_utils.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|