mirror of
https://github.com/VectifyAI/PageIndex.git
synced 2026-07-24 21:41:04 +02:00
fix: three cloud backend correctness issues
- _require_document: always verify doc existence via _get_metadata, even when folders are unavailable — previously a non-existent doc_id silently passed through to the chat API on non-Max plans - get_document: forward pageNum from the server metadata as page_count, matching the local backend's DocumentDetail contract - _normalize_nodes: preserve prefix_summary as a separate field instead of collapsing it into summary, matching the server's leaf/non-leaf distinction
This commit is contained in:
parent
1ac7e13cd9
commit
360ab662a6
1 changed files with 19 additions and 20 deletions
|
|
@ -6,7 +6,6 @@ API reference: https://github.com/VectifyAI/pageindex_sdk
|
|||
from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import urllib.parse
|
||||
import requests
|
||||
|
|
@ -17,6 +16,7 @@ from ..errors import (AUTH_HINT, CloudAPIError, CollectionAlreadyExistsError,
|
|||
CollectionNotFoundError, DocumentNotFoundError,
|
||||
PageIndexError)
|
||||
from ..events import QueryEvent
|
||||
from .._validation import validate_collection_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -110,13 +110,7 @@ class CloudBackend:
|
|||
|
||||
@staticmethod
|
||||
def _validate_collection_name(name: str) -> None:
|
||||
# .fullmatch() (not .match()): a $-anchored .match() would accept a
|
||||
# trailing newline ("papers\n") because $ matches just before a final \n.
|
||||
if not re.fullmatch(r'[a-zA-Z0-9_-]{1,128}', name):
|
||||
raise PageIndexError(
|
||||
f"Invalid collection name: {name!r}. "
|
||||
"Must be 1-128 chars of [a-zA-Z0-9_-]."
|
||||
)
|
||||
validate_collection_name(name)
|
||||
|
||||
@staticmethod
|
||||
def _enc(value: str) -> str:
|
||||
|
|
@ -210,6 +204,7 @@ class CloudBackend:
|
|||
return [f["name"] for f in folders]
|
||||
|
||||
def delete_collection(self, name: str) -> None:
|
||||
self._validate_collection_name(name)
|
||||
try:
|
||||
folder_id = self._get_folder_id(name)
|
||||
except CollectionNotFoundError:
|
||||
|
|
@ -264,15 +259,14 @@ class CloudBackend:
|
|||
def _get_metadata(self, doc_id: str) -> dict:
|
||||
return self._doc_request(doc_id, "GET", f"/doc/{self._enc(doc_id)}/metadata/")
|
||||
|
||||
def _require_document(self, collection: str, doc_id: str) -> dict | None:
|
||||
def _require_document(self, collection: str, doc_id: str) -> dict:
|
||||
"""Membership guard (the server's doc endpoints are user-scoped, not
|
||||
folder-scoped, so this is checked client-side). Returns the doc's
|
||||
metadata, or None when folders are unavailable on this plan."""
|
||||
folder-scoped, so this is checked client-side). Always verifies the
|
||||
document exists (raises DocumentNotFoundError on 404); when folders
|
||||
are available, also checks that the doc belongs to this collection."""
|
||||
folder_id = self._get_folder_id(collection)
|
||||
if folder_id is None:
|
||||
return None
|
||||
meta = self._get_metadata(doc_id)
|
||||
if meta.get("folderId") != folder_id:
|
||||
if folder_id is not None and meta.get("folderId") != folder_id:
|
||||
raise DocumentNotFoundError(
|
||||
f"Document {doc_id} not found in collection '{collection}'"
|
||||
)
|
||||
|
|
@ -293,23 +287,25 @@ class CloudBackend:
|
|||
stacklevel=3,
|
||||
)
|
||||
resp = self._require_document(collection, doc_id)
|
||||
if resp is None:
|
||||
resp = self._get_metadata(doc_id)
|
||||
# Fetch structure in the same call via tree endpoint
|
||||
tree_resp = self._doc_request(doc_id, "GET", f"/doc/{self._enc(doc_id)}/",
|
||||
params={"type": "tree", "summary": "true"})
|
||||
raw_tree = tree_resp.get("result", [])
|
||||
return {
|
||||
page_num = _as_int(resp.get("pageNum"))
|
||||
result = {
|
||||
"doc_id": resp.get("id", doc_id),
|
||||
"doc_name": resp.get("name", ""),
|
||||
"doc_description": resp.get("description", ""),
|
||||
"doc_type": "pdf",
|
||||
"status": resp.get("status", ""),
|
||||
"structure": self._normalize_tree(raw_tree, max_page=resp.get("pageNum") or None),
|
||||
"structure": self._normalize_tree(raw_tree, max_page=page_num),
|
||||
}
|
||||
if page_num is not None:
|
||||
result["page_count"] = page_num
|
||||
return result
|
||||
|
||||
def get_document_structure(self, collection: str, doc_id: str) -> list:
|
||||
meta = self._require_document(collection, doc_id) or self._get_metadata(doc_id)
|
||||
meta = self._require_document(collection, doc_id)
|
||||
resp = self._doc_request(doc_id, "GET", f"/doc/{self._enc(doc_id)}/",
|
||||
params={"type": "tree", "summary": "true"})
|
||||
raw_tree = resp.get("result", [])
|
||||
|
|
@ -365,10 +361,13 @@ class CloudBackend:
|
|||
normalized = {
|
||||
"title": node.get("title", ""),
|
||||
"node_id": node.get("node_id", ""),
|
||||
"summary": node.get("summary", node.get("prefix_summary", "")),
|
||||
"start_index": node.get("page_index"),
|
||||
"end_index": None,
|
||||
}
|
||||
if "summary" in node:
|
||||
normalized["summary"] = node["summary"]
|
||||
if "prefix_summary" in node:
|
||||
normalized["prefix_summary"] = node["prefix_summary"]
|
||||
if "text" in node:
|
||||
normalized["text"] = node["text"]
|
||||
children = node.get("nodes", [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue