From 755d79f89ecdb19a1582c0c10f14a2e87274aa88 Mon Sep 17 00:00:00 2001 From: zTgx <747674262@qq.com> Date: Wed, 29 Apr 2026 18:12:10 +0800 Subject: [PATCH 1/2] feat(py): add Python 3.11 ABI support and simplify error handling - Add abi3-py311 feature to pyo3 dependency in Cargo.toml to enable Python 3.11 compatibility - Update pyproject.toml to include pyo3/abi3-py311 feature flag - Replace custom VectorlessError implementation with pyo3's create_exception! macro for simpler error handling - Simplify error conversion logic in error.rs by removing manual exception class implementation --- Cargo.toml | 2 +- crates/vectorless-py/src/error.rs | 61 ++----------------------------- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b99e0fe..a8778a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ rand = "0.8" bm25 = { version = "2.3.2", features = ["parallelism"] } # Python bindings -pyo3 = { version = "0.28", features = ["extension-module"] } +pyo3 = { version = "0.28", features = ["extension-module", "abi3-py311"] } pyo3-async-runtimes = { version = "0.28", features = ["tokio-runtime"] } # Dev dependencies diff --git a/crates/vectorless-py/src/error.rs b/crates/vectorless-py/src/error.rs index 9f59cc4..1e22106 100644 --- a/crates/vectorless-py/src/error.rs +++ b/crates/vectorless-py/src/error.rs @@ -3,69 +3,14 @@ //! Python exception types and error conversion. -use pyo3::exceptions::PyException; +use pyo3::create_exception; use pyo3::prelude::*; use ::vectorless_engine::Error as RustError; -/// Python exception for vectorless errors. -#[pyclass(extends = PyException, subclass, skip_from_py_object)] -pub struct VectorlessError { - message: String, - kind: String, -} - -#[pymethods] -impl VectorlessError { - #[new] - fn new_py(message: String, kind: String) -> Self { - Self { message, kind } - } - - #[getter] - fn message(&self) -> &str { - &self.message - } - - #[getter] - fn kind(&self) -> &str { - &self.kind - } - - fn __str__(&self) -> &str { - &self.message - } - - fn __repr__(&self) -> String { - format!("VectorlessError('{}', kind='{}')", self.message, self.kind) - } -} - -impl VectorlessError { - pub fn new(message: String, kind: &str) -> Self { - Self { - message, - kind: kind.to_string(), - } - } -} - -impl From for PyErr { - fn from(err: VectorlessError) -> PyErr { - PyErr::new::((err.message, err.kind)) - } -} +create_exception!(vectorless, VectorlessError, pyo3::exceptions::PyException); /// Convert vectorless errors to Python exceptions. pub fn to_py_err(e: RustError) -> PyErr { - let message = e.to_string(); - let kind = match &e { - RustError::DocumentNotFound(_) => "not_found", - RustError::Parse(_) => "parse", - RustError::Config(_) => "config", - RustError::Workspace(_) => "workspace", - RustError::Llm(_) => "llm", - _ => "unknown", - }; - VectorlessError::new(message, kind).into() + VectorlessError::new_err(e.to_string()) } diff --git a/pyproject.toml b/pyproject.toml index 7c69925..da14e5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ Repository = "https://github.com/vectorlessflow/vectorless" python-source = "." module-name = "vectorless._vectorless" manifest-path = "crates/vectorless-py/Cargo.toml" -features = ["pyo3/extension-module"] +features = ["pyo3/extension-module", "pyo3/abi3-py311"] [tool.pytest.ini_options] asyncio_mode = "auto" From 333711a5ab6b6b66051849b8b8796b3f43fb36d2 Mon Sep 17 00:00:00 2001 From: zTgx <747674262@qq.com> Date: Wed, 29 Apr 2026 18:19:24 +0800 Subject: [PATCH 2/2] chore(release): bump version to 0.1.15 - Update workspace package version from 0.1.14 to 0.1.15 - Add release notes for version 0.15 in HISTORY.md - Enable PyO3 abi3 for cross-version Python compatibility - Simplify exception handling by using PyRuntimeError instead of custom VectorlessError - Remove unnecessary error module registration --- Cargo.toml | 2 +- HISTORY.md | 6 ++++++ crates/vectorless-py/src/document.rs | 17 ++++++++--------- crates/vectorless-py/src/engine.rs | 17 ++++------------- crates/vectorless-py/src/error.rs | 6 ++---- crates/vectorless-py/src/lib.rs | 3 +-- 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8778a2..5e0f29c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.14" +version = "0.1.15" description = "Knowing by reasoning, not vectors." edition = "2024" authors = ["zTgx "] diff --git a/HISTORY.md b/HISTORY.md index 875283f..4d52f82 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # HISTORY +## 0.1.15 (2026-04-29) + +- **Python bindings**: enabled PyO3 abi3 for cross-version compatibility +- Single wheel now supports Python 3.11, 3.12, and 3.13+ +- Simplified exception type using `create_exception!` macro + ## 0.1.14 (2026-04-29) - Homepage redesign with product showcase and improved UX diff --git a/crates/vectorless-py/src/document.rs b/crates/vectorless-py/src/document.rs index 5c7d53f..1145893 100644 --- a/crates/vectorless-py/src/document.rs +++ b/crates/vectorless-py/src/document.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use pyo3::prelude::*; +use pyo3::exceptions::PyRuntimeError; use pyo3_async_runtimes::tokio::future_into_py; use tokio::sync::Mutex; @@ -15,8 +16,6 @@ use vectorless_primitives::{ SectionCardInfo, SectionSummaryInfo, SimilarResult, TocEntry, TopicEntryInfo, WordCount, }; -use super::error::VectorlessError; - // ========================================================================= // PyDocumentInfo (existing — returned by compile) // ========================================================================= @@ -126,7 +125,7 @@ fn id_to_str(id: u64) -> String { } fn to_py_err(e: impl std::fmt::Display) -> PyErr { - PyErr::from(VectorlessError::new(e.to_string(), "navigation")) + PyRuntimeError::new_err(e.to_string()) } #[pymethods] @@ -382,10 +381,10 @@ impl PyDocument { let num = node_id .strip_prefix('n') .ok_or_else(|| { - VectorlessError::new("NodeId must start with 'n'".to_string(), "navigation") + PyRuntimeError::new_err("NodeId must start with 'n'") })? .parse::() - .map_err(|_| VectorlessError::new("Invalid NodeId".to_string(), "navigation"))?; + .map_err(|_| PyRuntimeError::new_err("Invalid NodeId"))?; let nav = nav.lock().await; Ok(nav .chains_for(num) @@ -403,10 +402,10 @@ impl PyDocument { let num = node_id .strip_prefix('n') .ok_or_else(|| { - VectorlessError::new("NodeId must start with 'n'".to_string(), "navigation") + PyRuntimeError::new_err("NodeId must start with 'n'") })? .parse::() - .map_err(|_| VectorlessError::new("Invalid NodeId".to_string(), "navigation"))?; + .map_err(|_| PyRuntimeError::new_err("Invalid NodeId"))?; let nav = nav.lock().await; Ok(nav .overlaps_for(num) @@ -424,10 +423,10 @@ impl PyDocument { let num = node_id .strip_prefix('n') .ok_or_else(|| { - VectorlessError::new("NodeId must start with 'n'".to_string(), "navigation") + PyRuntimeError::new_err("NodeId must start with 'n'") })? .parse::() - .map_err(|_| VectorlessError::new("Invalid NodeId".to_string(), "navigation"))?; + .map_err(|_| PyRuntimeError::new_err("Invalid NodeId"))?; let nav = nav.lock().await; Ok(nav.evidence_score_for(num).await.map(PyEvidenceScore::from)) }) diff --git a/crates/vectorless-py/src/engine.rs b/crates/vectorless-py/src/engine.rs index 49ef759..229016b 100644 --- a/crates/vectorless-py/src/engine.rs +++ b/crates/vectorless-py/src/engine.rs @@ -4,6 +4,7 @@ //! Engine Python wrapper — async compile/forget/list_documents. use pyo3::prelude::*; +use pyo3::exceptions::PyRuntimeError; use pyo3_async_runtimes::tokio::future_into_py; use std::sync::Arc; use tokio::runtime::Runtime; @@ -11,7 +12,6 @@ use tokio::runtime::Runtime; use ::vectorless_engine::{Engine, EngineBuilder, IngestInput, RawNodeInput}; use super::document::{PyDocument, PyDocumentInfo}; -use super::error::VectorlessError; use super::error::to_py_err; use super::graph::PyDocumentGraph; use super::metrics::PyMetricsReport; @@ -73,10 +73,7 @@ async fn run_load_document(engine: Arc, doc_id: String) -> PyResult Err(PyErr::from(VectorlessError::new( - format!("Document not found: {doc_id}"), - "navigation", - ))), + None => Err(PyRuntimeError::new_err(format!("Document not found: {doc_id}"))), } } @@ -144,10 +141,7 @@ impl PyEngine { config: Option>, ) -> PyResult { let rt = Runtime::new().map_err(|e| { - PyErr::from(VectorlessError::new( - format!("Failed to create tokio runtime: {}", e), - "config", - )) + PyRuntimeError::new_err(format!("Failed to create tokio runtime: {}", e)) })?; let rust_config = config.map(|c| c.inner.clone()); @@ -173,10 +167,7 @@ impl PyEngine { }); let engine = engine.map_err(|e| { - PyErr::from(VectorlessError::new( - format!("Failed to create engine: {}", e), - "config", - )) + PyRuntimeError::new_err(format!("Failed to create engine: {}", e)) })?; Ok(Self { diff --git a/crates/vectorless-py/src/error.rs b/crates/vectorless-py/src/error.rs index 1e22106..7714ec5 100644 --- a/crates/vectorless-py/src/error.rs +++ b/crates/vectorless-py/src/error.rs @@ -3,14 +3,12 @@ //! Python exception types and error conversion. -use pyo3::create_exception; +use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use ::vectorless_engine::Error as RustError; -create_exception!(vectorless, VectorlessError, pyo3::exceptions::PyException); - /// Convert vectorless errors to Python exceptions. pub fn to_py_err(e: RustError) -> PyErr { - VectorlessError::new_err(e.to_string()) + PyRuntimeError::new_err(e.to_string()) } diff --git a/crates/vectorless-py/src/lib.rs b/crates/vectorless-py/src/lib.rs index 09437f9..5ef7957 100644 --- a/crates/vectorless-py/src/lib.rs +++ b/crates/vectorless-py/src/lib.rs @@ -20,7 +20,6 @@ use document::{ PyTocEntry, PyTopicEntry, PyWordCount, }; use engine::PyEngine; -use error::VectorlessError; use graph::{PyDocumentGraph, PyDocumentGraphNode, PyEdgeEvidence, PyGraphEdge, PyWeightedKeyword}; use metrics::{PyLlmMetricsReport, PyMetricsReport, PyRetrievalMetricsReport}; @@ -39,7 +38,7 @@ fn _vectorless(m: &Bound<'_, PyModule>) -> PyResult<()> { .init(); }); - m.add_class::()?; + // VectorlessError is just PyRuntimeError, no need to register m.add_class::()?; m.add_class::()?; m.add_class::()?;