tiptap-python-utils 0.5.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
@@ -65,6 +66,7 @@ __all__ = [
65
66
  "open_tasks",
66
67
  "registry",
67
68
  "syncable_tasks",
69
+ "task",
68
70
  "text_slices",
69
71
  "tiptap_id",
70
72
  "visible_text",
@@ -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.5.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
 
@@ -206,6 +211,9 @@ content.replace_by_id("p1", {
206
211
 
207
212
  ## Text Extraction
208
213
 
214
+ Pull the visible plain text out of a document — useful for search indexing,
215
+ word counts, or previews.
216
+
209
217
  ```python
210
218
  from tiptap_python_utils import Content, text_slices, visible_text, word_count
211
219
 
@@ -218,6 +226,9 @@ slices = text_slices(content, context=True)
218
226
 
219
227
  ## Tasks
220
228
 
229
+ Query task lists in a document — find every task item or check whether any are
230
+ still open.
231
+
221
232
  ```python
222
233
  from tiptap_python_utils import Content, has_open_tasks, open_tasks
223
234
 
@@ -240,6 +251,9 @@ task.shared_id # sharedId attr, if any
240
251
 
241
252
  ## Shared-Node Synchronization
242
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
+
243
257
  `Content.shared_families()` collects canonical bodies grouped by `sharedId` into
244
258
  a `SharedFamilies` value object. `Content.sync_shared(families)` rewrites every
245
259
  matching node in the document from those canonical bodies, preserving
@@ -278,17 +292,6 @@ Related helpers on `Content`:
278
292
  - `node.with_shared_id(sid)` — stamp a sharedId onto a node (returns a new node).
279
293
  - `new_shared_id()` — mint a fresh `shared-…` identifier.
280
294
 
281
- ## Architecture (one paragraph)
282
-
283
- The package is layered: `contract` (key/kind/policy primitives) → `model`
284
- (immutable AST with a registry of node classes; unknown kinds round-trip via
285
- `Unknown`) → `codec` (raw I/O in `raw.py`, hydration in `reader.py`, dump in
286
- `writer.py`) → `walk` & `tree` (traversal + path-based replacement on the
287
- immutable tree) → `select` (fluent `Selection` — the single home for mutation)
288
- → `content` (public facade) → `text` / `tasks` / `shared` (user-facing
289
- workflows built on `Content`). All nodes are `@dataclass(frozen=True)`;
290
- mutations return new instances.
291
-
292
295
  ## Public API
293
296
 
294
297
  Common imports are available from the package root:
@@ -311,35 +314,21 @@ from tiptap_python_utils import (
311
314
  )
312
315
  ```
313
316
 
314
- ## Stability
315
-
316
- The project is pre-1.0; minor versions may include breaking changes. See
317
- [CHANGELOG.md](CHANGELOG.md) for what changed and when.
318
-
319
- ## Development
320
-
321
- ```bash
322
- python -m venv .venv
323
- . .venv/bin/activate
324
- python -m pip install -e ".[dev]"
325
- pytest -q
326
- ```
327
-
328
- Build and validate a release artifact:
329
-
330
- ```bash
331
- python -m build
332
- python -m twine check dist/*
333
- ```
334
-
335
317
  ## Contributing
336
318
 
337
319
  Issues and pull requests are welcome. Please read
338
- [CONTRIBUTING.md](CONTRIBUTING.md) for the local setup and release checklist,
339
- 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
340
322
  [github.com/tugkanpilka/tiptap-python-utils/issues](https://github.com/tugkanpilka/tiptap-python-utils/issues)
341
- before larger changes so we can align on the approach.
323
+ before opening a pull request so we can align on the approach.
342
324
 
343
325
  ## License
344
326
 
345
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,8 +1,9 @@
1
- tiptap_python_utils/__init__.py,sha256=OBr4gCjfFWNe8MLv_JDb6_-_8EYRebbCiuAWTkEZIEI,1470
1
+ tiptap_python_utils/__init__.py,sha256=0YJx2Uj-7A2TEvWw-ajC8Jlv1wLCBH0pAy0NP8NWva8,1501
2
2
  tiptap_python_utils/content.py,sha256=3PtrEfmXyOMPoHVEH8xxHUYozIab5WlG1EXiPj0DAvs,7048
3
3
  tiptap_python_utils/exceptions.py,sha256=GIP91_6gaz4Vp-uMbmZBFFpnHHvToKYWyYf78Apt0ns,150
4
4
  tiptap_python_utils/identity.py,sha256=cXqAECIqft4My575tp-jUjKhv2oTAEQnQdDSVsva6Ps,151
5
5
  tiptap_python_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
+ tiptap_python_utils/task.py,sha256=x5LttsHEtHZL6oAzYfjLNUXsUWpuLmUU6N-qoV8J-xA,1831
6
7
  tiptap_python_utils/types.py,sha256=f6ocw9oOTyj_khush3bP5cHS2YpybKS0jQgjzSNVx_k,152
7
8
  tiptap_python_utils/codec/__init__.py,sha256=kaqQ9OHJg_S0hwLFGS55_Mdhyr2wt9BRMrdYfhIKdHI,510
8
9
  tiptap_python_utils/codec/raw.py,sha256=glQWotPxzOFvPkg3nBBk3GbcM03Cj2yRclG62ckb1k0,2082
@@ -31,8 +32,8 @@ tiptap_python_utils/tree/__init__.py,sha256=YzlXRkQrlRGYmkWbpZ8mutZOmK4BzmiZaBqU
31
32
  tiptap_python_utils/tree/path.py,sha256=bHmBFIZ3BV4FVlB79HhSgjeP6qHdNXClB9DFovSe8fs,1067
32
33
  tiptap_python_utils/walk/__init__.py,sha256=hKU7DhRY6HVlxCxyWmnZtoobWNx5MCIT0OoErGe4Bp4,120
33
34
  tiptap_python_utils/walk/traversal.py,sha256=6GDFmPAuo_t4FeOeV6hqrqvwcTZ8G93ep21-hrFt7S0,2320
34
- tiptap_python_utils-0.5.0.dist-info/licenses/LICENSE,sha256=pIGeAFdiaTJBpJilmEbR8YQ5agrt7DqbGlTTrzqO6Qo,1089
35
- tiptap_python_utils-0.5.0.dist-info/METADATA,sha256=Nbe0TNUp5hKT2WjMRmsV2fVkmHqSq3k2dJgm8SiGhqg,11649
36
- tiptap_python_utils-0.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
37
- tiptap_python_utils-0.5.0.dist-info/top_level.txt,sha256=t7g66MmK6WqTixs5_ZnXe4j-RvH9thuDRKvxk_8F0AQ,20
38
- tiptap_python_utils-0.5.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,,