Summary
Collecting rag/flow/tests/ and test/unit_test/rag/ together in a single pytest invocation corrupts the cached deepdoc.parser package module in sys.modules, which then makes every from deepdoc.parser import X fail with a cascading ImportError: cannot import name 'PlainParser' from 'deepdoc.parser.pdf_parser' (unknown location).
This is not a deepdoc native-build problem and not a bug in any single test file. Each suite collects cleanly on its own.
Reproduction
Each suite alone is green:
python -m pytest rag/flow/tests/ --collect-only -q # 12 tests, OK
python -m pytest test/unit_test/rag/test_naive_merge.py --collect-only -q # 23 tests, OK
Combined collection fails:
python -m pytest rag/flow/tests/test_token_chunker_tag_overlap.py test/unit_test/rag/test_naive_merge.py --collect-only -q
# ERROR collecting rag/flow/tests/test_token_chunker_tag_overlap.py
# ImportError: cannot import name 'PlainParser' from 'deepdoc.parser.pdf_parser' (unknown location)
The failure is order-independent (swapping the two args still fails) and survives --import-mode=importlib.
Root cause
PlainParser is only a symptom. At the moment of failure, sys.modules shows:
deepdoc.parser.pdf_parser cached: __file__=None __path__=None hasPlainParser=False
deepdoc.parser.__path__=None # the deepdoc.parser package module itself is broken/partial
deepdoc.__path__=['/home/.../ragflow/deepdoc'] # correct
deepdoc.parser.pdf_parser has __file__=None and its SourceFileLoader.exec_module is never invoked, so the real deepdoc/parser/pdf_parser.py is never executed — it was cached as a namespace/partial module. Any later from deepdoc.parser import ... then fails against this poisoned cache, and the error chain cascades into docs_generator, invoke, rag.flow.*, etc.
The trigger is structural to the test layout:
test/unit_test/rag/ and rag/flow/tests/ are not packages (no __init__.py). pytest's default prepend import mode puts each of these directories on sys.path[0].
test/unit_test/rag is itself named rag, and test/unit_test/ contains a deepdoc/ directory (test/unit_test/deepdoc/parser/...).
- When both suites are imported in one process — the flow suite via
rag.flow.parser.pdf_chunk_metadata → api.db.services → deepdoc.parser, and the test/unit_test suite via rag.nlp and its conftest — deepdoc / deepdoc.parser resolution hits a namespace / partial-module cache collision and deepdoc.parser ends up cached as a non-package broken module.
Note: CPython 3.13 executes most of the import machinery in C, bypassing builtins.__import__, module_from_spec, and SourceFileLoader.exec_module, so the precise first corruption event cannot be observed via Python-level import hooks; the sys.modules end-state above plus the failure conditions are sufficient to localize the mechanism.
Independent verification that deepdoc is fine
python -c "import deepdoc.parser.pdf_parser as m; print(m.__file__)" # OK
python -c "from api.db.services.task_service import TaskService; print('ok')" # OK
# pure-python: import rag.nlp then import rag.flow.chunker.token_chunker -> OK
Suggested fixes (out of scope for this issue)
- Decouple the import path:
rag/flow/parser/pdf_chunk_metadata.py does from api.db.services.file_service import FileService at module top level, which pulls in the entire api.db stack (and thus deepdoc.parser) whenever the chunker package is imported. Moving that heavy import behind a function / lazy import removes the chunker from the deepdoc import chain.
- Make test directories real packages (add
__init__.py to test/unit_test/rag, rag/flow/tests, etc.) so pytest resolves them unambiguously instead of as sys.path roots / namespace portions.
- Keep the two suites in separate pytest invocations in CI as a stopgap (each already collects green alone).
Workaround for the affected test
rag/flow/tests/test_token_chunker_tag_overlap.py collects and passes cleanly on its own (and will in CI once deepdoc is built). It only breaks when collected in the same process as test/unit_test/rag/. No code change is required in that test.
Summary
Collecting
rag/flow/tests/andtest/unit_test/rag/together in a single pytest invocation corrupts the cacheddeepdoc.parserpackage module insys.modules, which then makes everyfrom deepdoc.parser import Xfail with a cascadingImportError: cannot import name 'PlainParser' from 'deepdoc.parser.pdf_parser' (unknown location).This is not a
deepdocnative-build problem and not a bug in any single test file. Each suite collects cleanly on its own.Reproduction
Each suite alone is green:
Combined collection fails:
The failure is order-independent (swapping the two args still fails) and survives
--import-mode=importlib.Root cause
PlainParseris only a symptom. At the moment of failure,sys.modulesshows:deepdoc.parser.pdf_parserhas__file__=Noneand itsSourceFileLoader.exec_moduleis never invoked, so the realdeepdoc/parser/pdf_parser.pyis never executed — it was cached as a namespace/partial module. Any laterfrom deepdoc.parser import ...then fails against this poisoned cache, and the error chain cascades intodocs_generator,invoke,rag.flow.*, etc.The trigger is structural to the test layout:
test/unit_test/rag/andrag/flow/tests/are not packages (no__init__.py). pytest's defaultprependimport mode puts each of these directories onsys.path[0].test/unit_test/ragis itself namedrag, andtest/unit_test/contains adeepdoc/directory (test/unit_test/deepdoc/parser/...).rag.flow.parser.pdf_chunk_metadata → api.db.services → deepdoc.parser, and thetest/unit_testsuite viarag.nlpand its conftest —deepdoc/deepdoc.parserresolution hits a namespace / partial-module cache collision anddeepdoc.parserends up cached as a non-package broken module.Note: CPython 3.13 executes most of the import machinery in C, bypassing
builtins.__import__,module_from_spec, andSourceFileLoader.exec_module, so the precise first corruption event cannot be observed via Python-level import hooks; thesys.modulesend-state above plus the failure conditions are sufficient to localize the mechanism.Independent verification that
deepdocis fineSuggested fixes (out of scope for this issue)
rag/flow/parser/pdf_chunk_metadata.pydoesfrom api.db.services.file_service import FileServiceat module top level, which pulls in the entireapi.dbstack (and thusdeepdoc.parser) whenever the chunker package is imported. Moving that heavy import behind a function / lazy import removes the chunker from thedeepdocimport chain.__init__.pytotest/unit_test/rag,rag/flow/tests, etc.) so pytest resolves them unambiguously instead of assys.pathroots / namespace portions.Workaround for the affected test
rag/flow/tests/test_token_chunker_tag_overlap.pycollects and passes cleanly on its own (and will in CI oncedeepdocis built). It only breaks when collected in the same process astest/unit_test/rag/. No code change is required in that test.