Skip to main content

Python implementation of core ProseMirror modules for collaborative editing

Project description

prosemirror-py

CI Code Coverage PyPI Package License Fellow Careers

This package provides Python implementations of the following ProseMirror packages:

The original implementation has been followed as closely as possible during translation to simplify keeping this package up-to-date with any upstream changes.

Why?

ProseMirror provides a powerful toolkit for building rich-text editors, but it's JavaScript-only. Until now, the only option for manipulating and working with ProseMirror documents from Python was to embed a JS runtime. With this translation, you can now define schemas, parse documents, and apply transforms directly via a native Python API.

Status

The full ProseMirror test suite has been translated and passes. This project only supports Python 3. The code has type annotations to support mypy or other typechecking tools.

Usage

Since this library is a direct port, the best place to learn how to use it is the official ProseMirror documentation. Here is a simple example using the included "basic" schema:

from prosemirror.transform import Transform
from prosemirror.schema.basic import schema

# Create a document containing a single paragraph with the text "Hello, world!"
doc = schema.node(
    "doc", {}, [schema.node("paragraph", {}, [schema.text("Hello, world!")])]
)

# Create a Transform which will be applied to the document.
tr = Transform(doc)

# Delete the text from position 3 to 5. Adds a ReplaceStep to the transform.
tr.delete(3, 5)

# Make the first three characters bold. Adds an AddMarkStep to the transform.
tr.add_mark(1, 4, schema.mark("strong"))

# This transform can be converted to JSON to be sent and applied elsewhere.
assert [step.to_json() for step in tr.steps] == [
    {"stepType": "replace", "from": 3, "to": 5},
    {"stepType": "addMark", "mark": {"type": "strong"}, "from": 1, "to": 4},
]

# The resulting document can also be converted to JSON.
assert tr.doc.to_json() == {
    "type": "doc",
    "content": [
        {
            "type": "paragraph",
            "content": [
                {"type": "text", "marks": [{"type": "strong"}], "text": "Heo"},
                {"type": "text", "text": ", world!"},
            ],
        }
    ],
}

Differences from Upstream

While the translation follows the original TypeScript implementation as closely as possible, some adaptations were necessary for Python. These are documented here for reference.

Naming Conventions

Python's snake_case naming is used throughout:

  • camelCase methods/properties become snake_case (e.g. nodeSize -> node_size, isBlock -> is_block, textBetween -> text_between)
  • from (a Python keyword) becomes from_ in parameter names and the Fragment.from_() static method

DOM Handling

The upstream uses browser DOM APIs. The Python port uses lxml for parsing and a lightweight custom Element / DocumentFragment model for serialization:

  • DOMParser: Uses lxml.html for HTML parsing. Text nodes are wrapped in <lxmltext> pseudo-elements since lxml doesn't represent text nodes as separate child elements. CSS selector matching uses lxml.cssselect.
  • DOMSerializer: Outputs HTML strings via custom Element and DocumentFragment classes rather than creating real DOM nodes.
  • XML namespaces: Not supported (raises NotImplementedError). This only affects SVG or MathML node serialization.

String Length and Slicing (UTF-16 Semantics)

JavaScript strings use UTF-16 encoding, so string.length counts UTF-16 code units (surrogate pairs count as 2). The Python port preserves these semantics using a text_length() helper and UTF-16 encode/decode for slicing in:

  • Node.node_size / TextNode.node_size
  • TextNode.cut()
  • TextNode.text_between()
  • Fragment.findIndex() / Fragment.cut()
  • diff.py (character-by-character comparison)

Deep Comparison

The upstream uses a custom compareDeep function for recursive comparison of arrays/objects. The Python port uses native ==, which already performs deep comparison of dicts and lists.

Resolve Cache

The upstream uses a WeakMap<Node, ResolveCache> for caching resolved positions. Python uses a dict[int, _ResolveCache] keyed by id(doc) with a weakref.ref callback to clean up entries when the document node is garbage collected.

Type System

  • TypeScript interfaces (NodeSpec, MarkSpec, ParseOptions, etc.) are translated as TypedDict or frozen dataclass types.
  • Union types use X | Y syntax (Python 3.10+).

Additional Conveniences

These are Python-specific additions not present in the upstream:

  • Fragment.from_json() accepts a JSON str and parses it automatically.
  • from_dom.py includes a from_html() helper to parse an HTML string directly to a ProseMirror document.
  • DOMSerializer output type is named HTMLOutputSpec (instead of DOMOutputSpec) to reflect that it produces HTML strings.

AI Disclosure

The initial version of this translation was written manually in 2019. AI is now used to help keep this translation up-to-date with upstream changes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

prosemirror-0.6.1.tar.gz (76.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

prosemirror-0.6.1-py3-none-any.whl (67.9 kB view details)

Uploaded Python 3

File details

Details for the file prosemirror-0.6.1.tar.gz.

File metadata

  • Download URL: prosemirror-0.6.1.tar.gz
  • Upload date:
  • Size: 76.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for prosemirror-0.6.1.tar.gz
Algorithm Hash digest
SHA256 eec45a4f35d03ddb58700199292646c34b334bbad385a21679fc7a8d181cd272
MD5 248f00201c27c63b912c9670c165c303
BLAKE2b-256 d68a36cd9224f70441fd66d428f7cad650923d949d0d9395cbae82aab6b78df5

See more details on using hashes here.

File details

Details for the file prosemirror-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: prosemirror-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 67.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for prosemirror-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f9d38313a72af1c579cbb87beac3a765f399416afc4567cb016aca53ec4cdae6
MD5 01b9112783f96ecd74effa9d1f0b4aac
BLAKE2b-256 8c6bff4f2362320c53ea989ef32e4ac978233b65ffb2e6e3131aea7860c740cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page