release: 0.9.61 — fix Python 3.12/3.13 breakage from 0.9.60

- chinese extra: pin jieba-py from 3.12 (was 3.14); jieba's invalid escapes are
  a hard error on 3.12+ and broke graphify.serve import.
- serve.py: suppress the tokenizer SyntaxWarning by category so -W error can't
  escalate it; catch SyntaxError as a fallback.
- hooks.py: harden the #3492 rebuild-root guard for 3.13, whose resolve() no
  longer raises on a symlink loop — require a real directory.
Verified full suite green on both 3.10 (5628) and 3.13 (5627).
This commit is contained in:
safishamsi committed 2026-09-12 22:23:00 +01:00
1 parent 7464f5b125
commit fe66389083
6 files changed
+84 -48

No files matched your search

+6 -1
View File
@@ -2,7 +2,12 @@
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
## 0.9.60 (unreleased)
## 0.9.61 (unreleased)
- Fix: `graphify.serve` now imports cleanly on Python 3.12 and 3.13. The `chinese` extra pins `jieba-py` from 3.12 onward (0.9.60 mistakenly kept the old `jieba` until 3.14, and its invalid regex escapes are a hard error on 3.12+), and the jieba import now suppresses the tokenizer's `SyntaxWarning` regardless of message or line so it never escalates under `-W error`.
- Fix: the git hook's rebuild-root guard now rejects a symlink-loop or dangling `.graphify_root` on Python 3.13, whose `Path.resolve()` no longer raises on a loop — the saved root must resolve to a real directory inside the repo before it is adopted.
## 0.9.60 (2026-09-12)
- Fix: atomic writes now fall back correctly on Windows `WinError 17` (cannot move to a different drive), not just `PermissionError` — a shared `os_replace_with_fallback` copies through a temp in the target directory and restores the original if the swap fails, keeping install/export/cache writes crash-safe (#3508, thanks @ayushcodes10).
- Fix: files that could not be classified into any language or type are now surfaced (a count and top extensions in the console and GRAPH_REPORT) instead of vanishing from a "successful" run; noise and ignored paths are unaffected (#3511, thanks @ayushcodes10).
+8 -2
View File
@@ -192,7 +192,10 @@ try:
try:
_cwd = Path.cwd().resolve()
_resolved = _candidate.resolve()
_in_repo = _resolved == _cwd or _cwd in _resolved.parents
# Python 3.13 no longer raises on a symlink loop (resolve returns
# the path unresolved), so require a real directory: a loop or a
# dangling target is not a dir and correctly falls back.
_in_repo = (_resolved == _cwd or _cwd in _resolved.parents) and _resolved.is_dir()
except (OSError, RuntimeError):
_in_repo = False
if _in_repo:
@@ -264,7 +267,10 @@ try:
try:
_cwd = Path.cwd().resolve()
_resolved = _candidate.resolve()
_in_repo = _resolved == _cwd or _cwd in _resolved.parents
# Python 3.13 no longer raises on a symlink loop (resolve returns
# the path unresolved), so require a real directory: a loop or a
# dangling target is not a dir and correctly falls back.
_in_repo = (_resolved == _cwd or _cwd in _resolved.parents) and _resolved.is_dir()
except (OSError, RuntimeError):
_in_repo = False
if _in_repo:
+8 -7
View File
@@ -19,14 +19,15 @@ from graphify.paths import default_graph_json as _default_graph_json
try:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
message=r'^"\\\." is an invalid escape sequence\.',
category=SyntaxWarning,
lineno=7,
)
# jieba/jieba-py carry invalid regex escapes that emit a SyntaxWarning on
# compile; the message and line moved across Python versions (3.12 also
# reworded it), so ignore the category wholesale here rather than pinning
# a message/lineno. Under `-W error::SyntaxWarning` this keeps the import
# from escalating to a fatal SyntaxError.
warnings.simplefilter("ignore", SyntaxWarning)
import jieba as _jieba # type: ignore[import-untyped]
except ImportError:
except (ImportError, SyntaxError):
# A stray/broken jieba must never take down the whole server import.
_jieba = None
+4 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "graphifyy"
version = "0.9.60"
version = "0.9.61"
description = "AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
readme = "README.md"
license = "Apache-2.0"
@@ -81,8 +81,8 @@ anthropic = ["anthropic"]
gemini = ["openai", "tiktoken"]
openai = ["openai", "tiktoken"]
chinese = [
"jieba; python_version < '3.14'",
"jieba-py>=0.46.12,<1; python_version >= '3.14'",
"jieba; python_version < '3.12'",
"jieba-py>=0.46.12,<1; python_version >= '3.12'",
]
sql = ["tree-sitter-sql"]
# extract_pascal() uses tree-sitter-pascal for AST-quality extraction (more
@@ -105,7 +105,7 @@ commonlisp = ["tree-sitter-commonlisp"]
# every platform, no C toolchain; optional because Robot Framework corpora are
# QA-automation specific.
robot = ["robotframework>=4.0"]
all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.16.1", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "graspologic-native>=1.3.1,<2; python_version >= '3.13'", "python-docx", "openpyxl", "psycopg[binary]", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.7.4", "matplotlib", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba; python_version < '3.14'", "jieba-py>=0.46.12,<1; python_version >= '3.14'", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal", "tree-sitter-ocaml", "tree-sitter-commonlisp", "robotframework>=4.0"]
all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.16.1", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "graspologic-native>=1.3.1,<2; python_version >= '3.13'", "python-docx", "openpyxl", "psycopg[binary]", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.7.4", "matplotlib", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba; python_version < '3.12'", "jieba-py>=0.46.12,<1; python_version >= '3.12'", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal", "tree-sitter-ocaml", "tree-sitter-commonlisp", "robotframework>=4.0"]
[project.scripts]
graphify = "graphify.__main__:main"
+1 -1
View File
@@ -46,7 +46,7 @@ def test_version_gated_optional_backends_are_packaged():
for extra in ("leiden", "all"):
assert "graspologic-native>=1.3.1,<2; python_version >= '3.13'" in extras[extra]
for extra in ("chinese", "all"):
assert "jieba-py>=0.46.12,<1; python_version >= '3.14'" in extras[extra]
assert "jieba-py>=0.46.12,<1; python_version >= '3.12'" in extras[extra]
def test_backend_pkg_hint_points_at_uv_tool_and_extra():
Generated
+57 -33
View File
@@ -6,11 +6,14 @@ resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version < '3.11'",
]
@@ -548,11 +551,14 @@ resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" },
@@ -1090,7 +1096,7 @@ wheels = [
[[package]]
name = "graphifyy"
version = "0.9.59"
version = "0.9.61"
source = { editable = "." }
dependencies = [
{ name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
@@ -1135,8 +1141,8 @@ all = [
{ name = "faster-whisper", marker = "python_full_version >= '3.11'" },
{ name = "graspologic", marker = "python_full_version < '3.13'" },
{ name = "graspologic-native", version = "1.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
{ name = "jieba", marker = "python_full_version < '3.14'" },
{ name = "jieba-py", marker = "python_full_version >= '3.14'" },
{ name = "jieba", marker = "python_full_version < '3.12'" },
{ name = "jieba-py", marker = "python_full_version >= '3.12'" },
{ name = "markdownify" },
{ name = "matplotlib" },
{ name = "mcp" },
@@ -1166,8 +1172,8 @@ bedrock = [
{ name = "boto3" },
]
chinese = [
{ name = "jieba", marker = "python_full_version < '3.14'" },
{ name = "jieba-py", marker = "python_full_version >= '3.14'" },
{ name = "jieba", marker = "python_full_version < '3.12'" },
{ name = "jieba-py", marker = "python_full_version >= '3.12'" },
]
commonlisp = [
{ name = "tree-sitter-commonlisp" },
@@ -1281,10 +1287,10 @@ requires-dist = [
{ name = "graspologic", marker = "python_full_version < '3.13' and extra == 'leiden'" },
{ name = "graspologic-native", marker = "python_full_version >= '3.13' and extra == 'all'", specifier = ">=1.3.1,<2" },
{ name = "graspologic-native", marker = "python_full_version >= '3.13' and extra == 'leiden'", specifier = ">=1.3.1,<2" },
{ name = "jieba", marker = "python_full_version < '3.14' and extra == 'all'" },
{ name = "jieba", marker = "python_full_version < '3.14' and extra == 'chinese'" },
{ name = "jieba-py", marker = "python_full_version >= '3.14' and extra == 'all'", specifier = ">=0.46.12,<1" },
{ name = "jieba-py", marker = "python_full_version >= '3.14' and extra == 'chinese'", specifier = ">=0.46.12,<1" },
{ name = "jieba", marker = "python_full_version < '3.12' and extra == 'all'" },
{ name = "jieba", marker = "python_full_version < '3.12' and extra == 'chinese'" },
{ name = "jieba-py", marker = "python_full_version >= '3.12' and extra == 'all'", specifier = ">=0.46.12,<1" },
{ name = "jieba-py", marker = "python_full_version >= '3.12' and extra == 'chinese'", specifier = ">=0.46.12,<1" },
{ name = "markdownify", marker = "extra == 'all'" },
{ name = "markdownify", marker = "extra == 'pdf'" },
{ name = "matplotlib", marker = "extra == 'all'" },
@@ -1424,9 +1430,12 @@ name = "graspologic-native"
version = "1.2.5"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/25/2d/62b30d89533643ccf4778a18eb023f291b8877b5d85de3342f07b2d363a7/graspologic_native-1.2.5.tar.gz", hash = "sha256:27ea7e01fa44466c0b4cdd678d4561e5d3dc0cb400015683b7ae1386031257a0", size = 2512729, upload-time = "2025-04-02T19:34:22.961Z" }
@@ -2326,11 +2335,14 @@ resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
wheels = [
@@ -2393,9 +2405,12 @@ name = "numpy"
version = "1.26.4"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" }
@@ -2669,9 +2684,12 @@ name = "pandas"
version = "3.0.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" },
@@ -4001,9 +4019,12 @@ name = "scikit-learn"
version = "1.8.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "joblib", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" },
@@ -4119,11 +4140,14 @@ resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version == '3.12.*' and sys_platform == 'win32'",
"python_full_version == '3.11.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'emscripten'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' and sys_platform == 'emscripten'",
"python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" },