mirror of
https://github.com/VectifyAI/PageIndex.git
synced 2026-07-24 21:41:04 +02:00
fix: restore 0.2.x config resolution and legacy submodule attribute access
page_index() and ConfigLoader silently dropped config.yaml after the refactor: defaults came from IndexConfig's hardcoded fields, flipping if_add_doc_description on (an extra billed LLM call per document) and discarding user-edited YAML including a custom default_path. Both now resolve explicit args > YAML > IndexConfig field defaults via IndexConfig.from_yaml, matching the CLI. The new SDK keeps its pure-code config path. import pageindex also lost the utils / page_index_md submodule attributes (pageindex.utils.print_tree raised AttributeError). A module __getattr__ now imports the shims lazily, so plain imports stay free of deprecation warnings while first use of a legacy attribute binds the module and warns. Also trims non-essential comments from the BASE_URL fix. Claude-Session: https://claude.ai/code/session_014B4HZkjdSiZXDmJtH5Jexn
This commit is contained in:
parent
dea211b4a2
commit
0f593c65d9
7 changed files with 82 additions and 15 deletions
|
|
@ -74,3 +74,12 @@ __all__ = [
|
|||
"get_document_structure",
|
||||
"get_page_content",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
# Lazy so plain `import pageindex` never trips the shims' deprecation
|
||||
# warnings; they fire only when the legacy attribute is actually used.
|
||||
if name in ("utils", "page_index_md"):
|
||||
import importlib
|
||||
return importlib.import_module(f".{name}", __name__)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ class CloudBackend:
|
|||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
# A callable is resolved per request so reassigning client.BASE_URL
|
||||
# after construction takes effect, matching 0.2.x call-time semantics.
|
||||
return self._base_url() if callable(self._base_url) else self._base_url
|
||||
|
||||
# ── HTTP helpers ──────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ class PageIndexClient:
|
|||
def _init_cloud(self, api_key: str):
|
||||
from .backend.cloud import CloudBackend
|
||||
from .cloud_api import LegacyCloudAPI
|
||||
# Pass a callable so BASE_URL is re-read on every request — 0.2.x
|
||||
# allowed reassigning client.BASE_URL after construction.
|
||||
# Callable: re-read per request so post-construction BASE_URL
|
||||
# reassignment (a 0.2.x pattern) still applies.
|
||||
base_url = lambda: self.BASE_URL
|
||||
self._backend = CloudBackend(api_key=api_key, base_url=base_url)
|
||||
self._legacy_cloud_api = LegacyCloudAPI(api_key=api_key, base_url=base_url)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ class LegacyCloudAPI:
|
|||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
# A callable is resolved per request so reassigning client.BASE_URL
|
||||
# after construction takes effect, matching 0.2.x call-time semantics.
|
||||
return self._base_url() if callable(self._base_url) else self._base_url
|
||||
|
||||
@base_url.setter
|
||||
|
|
|
|||
|
|
@ -1314,7 +1314,7 @@ def page_index(doc, model=None, toc_check_page_num=None, max_page_num_each_node=
|
|||
"if_add_node_text": if_add_node_text,
|
||||
}
|
||||
user_opt = {k: v for k, v in user_opt.items() if v is not None}
|
||||
opt = IndexConfig(**user_opt)
|
||||
opt = IndexConfig.from_yaml(**user_opt)
|
||||
return page_index_main(doc, opt)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -953,14 +953,14 @@ def _coerce_bool(value):
|
|||
|
||||
|
||||
class ConfigLoader:
|
||||
"""Legacy 0.2.x config helper. Defaults now come from IndexConfig; this
|
||||
class no longer reads the packaged ``config.yaml`` (the CLI still uses it
|
||||
via ``IndexConfig.from_yaml``). Prefer ``pageindex.IndexConfig``.
|
||||
"""Legacy 0.2.x config helper. Defaults come from ``default_path`` (or the
|
||||
packaged ``config.yaml``), with IndexConfig field defaults filling any keys
|
||||
the YAML omits. Prefer ``pageindex.IndexConfig`` in new code.
|
||||
"""
|
||||
|
||||
def __init__(self, default_path=None):
|
||||
from ..config import IndexConfig
|
||||
self._default_dict = IndexConfig().model_dump()
|
||||
self._default_dict = IndexConfig.from_yaml(default_path).model_dump()
|
||||
|
||||
def _validate_keys(self, user_dict):
|
||||
unknown_keys = set(user_dict) - set(self._default_dict)
|
||||
|
|
@ -968,7 +968,7 @@ class ConfigLoader:
|
|||
raise ValueError(f"Unknown config keys: {unknown_keys}")
|
||||
|
||||
def load(self, user_opt=None) -> _config:
|
||||
"""Merge user options over IndexConfig defaults, returning a namespace."""
|
||||
"""Merge user options over the YAML defaults, returning a namespace."""
|
||||
if user_opt is None:
|
||||
user_dict = {}
|
||||
elif isinstance(user_opt, _config):
|
||||
|
|
|
|||
|
|
@ -62,16 +62,78 @@ def test_get_leaf_nodes_has_331_fix():
|
|||
assert leaves == [{"title": "Leaf", "start_index": 1, "end_index": 2}]
|
||||
|
||||
|
||||
def test_configloader_no_longer_needs_config_yaml():
|
||||
"""ConfigLoader must build defaults from IndexConfig, not read config.yaml."""
|
||||
def test_configloader_defaults_come_from_packaged_yaml():
|
||||
"""ConfigLoader must read the packaged config.yaml as its defaults, like
|
||||
0.2.x — notably if_add_doc_description ships as "no" there, while the
|
||||
IndexConfig field default is True (the new-SDK default)."""
|
||||
from pageindex.index.utils import ConfigLoader
|
||||
cfg = ConfigLoader().load({"model": "gpt-5.4"})
|
||||
assert cfg.model == "gpt-5.4"
|
||||
assert cfg.if_add_node_summary is True # IndexConfig default
|
||||
assert cfg.if_add_node_summary is True # config.yaml: "yes"
|
||||
assert cfg.if_add_doc_description is False # config.yaml: "no"
|
||||
with pytest.raises(ValueError, match="Unknown config keys"):
|
||||
ConfigLoader().load({"nope": 1})
|
||||
|
||||
|
||||
def test_configloader_reads_custom_yaml_path(tmp_path):
|
||||
"""A custom default_path must be honored; keys the YAML omits fall back to
|
||||
IndexConfig field defaults."""
|
||||
from pageindex.index.utils import ConfigLoader
|
||||
custom = tmp_path / "my.yaml"
|
||||
custom.write_text('model: "my-model"\nif_add_node_summary: "no"\n')
|
||||
cfg = ConfigLoader(str(custom)).load()
|
||||
assert cfg.model == "my-model"
|
||||
assert cfg.if_add_node_summary is False
|
||||
assert cfg.if_add_node_id is True # omitted -> IndexConfig default
|
||||
|
||||
|
||||
def test_configloader_missing_custom_yaml_raises(tmp_path):
|
||||
from pageindex.index.utils import ConfigLoader
|
||||
with pytest.raises(FileNotFoundError):
|
||||
ConfigLoader(str(tmp_path / "nope.yaml"))
|
||||
|
||||
|
||||
def test_legacy_submodule_attrs_lazy_bound():
|
||||
"""Shim warnings fire on first attribute use, never at package import.
|
||||
Subprocess: in-process the attrs may already be bound by other tests."""
|
||||
import subprocess
|
||||
import sys
|
||||
code = (
|
||||
"import warnings\n"
|
||||
"with warnings.catch_warnings(record=True) as w:\n"
|
||||
" warnings.simplefilter('always')\n"
|
||||
" import pageindex\n"
|
||||
"assert not any('has moved' in str(x.message) for x in w), 'import warned'\n"
|
||||
"with warnings.catch_warnings(record=True) as w:\n"
|
||||
" warnings.simplefilter('always')\n"
|
||||
" assert callable(pageindex.utils.print_tree)\n"
|
||||
"assert any('pageindex.utils has moved' in str(x.message) for x in w)\n"
|
||||
"assert callable(pageindex.page_index_md.md_to_tree)\n"
|
||||
)
|
||||
result = subprocess.run([sys.executable, "-c", code],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_unknown_package_attr_still_raises():
|
||||
import pageindex
|
||||
with pytest.raises(AttributeError, match="no attribute 'definitely_not_real'"):
|
||||
pageindex.definitely_not_real
|
||||
|
||||
|
||||
def test_page_index_defaults_follow_config_yaml(monkeypatch):
|
||||
"""page_index() resolution order: explicit args > config.yaml > IndexConfig
|
||||
field defaults (the 0.2.x contract)."""
|
||||
import pageindex.index.page_index as pi
|
||||
captured = {}
|
||||
monkeypatch.setattr(pi, "page_index_main",
|
||||
lambda doc, opt: captured.setdefault("opt", opt))
|
||||
pi.page_index("dummy.pdf", model="my-model")
|
||||
opt = captured["opt"]
|
||||
assert opt.model == "my-model" # explicit arg wins
|
||||
assert opt.if_add_doc_description is False # config.yaml "no", not True
|
||||
|
||||
|
||||
def test_configloader_coerces_legacy_yes_no_strings():
|
||||
"""A legacy caller passing 'no' must get a real False, not a truthy
|
||||
string — page_index_main's `if opt.if_add_node_summary:` checks (bare
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue