tiptap-python-utils 0.4.0__py3-none-any.whl → 0.6.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.
@@ -1,5 +1,6 @@
1
1
  """Public API for TipTap JSON utilities."""
2
2
 
3
+ from . import task
3
4
  from .contract import key, kind
4
5
  from .content import Content
5
6
  from .exceptions import TiptapValidationError
@@ -21,6 +22,7 @@ from .model import (
21
22
  registry,
22
23
  )
23
24
  from .contract.policy import content_id, is_parseable, node_id, tiptap_id
25
+ from .identity import new_node_id
24
26
  from .select import Selection
25
27
  from .shared import SharedFamilies, fingerprint, new_shared_id
26
28
  from .tasks import has_open_tasks, open_tasks, syncable_tasks
@@ -58,11 +60,13 @@ __all__ = [
58
60
  "is_parseable",
59
61
  "key",
60
62
  "kind",
63
+ "new_node_id",
61
64
  "new_shared_id",
62
65
  "node_id",
63
66
  "open_tasks",
64
67
  "registry",
65
68
  "syncable_tasks",
69
+ "task",
66
70
  "text_slices",
67
71
  "tiptap_id",
68
72
  "visible_text",
@@ -8,6 +8,7 @@ from .raw import (
8
8
  require_object,
9
9
  )
10
10
  from .reader import (
11
+ build_node,
11
12
  read_children,
12
13
  read_doc,
13
14
  read_node,
@@ -16,6 +17,7 @@ from .reader import (
16
17
  from .writer import dump, dumps
17
18
 
18
19
  __all__ = [
20
+ "build_node",
19
21
  "dump",
20
22
  "dumps",
21
23
  "normalize_text",
@@ -2,14 +2,30 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Mapping
5
+ from typing import Any, Dict, Mapping, Optional
6
6
 
7
7
  from ..contract import key, kind
8
8
  from ..exceptions import TiptapValidationError
9
9
  from ..model import ContentTuple, Doc, Node, registry
10
+ from ..model.payload import has_any_identity
10
11
  from .raw import require_object
11
12
 
12
13
 
14
+ # Kinds that hold block/container children, never inline text directly.
15
+ _TEXT_REJECTING_KINDS = frozenset(
16
+ {
17
+ kind.DOC,
18
+ kind.BULLET_LIST,
19
+ kind.ORDERED_LIST,
20
+ kind.TASK_LIST,
21
+ kind.LIST_ITEM,
22
+ kind.TASK_ITEM,
23
+ kind.BLOCKQUOTE,
24
+ kind.TABLE_CELL,
25
+ }
26
+ )
27
+
28
+
13
29
  def read_doc(raw: Mapping[str, Any]) -> Doc | None:
14
30
  """Read a raw TipTap document root."""
15
31
  if raw.get(key.TYPE) != kind.DOC:
@@ -30,6 +46,43 @@ def read_children(raw_children: Any) -> ContentTuple:
30
46
  return tuple(read_node(child) for child in raw_children if isinstance(child, dict))
31
47
 
32
48
 
49
+ def build_node(
50
+ node_kind: str,
51
+ text: str = "",
52
+ *,
53
+ attrs: Optional[Dict[str, Any]] = None,
54
+ node_id: Optional[str] = None,
55
+ ) -> Node:
56
+ """Build any typed node from (kind, text, attrs) via the registry.
57
+
58
+ Constructs a minimal raw payload and hydrates it through ``read_node`` so
59
+ subclass-typed fields (e.g. ``Heading.level``) and ``present`` semantics
60
+ are derived the same way as when parsing real JSON. Pure: ``node_id`` is
61
+ only stamped when ``attrs`` carries no identity of its own.
62
+ """
63
+ merged = dict(attrs or {})
64
+ if node_id is not None and not has_any_identity(merged):
65
+ merged[key.ID] = node_id
66
+
67
+ raw: Dict[str, Any] = {key.TYPE: node_kind}
68
+ if merged:
69
+ raw[key.ATTRS] = merged
70
+ if text:
71
+ _attach_text(raw, node_kind, text)
72
+ return read_node(raw)
73
+
74
+
75
+ def _attach_text(raw: Dict[str, Any], node_kind: str, text: str) -> None:
76
+ if node_kind == kind.TEXT:
77
+ raw[key.TEXT] = text
78
+ elif node_kind in _TEXT_REJECTING_KINDS:
79
+ raise TiptapValidationError(
80
+ f"Node kind '{node_kind}' cannot hold inline text content"
81
+ )
82
+ else:
83
+ raw[key.CONTENT] = [{key.TYPE: kind.TEXT, key.TEXT: text}]
84
+
85
+
33
86
  def read_node_input(node_or_raw: Any, *, label: str) -> Node:
34
87
  """Read either a typed node or a raw node payload."""
35
88
  if isinstance(node_or_raw, Node):
@@ -5,13 +5,14 @@ from __future__ import annotations
5
5
  import json
6
6
  from copy import deepcopy
7
7
  from dataclasses import dataclass
8
- from typing import Any, Dict, Iterator, Optional
8
+ from typing import Any, Callable, Dict, Iterator, Optional
9
9
 
10
10
  from .exceptions import TiptapValidationError
11
11
  from .types import DocumentContent
12
12
 
13
13
  from . import codec
14
14
  from .contract import key, kind, policy
15
+ from .identity import new_node_id
15
16
  from .model import (
16
17
  Blockquote,
17
18
  BulletList,
@@ -118,6 +119,9 @@ class Content:
118
119
  tuple(ref for ref in self.refs() if ref.node.kind == node_kind),
119
120
  )
120
121
 
122
+ def where(self, pred: Callable[[Node], bool]) -> Selection:
123
+ return Selection(self, tuple(self.refs())).filter(pred)
124
+
121
125
  def where_shared_id(self, shared_id: str) -> Selection:
122
126
  return Selection(
123
127
  self,
@@ -148,6 +152,19 @@ class Content:
148
152
  return self
149
153
  return Selection(self, refs).transform(families.merge)
150
154
 
155
+ def append(
156
+ self,
157
+ node_kind: str,
158
+ text: str = "",
159
+ *,
160
+ attrs: Optional[Dict[str, Any]] = None,
161
+ node_id: Optional[str] = None,
162
+ ) -> "Content":
163
+ node = codec.build_node(
164
+ node_kind, text, attrs=attrs, node_id=node_id or new_node_id()
165
+ )
166
+ return self.append_root(node)
167
+
151
168
  def append_root(self, node_or_raw: Any) -> "Content":
152
169
  return self.of(kind.DOC).append(node_or_raw)
153
170
 
@@ -0,0 +1,9 @@
1
+ """Generic node identity primitives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import uuid4
6
+
7
+
8
+ def new_node_id() -> str:
9
+ return uuid4().hex
@@ -43,6 +43,14 @@ class Selection:
43
43
  leaves = tuple(leaf for ref in self._refs for leaf in _first_text_descendant(ref))
44
44
  return Selection(self._content, leaves)
45
45
 
46
+ def filter(self, pred: Callable[[Node], bool]) -> "Selection":
47
+ """Narrow the selection to refs whose node matches ``pred``."""
48
+ return Selection(self._content, tuple(r for r in self._refs if pred(r.node)))
49
+
50
+ def any(self, pred: Callable[[Node], bool] = lambda _node: True) -> bool:
51
+ """True if any selected node matches ``pred`` (short-circuits)."""
52
+ return any(pred(r.node) for r in self._refs)
53
+
46
54
  def text(self, value: str) -> "Content":
47
55
  self._require_text_only("text")
48
56
  return self._apply(lambda node: node.with_text(value))
@@ -0,0 +1,55 @@
1
+ """Pure, raw-dict helpers for TipTap ``taskList`` / ``taskItem`` shapes.
2
+
3
+ This module is the raw-dict counterpart to the typed ``tasks`` package:
4
+ ``tasks`` works on hydrated ``Content``/``TaskItem`` objects, while ``task``
5
+ deals only with plain TipTap JSON dicts and never imports the model layer.
6
+
7
+ Read as a namespace: ``task.create_list(...)``, ``task.is_list(...)``,
8
+ ``task.is_item(...)``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, List
14
+
15
+ from .contract import key, kind
16
+
17
+ __all__ = ["create_list", "is_item", "is_list"]
18
+
19
+
20
+ def create_list(items: List[Any]) -> dict:
21
+ """Wrap ``items`` in a ``taskList`` block.
22
+
23
+ By design this does **not** copy ``items`` — the same list object is
24
+ stored on the returned block's ``content`` (by reference). Callers may
25
+ keep appending to the original list afterwards and see the change
26
+ reflected here. Do not introduce ``deepcopy``/``list(...)``/``[*items]``
27
+ here: the shared-reference behaviour is part of the contract and is
28
+ pinned by ``tests/test_task_namespace.py``.
29
+ """
30
+ return {key.TYPE: kind.TASK_LIST, key.CONTENT: items}
31
+
32
+
33
+ def is_list(item: Any) -> bool:
34
+ """True iff ``item`` is a valid ``taskList`` dict.
35
+
36
+ Safe on missing keys (uses ``.get()``): all three conditions must hold —
37
+ ``item`` is a dict, its ``type`` is ``taskList``, and its ``content`` is a
38
+ list.
39
+ """
40
+ if not isinstance(item, dict):
41
+ return False
42
+ return item.get(key.TYPE) == kind.TASK_LIST and isinstance(
43
+ item.get(key.CONTENT), list
44
+ )
45
+
46
+
47
+ def is_item(node: Any) -> bool:
48
+ """True iff ``node`` is a ``taskItem`` dict.
49
+
50
+ Safe on missing keys: ``node`` must be a dict whose ``type`` is
51
+ ``taskItem``.
52
+ """
53
+ if not isinstance(node, dict):
54
+ return False
55
+ return node.get(key.TYPE) == kind.TASK_ITEM
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tiptap_python_utils
3
- Version: 0.4.0
4
- Summary: Python utilities for parsing, traversing, editing, and serializing TipTap JSON content.
3
+ Version: 0.6.0
4
+ Summary: Pure-Python utilities for processing TipTap JSON on the server side. Parse, traverse, edit, and serialize TipTap documents — no JavaScript bridge required. Zero runtime dependencies, Python 3.9+.
5
5
  Author: tiptap_python_utils contributors
6
6
  License: MIT License
7
7
 
@@ -56,23 +56,10 @@ Dynamic: license-file
56
56
  [![CI](https://github.com/tugkanpilka/tiptap-python-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/tugkanpilka/tiptap-python-utils/actions/workflows/ci.yml)
57
57
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
58
58
 
59
- Python utilities for [TipTap](https://tiptap.dev) JSON content.
60
-
61
- `tiptap_python_utils` parses TipTap documents into typed, immutable Python
62
- nodes, preserves unknown/custom nodes for lossless round trips, and provides
63
- small helpers for traversal, immutable edits, visible text extraction, task
64
- queries, and shared-node synchronization.
65
-
66
- - **Zero runtime dependencies.** Standard library only.
67
- - **Python 3.9+.** Tested on 3.9, 3.10, 3.11, 3.12, 3.13.
68
- - **Lossless round trip.** Unknown node kinds and any extra fields are preserved.
69
- - **Immutable AST.** All mutations return new instances via a fluent selection API.
70
-
71
- ## Install
72
-
73
- ```bash
74
- pip install tiptap_python_utils
75
- ```
59
+ TipTap is a JavaScript editor. If your backend is Python and you need to
60
+ process TipTap JSON — extract text, query tasks, sync shared nodes — this
61
+ library does it in pure Python with zero dependencies. No JS bridge, no
62
+ Node.js subprocess.
76
63
 
77
64
  ## Quick Start
78
65
 
@@ -94,8 +81,24 @@ raw = {
94
81
  updated = Content.require(raw).where_id("p1").leaf().text("New").dump()
95
82
  ```
96
83
 
84
+ ## Features
85
+
86
+ - **Zero runtime dependencies.** Standard library only.
87
+ - **Python 3.9+.** Tested on 3.9, 3.10, 3.11, 3.12, 3.13.
88
+ - **Lossless round trip.** Unknown node kinds and any extra fields are preserved.
89
+ - **Immutable AST.** All mutations return new instances via a fluent selection API.
90
+
91
+ ## Install
92
+
93
+ ```bash
94
+ pip install tiptap_python_utils
95
+ ```
96
+
97
97
  ## Three Ways to Load a Document
98
98
 
99
+ Pick a constructor by how much you trust the input — lenient, strict, or
100
+ auto-wrapping a bare node into a `doc`.
101
+
99
102
  | Constructor | When to use | On invalid input |
100
103
  |---|---|---|
101
104
  | `Content.parse(raw)` | Lenient — `raw` may be `None`, a string, or a dict | Returns a `Content` with `root=None` |
@@ -104,7 +107,8 @@ updated = Content.require(raw).where_id("p1").leaf().text("New").dump()
104
107
 
105
108
  ## Lossless Round Trip
106
109
 
107
- Parsing never silently drops fields. Two mechanisms preserve information:
110
+ Parsing never silently drops fields custom nodes and unknown keys survive a
111
+ parse-then-serialize cycle byte-for-byte. Two mechanisms preserve information:
108
112
 
109
113
  - `Node.extra` stores top-level keys that aren't part of the known schema
110
114
  (e.g. custom node attributes, vendor-specific keys).
@@ -125,7 +129,8 @@ assert Content.require(raw).to_dict() == raw # byte-for-byte
125
129
 
126
130
  ## Typed Nodes
127
131
 
128
- Build typed nodes directly and serialize them back to TipTap-compatible JSON:
132
+ Build typed nodes directly in Python and serialize them back to
133
+ TipTap-compatible JSON.
129
134
 
130
135
  ```python
131
136
  from tiptap_python_utils import Content, Paragraph, Text
@@ -136,8 +141,8 @@ doc = Content.wrap(node.raw())
136
141
 
137
142
  ## Selection and Editing
138
143
 
139
- The fluent selection API is the single home for mutation. Selection methods
140
- return a new `Content`; the original is never mutated.
144
+ The fluent selection API is the single home for mutation: every method returns
145
+ a new `Content`, so the original is never mutated.
141
146
 
142
147
  ### Select by id or kind
143
148
 
@@ -149,6 +154,22 @@ content.where_id("p1")
149
154
 
150
155
  # By TipTap kind.
151
156
  content.of(kind.PARAGRAPH)
157
+
158
+ # By an arbitrary predicate over every node (and its descendants).
159
+ content.where(lambda node: getattr(node, "level", None) == 1)
160
+ ```
161
+
162
+ ### Generic queries
163
+
164
+ `Selection` carries two predicate primitives that work for any kind, so you
165
+ don't need a bespoke `has_heading_text`-style helper per node type:
166
+
167
+ ```python
168
+ # Narrow a selection further.
169
+ content.of(kind.HEADING).filter(lambda n: n.level == 2)
170
+
171
+ # Existence check (short-circuits).
172
+ content.of(kind.HEADING).any(lambda n: n.text.strip() == "Introduction")
152
173
  ```
153
174
 
154
175
  ### Atomic mutations
@@ -175,6 +196,11 @@ content.where_id("ul1").append({"type": "listItem", "attrs": {"id": "li-new"}, "
175
196
  # Append a node to the document root.
176
197
  content.append_root({"type": "paragraph", "attrs": {"id": "p2"}, "content": []})
177
198
 
199
+ # Build-and-append in one call — works for any kind, stamps a fresh id when
200
+ # none is given. Typed fields (e.g. Heading.level) hydrate correctly.
201
+ content.append(kind.HEADING, "New section", attrs={"level": 2})
202
+ content.append(kind.PARAGRAPH, "Body text", node_id="p3")
203
+
178
204
  # Replace a node by id (the replacement's attrs.id must match).
179
205
  content.replace_by_id("p1", {
180
206
  "type": "paragraph",
@@ -185,6 +211,9 @@ content.replace_by_id("p1", {
185
211
 
186
212
  ## Text Extraction
187
213
 
214
+ Pull the visible plain text out of a document — useful for search indexing,
215
+ word counts, or previews.
216
+
188
217
  ```python
189
218
  from tiptap_python_utils import Content, text_slices, visible_text, word_count
190
219
 
@@ -197,6 +226,9 @@ slices = text_slices(content, context=True)
197
226
 
198
227
  ## Tasks
199
228
 
229
+ Query task lists in a document — find every task item or check whether any are
230
+ still open.
231
+
200
232
  ```python
201
233
  from tiptap_python_utils import Content, has_open_tasks, open_tasks
202
234
 
@@ -219,6 +251,9 @@ task.shared_id # sharedId attr, if any
219
251
 
220
252
  ## Shared-Node Synchronization
221
253
 
254
+ Keep copies of the same logical node (linked by `sharedId`) in sync — collect
255
+ canonical bodies, then rewrite every matching node from them.
256
+
222
257
  `Content.shared_families()` collects canonical bodies grouped by `sharedId` into
223
258
  a `SharedFamilies` value object. `Content.sync_shared(families)` rewrites every
224
259
  matching node in the document from those canonical bodies, preserving
@@ -257,17 +292,6 @@ Related helpers on `Content`:
257
292
  - `node.with_shared_id(sid)` — stamp a sharedId onto a node (returns a new node).
258
293
  - `new_shared_id()` — mint a fresh `shared-…` identifier.
259
294
 
260
- ## Architecture (one paragraph)
261
-
262
- The package is layered: `contract` (key/kind/policy primitives) → `model`
263
- (immutable AST with a registry of node classes; unknown kinds round-trip via
264
- `Unknown`) → `codec` (raw I/O in `raw.py`, hydration in `reader.py`, dump in
265
- `writer.py`) → `walk` & `tree` (traversal + path-based replacement on the
266
- immutable tree) → `select` (fluent `Selection` — the single home for mutation)
267
- → `content` (public facade) → `text` / `tasks` / `shared` (user-facing
268
- workflows built on `Content`). All nodes are `@dataclass(frozen=True)`;
269
- mutations return new instances.
270
-
271
295
  ## Public API
272
296
 
273
297
  Common imports are available from the package root:
@@ -281,6 +305,7 @@ from tiptap_python_utils import (
281
305
  Text,
282
306
  has_open_tasks,
283
307
  kind,
308
+ new_node_id,
284
309
  new_shared_id,
285
310
  open_tasks,
286
311
  text_slices,
@@ -289,35 +314,21 @@ from tiptap_python_utils import (
289
314
  )
290
315
  ```
291
316
 
292
- ## Stability
293
-
294
- The project is pre-1.0; minor versions may include breaking changes. See
295
- [CHANGELOG.md](CHANGELOG.md) for what changed and when.
296
-
297
- ## Development
298
-
299
- ```bash
300
- python -m venv .venv
301
- . .venv/bin/activate
302
- python -m pip install -e ".[dev]"
303
- pytest -q
304
- ```
305
-
306
- Build and validate a release artifact:
307
-
308
- ```bash
309
- python -m build
310
- python -m twine check dist/*
311
- ```
312
-
313
317
  ## Contributing
314
318
 
315
319
  Issues and pull requests are welcome. Please read
316
- [CONTRIBUTING.md](CONTRIBUTING.md) for the local setup and release checklist,
317
- and open an issue at
320
+ [CONTRIBUTING.md](CONTRIBUTING.md) for the local setup, architecture overview,
321
+ and release checklist, and open an issue at
318
322
  [github.com/tugkanpilka/tiptap-python-utils/issues](https://github.com/tugkanpilka/tiptap-python-utils/issues)
319
- before larger changes so we can align on the approach.
323
+ before opening a pull request so we can align on the approach.
320
324
 
321
325
  ## License
322
326
 
323
327
  MIT — see [LICENSE](LICENSE).
328
+
329
+ ## Stability
330
+
331
+ The project is pre-1.0; minor versions may include breaking changes. See
332
+ [CHANGELOG.md](CHANGELOG.md) for what changed and when.
333
+ </content>
334
+ </invoke>
@@ -1,11 +1,13 @@
1
- tiptap_python_utils/__init__.py,sha256=J78QM5SdfIB3qEeZ0f0ejFJgE1lS8o6VJdCxD5_yCkc,1417
2
- tiptap_python_utils/content.py,sha256=NfTow1rPtTKcBkYTyPA50y0mjmlq57uXnXwFeKgKH_0,6522
1
+ tiptap_python_utils/__init__.py,sha256=0YJx2Uj-7A2TEvWw-ajC8Jlv1wLCBH0pAy0NP8NWva8,1501
2
+ tiptap_python_utils/content.py,sha256=3PtrEfmXyOMPoHVEH8xxHUYozIab5WlG1EXiPj0DAvs,7048
3
3
  tiptap_python_utils/exceptions.py,sha256=GIP91_6gaz4Vp-uMbmZBFFpnHHvToKYWyYf78Apt0ns,150
4
+ tiptap_python_utils/identity.py,sha256=cXqAECIqft4My575tp-jUjKhv2oTAEQnQdDSVsva6Ps,151
4
5
  tiptap_python_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
+ tiptap_python_utils/task.py,sha256=x5LttsHEtHZL6oAzYfjLNUXsUWpuLmUU6N-qoV8J-xA,1831
5
7
  tiptap_python_utils/types.py,sha256=f6ocw9oOTyj_khush3bP5cHS2YpybKS0jQgjzSNVx_k,152
6
- tiptap_python_utils/codec/__init__.py,sha256=by1i-NoxvdH-_sBxYRQrm_P5EaOCRDYss7pkcj-zot4,476
8
+ tiptap_python_utils/codec/__init__.py,sha256=kaqQ9OHJg_S0hwLFGS55_Mdhyr2wt9BRMrdYfhIKdHI,510
7
9
  tiptap_python_utils/codec/raw.py,sha256=glQWotPxzOFvPkg3nBBk3GbcM03Cj2yRclG62ckb1k0,2082
8
- tiptap_python_utils/codec/reader.py,sha256=zBJQZ8NX3MuNNhEZbP2POjj9OnkiO64jnGoKDvbqju0,1376
10
+ tiptap_python_utils/codec/reader.py,sha256=LOcGgx8eFOWH_5LyI2ydBfJP54XwJxqFBjOs8zUUYCE,2963
9
11
  tiptap_python_utils/codec/writer.py,sha256=YkOyHkkiufCRFUDtnuGXd6AD4a57mDsDWksn4ZFzyyc,285
10
12
  tiptap_python_utils/contract/__init__.py,sha256=gJMHPqIl6vkcXAOKNKtZIbs8aQs1abqykEpc5i3abQY,115
11
13
  tiptap_python_utils/contract/key.py,sha256=JV-pBYY_zO9uZ3XWQtjzrTcmhgisVFO2O-zscrUHhPA,294
@@ -17,7 +19,7 @@ tiptap_python_utils/model/nodes.py,sha256=obi8prcwJeJ3MBGZNCdbiGBwiPNJhiKb08b3oW
17
19
  tiptap_python_utils/model/payload.py,sha256=UAWAaXgh50tglwFGBjkWj5ngMHQE35XQR1oHNysHdTw,2417
18
20
  tiptap_python_utils/model/registry.py,sha256=2TbiEvUkVh85W0I0YKjpeVivcvKFCvcO1GtYEzMNu6Q,1168
19
21
  tiptap_python_utils/select/__init__.py,sha256=lFavos0E3w2D_keUPDa5xkWhIjkQiee7yNK7X-G-U6o,88
20
- tiptap_python_utils/select/selection.py,sha256=oTJ5l4o4vr0yKMXcIfhZInzXFqD8X6T9ZhE2WSxpVEY,4999
22
+ tiptap_python_utils/select/selection.py,sha256=j-3x_MgNdp88dlwWKDdDrXkyDoCXleXuvlnVQdWTezM,5432
21
23
  tiptap_python_utils/shared/__init__.py,sha256=XoRaVFjOlCWbnTeTFKw5DdoLqGA6sGdRr0eGpSkFQS8,223
22
24
  tiptap_python_utils/shared/families.py,sha256=T89HaILztYmEJU_0rWeJYQuTc1uU0oCS1QuYQF_b-wg,2440
23
25
  tiptap_python_utils/shared/fingerprint.py,sha256=X0zrxbjM-ATUukXp5YUYBW9XzkeeYxP7szpHeFBsr_8,582
@@ -30,8 +32,8 @@ tiptap_python_utils/tree/__init__.py,sha256=YzlXRkQrlRGYmkWbpZ8mutZOmK4BzmiZaBqU
30
32
  tiptap_python_utils/tree/path.py,sha256=bHmBFIZ3BV4FVlB79HhSgjeP6qHdNXClB9DFovSe8fs,1067
31
33
  tiptap_python_utils/walk/__init__.py,sha256=hKU7DhRY6HVlxCxyWmnZtoobWNx5MCIT0OoErGe4Bp4,120
32
34
  tiptap_python_utils/walk/traversal.py,sha256=6GDFmPAuo_t4FeOeV6hqrqvwcTZ8G93ep21-hrFt7S0,2320
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,,
35
+ tiptap_python_utils-0.6.0.dist-info/licenses/LICENSE,sha256=pIGeAFdiaTJBpJilmEbR8YQ5agrt7DqbGlTTrzqO6Qo,1089
36
+ tiptap_python_utils-0.6.0.dist-info/METADATA,sha256=UnRezBToej-6BT4lPGNwqT7OK6-P3l9omAwIdO2uMxc,11468
37
+ tiptap_python_utils-0.6.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
38
+ tiptap_python_utils-0.6.0.dist-info/top_level.txt,sha256=t7g66MmK6WqTixs5_ZnXe4j-RvH9thuDRKvxk_8F0AQ,20
39
+ tiptap_python_utils-0.6.0.dist-info/RECORD,,