tiptap-python-utils 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
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/__init__.py +3 -15
- tiptap_python_utils/content.py +31 -0
- tiptap_python_utils/contract/key.py +2 -0
- tiptap_python_utils/model/base.py +3 -0
- tiptap_python_utils/model/nodes.py +0 -4
- tiptap_python_utils/select/selection.py +4 -1
- tiptap_python_utils/shared/__init__.py +5 -11
- tiptap_python_utils/shared/families.py +67 -41
- tiptap_python_utils/shared/fingerprint.py +11 -9
- tiptap_python_utils/shared/identity.py +1 -40
- {tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/METADATA +22 -14
- {tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/RECORD +15 -16
- tiptap_python_utils/shared/sync.py +0 -63
- {tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/WHEEL +0 -0
- {tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/top_level.txt +0 -0
tiptap_python_utils/__init__.py
CHANGED
|
@@ -22,15 +22,7 @@ from .model import (
|
|
|
22
22
|
)
|
|
23
23
|
from .contract.policy import content_id, is_parseable, node_id, tiptap_id
|
|
24
24
|
from .select import Selection
|
|
25
|
-
from .shared import
|
|
26
|
-
fingerprint_shared,
|
|
27
|
-
has_shared,
|
|
28
|
-
new_shared_id,
|
|
29
|
-
shared_id,
|
|
30
|
-
shared_families,
|
|
31
|
-
stamp_shared,
|
|
32
|
-
sync_shared,
|
|
33
|
-
)
|
|
25
|
+
from .shared import SharedFamilies, fingerprint, new_shared_id
|
|
34
26
|
from .tasks import has_open_tasks, open_tasks, syncable_tasks
|
|
35
27
|
from .text import NodeText, text_slices, visible_text, word_count
|
|
36
28
|
from .walk import Ref, Walker
|
|
@@ -52,6 +44,7 @@ __all__ = [
|
|
|
52
44
|
"Paragraph",
|
|
53
45
|
"Ref",
|
|
54
46
|
"Selection",
|
|
47
|
+
"SharedFamilies",
|
|
55
48
|
"TableCell",
|
|
56
49
|
"TaskItem",
|
|
57
50
|
"TaskList",
|
|
@@ -60,9 +53,8 @@ __all__ = [
|
|
|
60
53
|
"Unknown",
|
|
61
54
|
"Walker",
|
|
62
55
|
"content_id",
|
|
63
|
-
"
|
|
56
|
+
"fingerprint",
|
|
64
57
|
"has_open_tasks",
|
|
65
|
-
"has_shared",
|
|
66
58
|
"is_parseable",
|
|
67
59
|
"key",
|
|
68
60
|
"kind",
|
|
@@ -70,10 +62,6 @@ __all__ = [
|
|
|
70
62
|
"node_id",
|
|
71
63
|
"open_tasks",
|
|
72
64
|
"registry",
|
|
73
|
-
"shared_families",
|
|
74
|
-
"shared_id",
|
|
75
|
-
"stamp_shared",
|
|
76
|
-
"sync_shared",
|
|
77
65
|
"syncable_tasks",
|
|
78
66
|
"text_slices",
|
|
79
67
|
"tiptap_id",
|
tiptap_python_utils/content.py
CHANGED
|
@@ -28,6 +28,7 @@ from .model import (
|
|
|
28
28
|
Text,
|
|
29
29
|
)
|
|
30
30
|
from .select import Selection
|
|
31
|
+
from .shared import SharedFamilies
|
|
31
32
|
from .walk import Ref, Walker
|
|
32
33
|
|
|
33
34
|
|
|
@@ -117,6 +118,36 @@ class Content:
|
|
|
117
118
|
tuple(ref for ref in self.refs() if ref.node.kind == node_kind),
|
|
118
119
|
)
|
|
119
120
|
|
|
121
|
+
def where_shared_id(self, shared_id: str) -> Selection:
|
|
122
|
+
return Selection(
|
|
123
|
+
self,
|
|
124
|
+
tuple(
|
|
125
|
+
ref
|
|
126
|
+
for ref in self.refs(parseable=True)
|
|
127
|
+
if ref.node.shared_id == shared_id
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def has_shared(self, shared_id: str) -> bool:
|
|
132
|
+
return any(
|
|
133
|
+
ref.node.shared_id == shared_id for ref in self.refs(parseable=True)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def shared_families(self) -> SharedFamilies:
|
|
137
|
+
return SharedFamilies.from_content(self)
|
|
138
|
+
|
|
139
|
+
def sync_shared(self, families: SharedFamilies) -> "Content":
|
|
140
|
+
if self._root is None:
|
|
141
|
+
return self
|
|
142
|
+
refs = tuple(
|
|
143
|
+
ref
|
|
144
|
+
for ref in self.refs(parseable=True)
|
|
145
|
+
if ref.node.shared_id and ref.node.shared_id in families
|
|
146
|
+
)
|
|
147
|
+
if not refs:
|
|
148
|
+
return self
|
|
149
|
+
return Selection(self, refs).transform(families.merge)
|
|
150
|
+
|
|
120
151
|
def append_root(self, node_or_raw: Any) -> "Content":
|
|
121
152
|
return self.of(kind.DOC).append(node_or_raw)
|
|
122
153
|
|
|
@@ -79,6 +79,9 @@ class Node:
|
|
|
79
79
|
attrs[name] = deepcopy(value)
|
|
80
80
|
return replace(self, attrs=attrs, present=self.present | {key.ATTRS})
|
|
81
81
|
|
|
82
|
+
def with_shared_id(self, value: str) -> "Node":
|
|
83
|
+
return self.with_attr(key.SHARED_ID, value)
|
|
84
|
+
|
|
82
85
|
def with_content(self, content: ContentTuple) -> "Node":
|
|
83
86
|
return replace(self, content=content, present=self.present | {key.CONTENT})
|
|
84
87
|
|
|
@@ -66,10 +66,6 @@ class TaskItem(Node):
|
|
|
66
66
|
def is_completed(self) -> bool:
|
|
67
67
|
return bool(task_completion(self.attrs, self.extra))
|
|
68
68
|
|
|
69
|
-
@property
|
|
70
|
-
def shared_id(self) -> str | None:
|
|
71
|
-
return policy.shared_id(self.attrs)
|
|
72
|
-
|
|
73
69
|
def raw_attrs(self) -> Dict[str, Any]:
|
|
74
70
|
attrs = super().raw_attrs()
|
|
75
71
|
if key.CHECKED in attrs or (
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from dataclasses import replace
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Iterator, Tuple
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterator, Tuple
|
|
8
8
|
|
|
9
9
|
from .. import codec
|
|
10
10
|
from ..contract import key
|
|
@@ -69,6 +69,9 @@ class Selection:
|
|
|
69
69
|
raise TiptapValidationError("Child node content must not be a document root")
|
|
70
70
|
return self._apply(lambda node: node.append(child))
|
|
71
71
|
|
|
72
|
+
def transform(self, fn: Callable[[Node], Node]) -> "Content":
|
|
73
|
+
return self._apply(fn)
|
|
74
|
+
|
|
72
75
|
def dump(self) -> str:
|
|
73
76
|
return self._content.dump()
|
|
74
77
|
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
"""Shared-node service exports."""
|
|
2
2
|
|
|
3
|
-
from .families import
|
|
4
|
-
from .fingerprint import
|
|
5
|
-
from .identity import new_shared_id
|
|
6
|
-
from .sync import sync_shared
|
|
3
|
+
from .families import SharedFamilies
|
|
4
|
+
from .fingerprint import fingerprint
|
|
5
|
+
from .identity import new_shared_id
|
|
7
6
|
|
|
8
7
|
__all__ = [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"SharedFamilies",
|
|
9
|
+
"fingerprint",
|
|
11
10
|
"new_shared_id",
|
|
12
|
-
"normalize_shared_id",
|
|
13
|
-
"shared_families",
|
|
14
|
-
"shared_id",
|
|
15
|
-
"stamp_shared",
|
|
16
|
-
"sync_shared",
|
|
17
11
|
]
|
|
@@ -1,47 +1,73 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SharedFamilies: canonical bodies grouped by sharedId."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import TYPE_CHECKING, Iterator, Mapping
|
|
7
8
|
|
|
8
|
-
from ..
|
|
9
|
+
from .. import codec
|
|
9
10
|
from ..contract import key
|
|
10
11
|
from ..exceptions import TiptapValidationError
|
|
11
|
-
from
|
|
12
|
-
from .
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
12
|
+
from ..model import Node
|
|
13
|
+
from .fingerprint import fingerprint
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ..content import Content
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class SharedFamilies:
|
|
21
|
+
"""Canonical node bodies indexed by sharedId."""
|
|
22
|
+
|
|
23
|
+
_bodies: Mapping[str, Node] = field(default_factory=lambda: MappingProxyType({}))
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_content(cls, content: "Content") -> "SharedFamilies":
|
|
27
|
+
bodies: dict[str, Node] = {}
|
|
28
|
+
prints: dict[str, str] = {}
|
|
29
|
+
for ref in content.refs(parseable=True):
|
|
30
|
+
sid = ref.node.shared_id
|
|
31
|
+
if not sid:
|
|
32
|
+
continue
|
|
33
|
+
fp = fingerprint(ref.node)
|
|
34
|
+
if sid in prints and prints[sid] != fp:
|
|
35
|
+
raise TiptapValidationError(
|
|
36
|
+
f"Conflicting node bodies detected for sharedId '{sid}'"
|
|
37
|
+
)
|
|
38
|
+
bodies.setdefault(sid, ref.node)
|
|
39
|
+
prints.setdefault(sid, fp)
|
|
40
|
+
return cls(MappingProxyType(bodies))
|
|
41
|
+
|
|
42
|
+
def __contains__(self, shared_id: str) -> bool:
|
|
43
|
+
return shared_id in self._bodies
|
|
44
|
+
|
|
45
|
+
def __getitem__(self, shared_id: str) -> Node:
|
|
46
|
+
return self._bodies[shared_id]
|
|
47
|
+
|
|
48
|
+
def __iter__(self) -> Iterator[str]:
|
|
49
|
+
return iter(self._bodies)
|
|
50
|
+
|
|
51
|
+
def __len__(self) -> int:
|
|
52
|
+
return len(self._bodies)
|
|
53
|
+
|
|
54
|
+
def merge(self, target: Node) -> Node:
|
|
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
|
+
"""
|
|
61
|
+
canonical = self[target.shared_id]
|
|
62
|
+
raw = canonical.raw()
|
|
63
|
+
attrs = dict(raw.get(key.ATTRS, {}))
|
|
64
|
+
attrs.pop(key.PLACE, None)
|
|
65
|
+
if target.id:
|
|
66
|
+
attrs[key.ID] = target.id
|
|
67
|
+
if target.shared_id:
|
|
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]
|
|
72
|
+
raw[key.ATTRS] = attrs
|
|
73
|
+
return codec.read_node(raw)
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
"""Identity-stripped fingerprint of a
|
|
1
|
+
"""Identity-stripped fingerprint of a node, used to detect divergent bodies."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from copy import deepcopy
|
|
7
|
-
from typing import Any
|
|
8
6
|
|
|
9
7
|
from ..contract import key
|
|
8
|
+
from ..model import Node
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
attrs = dict(
|
|
11
|
+
def fingerprint(node: Node) -> str:
|
|
12
|
+
raw = node.raw()
|
|
13
|
+
attrs = dict(raw.get(key.ATTRS, {}))
|
|
15
14
|
attrs.pop(key.ID, None)
|
|
16
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)
|
|
17
19
|
if attrs:
|
|
18
|
-
|
|
20
|
+
raw[key.ATTRS] = attrs
|
|
19
21
|
else:
|
|
20
|
-
|
|
21
|
-
return json.dumps(
|
|
22
|
+
raw.pop(key.ATTRS, None)
|
|
23
|
+
return json.dumps(raw, sort_keys=True)
|
|
@@ -1,48 +1,9 @@
|
|
|
1
|
-
"""Shared-node identity primitives.
|
|
2
|
-
|
|
3
|
-
Pure value-level helpers: normalize a sharedId, mint a new one, read a node's
|
|
4
|
-
sharedId, and stamp identity onto a raw node payload. No document traversal,
|
|
5
|
-
no families, no sync — those live in their sibling modules.
|
|
6
|
-
"""
|
|
1
|
+
"""Shared-node identity primitives."""
|
|
7
2
|
|
|
8
3
|
from __future__ import annotations
|
|
9
4
|
|
|
10
|
-
from typing import Any, Dict, Optional
|
|
11
5
|
from uuid import uuid4
|
|
12
6
|
|
|
13
|
-
from .. import codec
|
|
14
|
-
from ..contract import key
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def normalize_shared_id(value: Any) -> Optional[str]:
|
|
18
|
-
if value is None:
|
|
19
|
-
return None
|
|
20
|
-
if isinstance(value, str):
|
|
21
|
-
stripped = value.strip()
|
|
22
|
-
return stripped or None
|
|
23
|
-
return str(value)
|
|
24
|
-
|
|
25
7
|
|
|
26
8
|
def new_shared_id() -> str:
|
|
27
9
|
return f"shared-{uuid4().hex}"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def shared_id(node: str | Dict[str, Any]) -> Optional[str]:
|
|
31
|
-
parsed = codec.read_node_input(node, label="Node content")
|
|
32
|
-
return normalize_shared_id(parsed.attrs.get(key.SHARED_ID))
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def stamp_shared(
|
|
36
|
-
node: str | Dict[str, Any],
|
|
37
|
-
shared_id: str,
|
|
38
|
-
local_id: Optional[str] = None,
|
|
39
|
-
) -> dict[str, Any]:
|
|
40
|
-
"""Return a deep-copied node with sharedId and optional local id stamped."""
|
|
41
|
-
parsed = codec.read_node_input(node, label="Node content").raw()
|
|
42
|
-
attrs = dict(parsed.get(key.ATTRS, {}))
|
|
43
|
-
|
|
44
|
-
if local_id is not None:
|
|
45
|
-
attrs[key.ID] = local_id
|
|
46
|
-
attrs[key.SHARED_ID] = shared_id
|
|
47
|
-
parsed[key.ATTRS] = attrs
|
|
48
|
-
return parsed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tiptap_python_utils
|
|
3
|
-
Version: 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
|
|
@@ -219,36 +219,44 @@ task.shared_id # sharedId attr, if any
|
|
|
219
219
|
|
|
220
220
|
## Shared-Node Synchronization
|
|
221
221
|
|
|
222
|
-
`shared_families` collects canonical bodies grouped by `sharedId
|
|
223
|
-
`sync_shared` rewrites every
|
|
224
|
-
|
|
222
|
+
`Content.shared_families()` collects canonical bodies grouped by `sharedId` into
|
|
223
|
+
a `SharedFamilies` value object. `Content.sync_shared(families)` rewrites every
|
|
224
|
+
matching node in the document from those canonical bodies, preserving
|
|
225
|
+
per-instance identity (`id`, `sharedId`). Both return immutable values — the
|
|
226
|
+
original `Content` is never mutated.
|
|
225
227
|
|
|
226
228
|
```python
|
|
227
|
-
from tiptap_python_utils import
|
|
229
|
+
from tiptap_python_utils import Content
|
|
228
230
|
|
|
229
231
|
# Canonical doc: the source of truth for every shared body.
|
|
230
|
-
canonical = {"type": "doc", "content": [
|
|
232
|
+
canonical = Content.require({"type": "doc", "content": [
|
|
231
233
|
{
|
|
232
234
|
"type": "paragraph",
|
|
233
235
|
"attrs": {"id": "p1", "sharedId": "intro"},
|
|
234
236
|
"content": [{"type": "text", "text": "Authoritative intro"}],
|
|
235
237
|
}
|
|
236
|
-
]}
|
|
238
|
+
]})
|
|
237
239
|
|
|
238
240
|
# Doc that mirrors the same sharedId but with a stale body.
|
|
239
|
-
target = {"type": "doc", "content": [
|
|
241
|
+
target = Content.require({"type": "doc", "content": [
|
|
240
242
|
{
|
|
241
243
|
"type": "paragraph",
|
|
242
244
|
"attrs": {"id": "p1-copy", "sharedId": "intro"},
|
|
243
245
|
"content": [{"type": "text", "text": "Stale copy"}],
|
|
244
246
|
}
|
|
245
|
-
]}
|
|
247
|
+
]})
|
|
246
248
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
assert changed is True
|
|
249
|
+
synced = target.sync_shared(canonical.shared_families())
|
|
250
|
+
assert synced.has_shared("intro")
|
|
250
251
|
```
|
|
251
252
|
|
|
253
|
+
Related helpers on `Content`:
|
|
254
|
+
|
|
255
|
+
- `content.where_shared_id(sid)` — `Selection` over every node with that sharedId.
|
|
256
|
+
- `content.has_shared(sid)` — quick presence check.
|
|
257
|
+
- `node.with_shared_id(sid)` — stamp a sharedId onto a node (returns a new node).
|
|
258
|
+
- `new_shared_id()` — mint a fresh `shared-…` identifier.
|
|
259
|
+
|
|
252
260
|
## Architecture (one paragraph)
|
|
253
261
|
|
|
254
262
|
The package is layered: `contract` (key/kind/policy primitives) → `model`
|
|
@@ -268,13 +276,13 @@ Common imports are available from the package root:
|
|
|
268
276
|
from tiptap_python_utils import (
|
|
269
277
|
Content,
|
|
270
278
|
Paragraph,
|
|
279
|
+
SharedFamilies,
|
|
271
280
|
TaskItem,
|
|
272
281
|
Text,
|
|
273
282
|
has_open_tasks,
|
|
274
283
|
kind,
|
|
284
|
+
new_shared_id,
|
|
275
285
|
open_tasks,
|
|
276
|
-
shared_families,
|
|
277
|
-
sync_shared,
|
|
278
286
|
text_slices,
|
|
279
287
|
visible_text,
|
|
280
288
|
word_count,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
tiptap_python_utils/__init__.py,sha256=
|
|
2
|
-
tiptap_python_utils/content.py,sha256=
|
|
1
|
+
tiptap_python_utils/__init__.py,sha256=J78QM5SdfIB3qEeZ0f0ejFJgE1lS8o6VJdCxD5_yCkc,1417
|
|
2
|
+
tiptap_python_utils/content.py,sha256=NfTow1rPtTKcBkYTyPA50y0mjmlq57uXnXwFeKgKH_0,6522
|
|
3
3
|
tiptap_python_utils/exceptions.py,sha256=GIP91_6gaz4Vp-uMbmZBFFpnHHvToKYWyYf78Apt0ns,150
|
|
4
4
|
tiptap_python_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
5
|
tiptap_python_utils/types.py,sha256=f6ocw9oOTyj_khush3bP5cHS2YpybKS0jQgjzSNVx_k,152
|
|
@@ -8,21 +8,20 @@ tiptap_python_utils/codec/raw.py,sha256=glQWotPxzOFvPkg3nBBk3GbcM03Cj2yRclG62ckb
|
|
|
8
8
|
tiptap_python_utils/codec/reader.py,sha256=zBJQZ8NX3MuNNhEZbP2POjj9OnkiO64jnGoKDvbqju0,1376
|
|
9
9
|
tiptap_python_utils/codec/writer.py,sha256=YkOyHkkiufCRFUDtnuGXd6AD4a57mDsDWksn4ZFzyyc,285
|
|
10
10
|
tiptap_python_utils/contract/__init__.py,sha256=gJMHPqIl6vkcXAOKNKtZIbs8aQs1abqykEpc5i3abQY,115
|
|
11
|
-
tiptap_python_utils/contract/key.py,sha256=
|
|
11
|
+
tiptap_python_utils/contract/key.py,sha256=JV-pBYY_zO9uZ3XWQtjzrTcmhgisVFO2O-zscrUHhPA,294
|
|
12
12
|
tiptap_python_utils/contract/kind.py,sha256=I-vkJ9mskw07qjbIR5uC1s3z0AvtwA2y3qovkzTsY2Q,297
|
|
13
13
|
tiptap_python_utils/contract/policy.py,sha256=AeJfLv07JWqhuJMakUgz1Y-bMpac28h426bj17hKvwk,1193
|
|
14
14
|
tiptap_python_utils/model/__init__.py,sha256=Xv2GP0fCM72H2_Jxo5vvJfStA6ztDavzjYcaV0U0p9g,1049
|
|
15
|
-
tiptap_python_utils/model/base.py,sha256=
|
|
16
|
-
tiptap_python_utils/model/nodes.py,sha256=
|
|
15
|
+
tiptap_python_utils/model/base.py,sha256=STQczhXwe49x48TrCXkIzU4F4vcz1FS65ipXS663Az4,4663
|
|
16
|
+
tiptap_python_utils/model/nodes.py,sha256=obi8prcwJeJ3MBGZNCdbiGBwiPNJhiKb08b3oW5tTm4,2891
|
|
17
17
|
tiptap_python_utils/model/payload.py,sha256=UAWAaXgh50tglwFGBjkWj5ngMHQE35XQR1oHNysHdTw,2417
|
|
18
18
|
tiptap_python_utils/model/registry.py,sha256=2TbiEvUkVh85W0I0YKjpeVivcvKFCvcO1GtYEzMNu6Q,1168
|
|
19
19
|
tiptap_python_utils/select/__init__.py,sha256=lFavos0E3w2D_keUPDa5xkWhIjkQiee7yNK7X-G-U6o,88
|
|
20
|
-
tiptap_python_utils/select/selection.py,sha256=
|
|
21
|
-
tiptap_python_utils/shared/__init__.py,sha256=
|
|
22
|
-
tiptap_python_utils/shared/families.py,sha256=
|
|
23
|
-
tiptap_python_utils/shared/fingerprint.py,sha256=
|
|
24
|
-
tiptap_python_utils/shared/identity.py,sha256=
|
|
25
|
-
tiptap_python_utils/shared/sync.py,sha256=k2523VJJsiIRNrvJyt9W11Z7JIoniKSxTnrwtv43O1A,2127
|
|
20
|
+
tiptap_python_utils/select/selection.py,sha256=oTJ5l4o4vr0yKMXcIfhZInzXFqD8X6T9ZhE2WSxpVEY,4999
|
|
21
|
+
tiptap_python_utils/shared/__init__.py,sha256=XoRaVFjOlCWbnTeTFKw5DdoLqGA6sGdRr0eGpSkFQS8,223
|
|
22
|
+
tiptap_python_utils/shared/families.py,sha256=T89HaILztYmEJU_0rWeJYQuTc1uU0oCS1QuYQF_b-wg,2440
|
|
23
|
+
tiptap_python_utils/shared/fingerprint.py,sha256=X0zrxbjM-ATUukXp5YUYBW9XzkeeYxP7szpHeFBsr_8,582
|
|
24
|
+
tiptap_python_utils/shared/identity.py,sha256=cwUy6CMvlGV15Z8FyY3idDl2tPs9eiIWbwJRsNQgwY0,164
|
|
26
25
|
tiptap_python_utils/tasks/__init__.py,sha256=EXNu9YfJHBjyUZGf5HJIvaW7Ub_aN6fw_CZBNjBW0Og,154
|
|
27
26
|
tiptap_python_utils/tasks/query.py,sha256=59hb0fubBHlfOQekv_KY3DbTEBPFdYh_1Tc9LuFsL9w,464
|
|
28
27
|
tiptap_python_utils/text/__init__.py,sha256=F8PVtbGQlshGAtNLGNhGt8eyOwyE3Z5GC2lZLovxWg4,170
|
|
@@ -31,8 +30,8 @@ tiptap_python_utils/tree/__init__.py,sha256=YzlXRkQrlRGYmkWbpZ8mutZOmK4BzmiZaBqU
|
|
|
31
30
|
tiptap_python_utils/tree/path.py,sha256=bHmBFIZ3BV4FVlB79HhSgjeP6qHdNXClB9DFovSe8fs,1067
|
|
32
31
|
tiptap_python_utils/walk/__init__.py,sha256=hKU7DhRY6HVlxCxyWmnZtoobWNx5MCIT0OoErGe4Bp4,120
|
|
33
32
|
tiptap_python_utils/walk/traversal.py,sha256=6GDFmPAuo_t4FeOeV6hqrqvwcTZ8G93ep21-hrFt7S0,2320
|
|
34
|
-
tiptap_python_utils-0.
|
|
35
|
-
tiptap_python_utils-0.
|
|
36
|
-
tiptap_python_utils-0.
|
|
37
|
-
tiptap_python_utils-0.
|
|
38
|
-
tiptap_python_utils-0.
|
|
33
|
+
tiptap_python_utils-0.4.0.dist-info/licenses/LICENSE,sha256=pIGeAFdiaTJBpJilmEbR8YQ5agrt7DqbGlTTrzqO6Qo,1089
|
|
34
|
+
tiptap_python_utils-0.4.0.dist-info/METADATA,sha256=UOD9KXoyarycfxuiloO13KsRJvR8_hvYFtASYHMvP4I,10854
|
|
35
|
+
tiptap_python_utils-0.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
36
|
+
tiptap_python_utils-0.4.0.dist-info/top_level.txt,sha256=t7g66MmK6WqTixs5_ZnXe4j-RvH9thuDRKvxk_8F0AQ,20
|
|
37
|
+
tiptap_python_utils-0.4.0.dist-info/RECORD,,
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""Rewrite shared nodes from canonical bodies while preserving local identity."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from copy import deepcopy
|
|
6
|
-
from typing import Any, Dict
|
|
7
|
-
|
|
8
|
-
from .. import codec
|
|
9
|
-
from ..content import Content
|
|
10
|
-
from ..contract import key
|
|
11
|
-
from ..tree import node_at_path, replace_at_path
|
|
12
|
-
from .identity import normalize_shared_id
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def sync_shared(
|
|
16
|
-
content: str | Dict[str, Any],
|
|
17
|
-
families: dict[str, dict[str, Any]],
|
|
18
|
-
) -> tuple[str, bool]:
|
|
19
|
-
"""Rewrite matching shared nodes using canonical bodies."""
|
|
20
|
-
tiptap = Content.require(content)
|
|
21
|
-
updated_root = tiptap._require_root()
|
|
22
|
-
changed = False
|
|
23
|
-
|
|
24
|
-
refs = tuple(tiptap.refs(parseable=True))
|
|
25
|
-
for ref in sorted(refs, key=lambda item: len(item.path), reverse=True):
|
|
26
|
-
current = node_at_path(updated_root, ref.path)
|
|
27
|
-
current_raw = current.raw()
|
|
28
|
-
current_shared_id = normalize_shared_id(
|
|
29
|
-
current_raw.get(key.ATTRS, {}).get(key.SHARED_ID)
|
|
30
|
-
)
|
|
31
|
-
if not current_shared_id or current_shared_id not in families:
|
|
32
|
-
continue
|
|
33
|
-
|
|
34
|
-
replacement_raw = _merge_preserving_identity(
|
|
35
|
-
current_raw,
|
|
36
|
-
families[current_shared_id],
|
|
37
|
-
)
|
|
38
|
-
if replacement_raw == current_raw:
|
|
39
|
-
continue
|
|
40
|
-
|
|
41
|
-
replacement = codec.read_node_input(replacement_raw, label="Node content")
|
|
42
|
-
updated_root = replace_at_path(updated_root, ref.path, replacement)
|
|
43
|
-
changed = True
|
|
44
|
-
|
|
45
|
-
return tiptap._with_root(updated_root).dump(), changed
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _merge_preserving_identity(
|
|
49
|
-
target_node: dict[str, Any],
|
|
50
|
-
canonical_node: dict[str, Any],
|
|
51
|
-
) -> dict[str, Any]:
|
|
52
|
-
replacement = deepcopy(canonical_node)
|
|
53
|
-
target_attrs = target_node.get(key.ATTRS, {})
|
|
54
|
-
replacement_attrs = dict(replacement.get(key.ATTRS, {}))
|
|
55
|
-
|
|
56
|
-
if isinstance(target_attrs, dict) and target_attrs.get(key.ID):
|
|
57
|
-
replacement_attrs[key.ID] = target_attrs[key.ID]
|
|
58
|
-
target_shared_id = normalize_shared_id(target_attrs.get(key.SHARED_ID))
|
|
59
|
-
if target_shared_id:
|
|
60
|
-
replacement_attrs[key.SHARED_ID] = target_shared_id
|
|
61
|
-
|
|
62
|
-
replacement[key.ATTRS] = replacement_attrs
|
|
63
|
-
return replacement
|
|
File without changes
|
{tiptap_python_utils-0.2.0.dist-info → tiptap_python_utils-0.4.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|