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
572 changes: 526 additions & 46 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = "scikit_build_core.build"

[project]
name = "docx-comment-parser"
version = "1.5.0"
version = "1.5.1"
description = "Fast C++ library for extracting comment metadata from .docx files"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
23 changes: 16 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
ROOT = os.path.dirname(os.path.abspath(__file__))


def _read_long_description() -> str:
"""Return README.md's contents, or an empty string if it is absent."""
readme = os.path.join(ROOT, "README.md")
if not os.path.exists(readme):
return ""
with open(readme, encoding="utf-8") as fh:
return fh.read()


def _is_mingw() -> bool:
"""Return True when building with MinGW-w64 GCC under Windows."""
if sys.platform != "win32":
Expand Down Expand Up @@ -126,14 +135,10 @@ def _is_mingw() -> bool:

setup(
name="docx-comment-parser",
version="1.3.0",
version="1.5.1",
author="nick-developer",
description="Fast C++ library for extracting comment metadata from .docx files",
long_description=(
open(os.path.join(ROOT, "README.md"), encoding="utf-8").read()
if os.path.exists(os.path.join(ROOT, "README.md"))
else ""
),
long_description=_read_long_description(),
long_description_content_type="text/markdown",
ext_modules=[ext],
# Pure-Python wrapper package (exporters, filters, CLI) lives in src/.
Expand All @@ -151,9 +156,13 @@ def _is_mingw() -> bool:
"polars": ["polars>=1.0"],
"cli": ["typer>=0.12", "rich>=13.0"],
"report": ["jinja2>=3.0"],
# Kept in step with pyproject.toml: accelerated fuzzy matching for the
# diff engine, and exact token counts for chunking and compression.
"diff": ["rapidfuzz>=3.0"],
"llm": ["tiktoken>=0.7"],
"all": [
"pandas>=2.0", "polars>=1.0", "typer>=0.12", "rich>=13.0",
"jinja2>=3.0",
"jinja2>=3.0", "rapidfuzz>=3.0", "tiktoken>=0.7",
],
},
entry_points={
Expand Down
47 changes: 39 additions & 8 deletions src/docx_comment_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
records = parser.to_llm_dataset(anonymize="strict")
parser.export_jsonl("review.jsonl")

and in 1.5.1 you choose how much of the document comes with each comment, fix
the classifier's tags by hand, and spend fewer tokens doing it::

records = parser.to_llm_dataset(
context={"unit": "sentences", "before": 2, "after": 1},
overrides={4: {"priority": "blocker"}},
compress="balanced",
)

Export methods are attached to the compiled ``DocxParser`` / ``BatchParser``
classes at import time. That is deliberate: it keeps a single parser object as
the one thing a user needs to hold, rather than introducing a second wrapper
Expand All @@ -33,7 +42,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Union
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union

# ─── Compiled core ───────────────────────────────────────────────────────────
# Re-exported verbatim so `dcp.DocxParser` etc. resolve exactly as they did
Expand All @@ -60,9 +69,19 @@
import pandas
import polars

from .llm.dataset import LLMDataset
# The lazily resolved names below (see ``_LAZY``), declared here so static
# analysers can see every entry of ``__all__``. Never executed at runtime.
from . import comparison, llm, privacy, reporting
from .comparison.diff_engine import DiffResult, compare_comments
from .llm.chunking import estimate_tokens
from .llm.classification import classify_comment
from .llm.dataset import LLMDataset, LLMRecord
from .llm.dataset import build_dataset as build_llm_dataset
from .privacy.anonymizer import Anonymizer, anonymize_comments, scan_comments
from .privacy.policy import AnonymizationPolicy
from .reporting.analytics import build_report_data

__version__ = "1.5.0"
__version__ = "1.5.1"

__all__ = [
# compiled core (unchanged public API)
Expand Down Expand Up @@ -309,11 +328,14 @@ def _parser_to_llm_records(
classify: bool = True,
anonymize: Optional[Any] = None,
anonymizer: Optional[Any] = None,
**kwargs: Any,
) -> "LLMDataset":
"""Return this document's comments as an :class:`LLMDataset`.

The object form of :meth:`to_llm_dataset`: same records, plus the
anonymisation report and the vault that reverses it.
anonymisation report and the vault that reverses it. Also takes
``context=`` (sentences, paragraphs, words), ``overrides=`` (manual tags)
and ``compress=`` (token reduction) — see ``llm.build_dataset``.
"""
from .llm.dataset import build_dataset

Expand All @@ -326,6 +348,7 @@ def _parser_to_llm_records(
classify=classify,
anonymize=anonymize,
anonymizer=anonymizer,
**kwargs,
)


Expand Down Expand Up @@ -370,17 +393,25 @@ def _parser_to_embeddings_input(
)


#: build_dataset keywords that to_llm_chunks forwards, as opposed to the
#: chunking keywords it passes to Chunk packing.
_DATASET_OPTIONS = frozenset(
{
"chars_before", "chars_after", "include_thread", "include_context",
"classify", "anonymize", "anonymizer", "documents", "context",
"overrides", "compress",
}
)


def _parser_to_llm_chunks(
self: DocxParser, max_tokens: int = 100_000, **kwargs: Any
) -> List[Any]:
"""Split this document's records into context-window-sized chunks."""
dataset_kwargs = {
key: kwargs.pop(key)
for key in list(kwargs)
if key in {
"chars_before", "chars_after", "include_thread",
"include_context", "classify", "anonymize", "anonymizer",
}
if key in _DATASET_OPTIONS
}
return _parser_to_llm_records(self, **dataset_kwargs).chunks(max_tokens, **kwargs)

Expand Down
Loading
Loading