Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@ jobs:
# (avoids PF-007: CLI surface must actually run in CI, not skip silently).
- name: Build mds CLI (CF-SM2 parity producer)
run: cargo build -p mds-cli
# Install Python + the mdscript binding so CF-SM2 can compare the Python
# Install Python + the markdown_script binding so CF-SM2 can compare the Python
# output as the fourth parity surface (avoids PF-007). pip uses the
# maturin PEP 517 build backend declared in crates/mds-python/pyproject.toml;
# no pre-installed maturin needed. MDS_PYTHON_BIN is set to the exact
# executable that owns the installed module so findPythonForMdscript()
# executable that owns the installed module so findPythonForMarkdownScript()
# picks it up cross-platform (bin/ on Unix, Scripts/ on Windows).
- uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -327,8 +327,8 @@ jobs:
run: ls dist/ && ls dist/ | grep -q 'cp311-abi3' || (echo "expected a cp311-abi3 wheel" && exit 1)
- name: Install the built wheel (not editable) + import smoke
run: |
python -m pip install --find-links dist --no-index mdscript
python -c "import mdscript; r = mdscript.compile('Hello {n}!', vars={'n': 'CI'}); print(r.kind, r.output)"
python -m pip install --find-links dist --no-index markdown-script
python -c "import markdown_script; r = markdown_script.compile('Hello {n}!', vars={'n': 'CI'}); print(r.kind, r.output)"
# Run the suite against the INSTALLED wheel (mypy/pyright deselected — they
# are covered in the develop job; parity CLI is built for the live check).
- name: Build mds CLI (parity producer)
Expand Down
22 changes: 18 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
public `mds-core` API: `format_str` / `format_str_with`. (#60)

- **Native Python bindings** (`crates/mds-python`, PyO3 + maturin), to be distributed
as `mdscript` on PyPI. Seven functions — `compile`, `compile_file`,
as `markdown-script` on PyPI (importable as `markdown_script`). Seven functions — `compile`, `compile_file`,
`compile_virtual`, `check`, `check_file`, `check_virtual`, and `scan_imports` —
with idiomatic keyword-only signatures. Results are typed, frozen, and picklable
(`CompileResult` / `Message` / `Span` / `CheckResult`), and failures raise a native
Expand Down Expand Up @@ -158,9 +158,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and WASM backends implement the full surface; `lintFile()` on the WASM backend
uses `buildModulesMap` for `@import` resolution.

**Python** (`mdscript`): `lint()`, `lint_file()`, `lint_virtual()` with keyword-only
**Python** (`markdown_script`): `lint()`, `lint_file()`, `lint_virtual()` with keyword-only
`rules` and `base_path` / `vars` options; `LintResult` with `.version`, `.truncated`,
`.files`, `.to_dict()`, `.to_json()`. Stubs shipped in `_mdscript.pyi` / `__init__.pyi`.
`.files`, `.to_dict()`, `.to_json()`. Stubs shipped in `_markdown_script.pyi` / `__init__.pyi`.

**⚠ TypeScript interface implementers**: `MdsBaseBackend` gained `lint` and
`lintVirtual` as required members; `MdsNodeBackend` gained `lintFile`. Code that
Expand All @@ -186,7 +186,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

**WASM** (`@mdscript/mds-wasm`): same `sourceMap`/`sourcesContent` options on `compile()`.

**Python** (`mdscript`): `compile()`, `compile_file()`, and `compile_virtual()` accept
**Python** (`markdown_script`): `compile()`, `compile_file()`, and `compile_virtual()` accept
`source_map=True` and `sources_content=True` keyword arguments. Results expose a
`.source_map` property (`dict | None`).

Expand Down Expand Up @@ -528,6 +528,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
four tie-free sort sites switched to `sort_unstable`. CI-measured size: 836,126 bytes
(Binaryen v129) against the 850,000-byte guard.

- **Python distribution renamed: `mdscript` → `markdown-script`; import as `markdown_script` (#292, ADR-012).**
The PyPI name `mdscript` is held by a genuine dormant 2021 project with a direct
topical collision (`top_level.txt` is exactly `mdscript`), and there is no PEP 541
route for reclaiming it. Both the distribution name and the importable module name
must change together because they were identical before (`mdscript`/`mdscript`). The
private extension module is renamed from `_mdscript` to `_markdown_script` for
consistency with the new public package name.

This rename is pre-publication: `publish = false` in `crates/mds-python/Cargo.toml`
and there is no PyPI publish step in `release.yml`, so there are zero existing PyPI
consumers. No deprecation shim or compat alias is provided.

Migration: `pip install markdown-script` (hyphen), then `import markdown_script` (underscore).

### Deprecated

- **`mds::fix::apply_fixes` is deprecated in favor of `apply_fixes_incremental` (#209).**
Expand Down
8 changes: 4 additions & 4 deletions crates/mds-python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ debug-panics = []

[lib]
crate-type = ["cdylib"]
# The compiled module is imported as `mdscript._mdscript`; the Rust symbol is
# `PyInit__mdscript`, so the lib name must be `_mdscript` and match the
# `#[pymodule] fn _mdscript` in src/lib.rs.
name = "_mdscript"
# The compiled module is imported as `markdown_script._markdown_script`; the Rust symbol is
# `PyInit__markdown_script`, so the lib name must be `_markdown_script` and match the
# `#[pymodule] fn _markdown_script` in src/lib.rs.
name = "_markdown_script"
# No Rust unit tests and no doctests: `abi3-py311` is always on and this is a
# cdylib, so `cargo build/clippy/test --workspace` compile the extension without
# linking libpython. All coverage is the pytest suite. `test = false`
Expand Down
24 changes: 12 additions & 12 deletions crates/mds-python/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# mdscript
# markdown-script

Native **Python bindings** for [MDS (Markdown Script)](https://github.com/dean0x/mdscript) —
a composable LLM prompt-template compiler. Compile `.mds` templates to Markdown or
structured chat messages in-process, backed by the same Rust core as the MDS CLI and
the Node.js / WASM bindings. Output is byte-identical across every binding.

```bash
pip install mdscript
pip install markdown-script
```

> **Not yet on PyPI** — publishing and the `mdscript` name registration are tracked in
> **Not yet on PyPI** — publishing and the `markdown-script` name registration are tracked in
> [#132]. For now, build from source: `pip install ./crates/mds-python` (or `maturin
> build -m crates/mds-python/Cargo.toml` to produce a wheel), with a Rust toolchain and
> `python3` on `PATH`. Once published, wheels ship as `cp311-abi3` (CPython 3.11+, one
Expand All @@ -20,24 +20,24 @@ pip install mdscript
## Quick start

```python
import mdscript
import markdown_script

# Markdown template
r = mdscript.compile("Hello {{name}}!", vars={"name": "Alice"})
r = markdown_script.compile("Hello {{name}}!", vars={"name": "Alice"})
assert r.kind == "markdown"
assert r.output == "Hello Alice!"

# @message template → structured messages
r = mdscript.compile("@message user:\nHi\n@end\n")
r = markdown_script.compile("@message user:\nHi\n@end\n")
assert r.kind == "messages"
assert r.messages[0].role == "user"
assert r.output is None # inactive payload is None

# Validate without rendering
mdscript.check("Hello {{name}}!", vars={"name": "Bob"})
markdown_script.check("Hello {{name}}!", vars={"name": "Bob"})

# Compile a file (dependencies come back as absolute paths)
r = mdscript.compile_file("prompts/agent.mds")
r = markdown_script.compile_file("prompts/agent.mds")
print(r.dependencies)
```

Expand Down Expand Up @@ -93,12 +93,12 @@ Results are frozen, comparable by value, intentionally unhashable, and picklable

### Errors

Every failure raises `mdscript.MdsError` (a subclass of `Exception`):
Every failure raises `markdown_script.MdsError` (a subclass of `Exception`):

```python
try:
mdscript.compile("Hello {{undefined}}!")
except mdscript.MdsError as e:
markdown_script.compile("Hello {{undefined}}!")
except markdown_script.MdsError as e:
print(e.code) # "mds::undefined_var"
print(str(e)) # == e.message
print(e.help) # hint, or None
Expand All @@ -109,7 +109,7 @@ except mdscript.MdsError as e:
## Concurrency

Compilation is synchronous, stateless CPU work and **releases the GIL**, so calls
parallelise across threads. For `asyncio`, offload with `asyncio.to_thread(mdscript.compile, src)`.
parallelise across threads. For `asyncio`, offload with `asyncio.to_thread(markdown_script.compile, src)`.
The extension is also free-threading (`cp314t`) ready — result classes are frozen and
the module declares `gil_used = false` — though a free-threaded wheel is not yet shipped.

Expand Down
12 changes: 6 additions & 6 deletions crates/mds-python/benchmarks/bench.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""On-demand throughput + GIL-scaling benchmark for mdscript (stdlib only).
"""On-demand throughput + GIL-scaling benchmark for markdown_script (stdlib only).

Not part of the gated test suite. Run directly:

Expand All @@ -16,19 +16,19 @@
import threading
import time

import mdscript
import markdown_script

REPRESENTATIVE = "---\nname: Alice\n---\n@for i in items:\n- {{name}}: item {{i}}\n@end\n"
ITEMS = list(range(200))
VARS = {"items": ITEMS}


def bench_latency(iterations: int = 2000) -> None:
mdscript.compile(REPRESENTATIVE, vars=VARS) # warm up
markdown_script.compile(REPRESENTATIVE, vars=VARS) # warm up
samples = []
for _ in range(iterations):
t0 = time.perf_counter()
mdscript.compile(REPRESENTATIVE, vars=VARS)
markdown_script.compile(REPRESENTATIVE, vars=VARS)
samples.append(time.perf_counter() - t0)
samples.sort()
p50 = statistics.median(samples) * 1e6
Expand All @@ -40,7 +40,7 @@ def bench_latency(iterations: int = 2000) -> None:
def bench_gil_scaling(total: int = 4000) -> None:
def run_n(n: int) -> None:
for _ in range(n):
mdscript.compile(REPRESENTATIVE, vars=VARS)
markdown_script.compile(REPRESENTATIVE, vars=VARS)

t0 = time.perf_counter()
run_n(total)
Expand Down Expand Up @@ -68,7 +68,7 @@ def run_n(n: int) -> None:


def main() -> None:
print(f"mdscript {mdscript.__version__}\n")
print(f"markdown-script {markdown_script.__version__}\n")
bench_latency()
bench_gil_scaling()

Expand Down
8 changes: 4 additions & 4 deletions crates/mds-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ requires = ["maturin>=1.13.3,<1.14"]
build-backend = "maturin"

[project]
name = "mdscript"
name = "markdown-script"
description = "Composable LLM prompt template compiler — native Python bindings for MDS (Markdown Script)"
requires-python = ">=3.11"
# PEP 639: an SPDX license expression as a plain string (NOT a `{ text = ... }`
Expand Down Expand Up @@ -47,10 +47,10 @@ markers = [
]

[tool.maturin]
# Mixed Rust/Python layout: the pure-Python package lives under python/mdscript/ and
# the compiled extension is injected as the submodule `mdscript._mdscript`.
# Mixed Rust/Python layout: the pure-Python package lives under python/markdown_script/
# and the compiled extension is injected as the submodule `markdown_script._markdown_script`.
python-source = "python"
module-name = "mdscript._mdscript"
module-name = "markdown_script._markdown_script"
# abi3-py311: emit a single `cp311-abi3` wheel usable on CPython 3.11+. Redundant with
# the always-on workspace pyo3 feature, but stated explicitly for clarity.
features = ["pyo3/abi3-py311"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""mdscript — composable LLM prompt template compiler (native Python bindings).
"""markdown_script — composable LLM prompt template compiler (native Python bindings).

Compile ``.mds`` templates to Markdown or structured chat messages in-process, via
the same Rust core that powers the MDS CLI and Node.js/WASM bindings. Output is
byte-identical across all bindings.

Example
-------
>>> import mdscript
>>> r = mdscript.compile("Hello {{name}}!", vars={"name": "Alice"})
>>> import markdown_script
>>> r = markdown_script.compile("Hello {{name}}!", vars={"name": "Alice"})
>>> r.kind, r.output
('markdown', 'Hello Alice!')

Expand All @@ -20,7 +20,7 @@

from importlib import metadata as _metadata

from ._mdscript import (
from ._markdown_script import (
CheckResult,
CompileResult,
LintDiagnostic,
Expand All @@ -41,13 +41,13 @@
scan_imports,
)

# The native exception is registered under the extension submodule `_mdscript`.
# Retag it (and it alone — the result classes already declare `module = "mdscript"`)
# to the public package so `pickle`, `repr`, and tracebacks resolve `mdscript.MdsError`.
MdsError.__module__ = "mdscript"
# The native exception is registered under the extension submodule `_markdown_script`.
# Retag it (and it alone — the result classes already declare `module = "markdown_script"`)
# to the public package so `pickle`, `repr`, and tracebacks resolve `markdown_script.MdsError`.
MdsError.__module__ = "markdown_script"

try:
__version__ = _metadata.version("mdscript")
__version__ = _metadata.version("markdown-script")
except _metadata.PackageNotFoundError: # pragma: no cover - source tree without an install
__version__ = "0.0.0"

Expand Down
49 changes: 49 additions & 0 deletions crates/mds-python/python/markdown_script/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Public type surface for the ``markdown_script`` package.

Everything is re-exported from the native ``._markdown_script`` extension; see
``_markdown_script.pyi`` for the full signatures.
"""

from __future__ import annotations

from ._markdown_script import CheckResult as CheckResult
from ._markdown_script import CompileResult as CompileResult
from ._markdown_script import LintDiagnostic as LintDiagnostic
from ._markdown_script import LintFileReport as LintFileReport
from ._markdown_script import LintResult as LintResult
from ._markdown_script import MdsError as MdsError
from ._markdown_script import Message as Message
from ._markdown_script import Span as Span
from ._markdown_script import check as check
from ._markdown_script import check_file as check_file
from ._markdown_script import check_virtual as check_virtual
from ._markdown_script import compile as compile
from ._markdown_script import compile_file as compile_file
from ._markdown_script import compile_virtual as compile_virtual
from ._markdown_script import lint as lint
from ._markdown_script import lint_file as lint_file
from ._markdown_script import lint_virtual as lint_virtual
from ._markdown_script import scan_imports as scan_imports

__version__: str
__all__ = [
"CheckResult",
"CompileResult",
"LintDiagnostic",
"LintFileReport",
"LintResult",
"MdsError",
"Message",
"Span",
"__version__",
"check",
"check_file",
"check_virtual",
"compile",
"compile_file",
"compile_virtual",
"lint",
"lint_file",
"lint_virtual",
"scan_imports",
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Type stubs for the native ``mdscript._mdscript`` extension module.
"""Type stubs for the native ``markdown_script._markdown_script`` extension module.

The runtime objects are implemented in Rust (PyO3). These stubs describe the public
surface for ``mypy``/``pyright``. Result classes are frozen — their attributes are
Expand Down
49 changes: 0 additions & 49 deletions crates/mds-python/python/mdscript/__init__.pyi

This file was deleted.

Loading
Loading