[build-system]
requires = [
    "pdm-backend>=2.4.3",
]
build-backend = "pdm.backend"

[project]
name = "local-deep-research"
dynamic = []
description = "AI-powered research assistant with deep, iterative analysis using LLMs and web searches"
readme = "README.md"
requires-python = ">=3.11,<3.15"
authors = [
    { name = "LearningCircuit", email = "185559241+LearningCircuit@users.noreply.github.com" },
    { name = "HashedViking", email = "6432677+HashedViking@users.noreply.github.com" },
    { name = "djpetti", email = "djpetti@gmail.com" },
]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
dependencies = [
    "langchain~=1.2",
    "langchain-community~=0.4",
    "langchain-core~=1.2",
    "langchain-ollama~=1.0",
    "langchain-openai~=1.1",
    "langchain-anthropic~=1.3",
    "duckduckgo-search~=8.1",
    "python-dateutil~=2.9",
    "typing-extensions~=4.15",
    "justext~=3.0",
    "playwright~=1.58",
    "beautifulsoup4~=4.14",
    "flask~=3.1.3",
    "werkzeug~=3.1.6",
    "flask-cors~=6.0",
    "flask-socketio~=5.6.1",
    "alembic~=1.17",
    "sqlalchemy~=2.0",
    "sqlalchemy-utc~=0.14",
    "wikipedia~=1.4",
    "arxiv~=2.4",
    "pypdf~=6.9.1",
    "sentence-transformers~=5.2",
    "faiss-cpu~=1.13",
    "pydantic~=2.12",
    "pydantic-settings~=2.12",
    "toml~=0.10",
    "platformdirs~=4.5",
    "dynaconf~=3.2",
    "requests>=2.32",
    "urllib3~=2.6",
    "tiktoken~=0.12",
    "xmltodict~=1.0",
    "lxml~=6.0",
    "defusedxml~=0.7",
    "nh3~=0.3",
    "pdfplumber~=0.11",
    "unstructured~=0.18",
    "google-search-results~=2.4",
    "importlib-resources~=6.5",
    "setuptools~=82.0",
    "jaraco-context~=6.1",
    "flask-wtf~=1.2",
    "optuna~=4.7",
    "elasticsearch~=9.3",
    "methodtools~=0.4",
    "loguru~=0.7",
    "cachetools~=7.0",
    "matplotlib~=3.10",
    "pandas~=3.0",
    "plotly~=6.5",
    "kaleido~=1.2",
    "aiohttp~=3.13",
    "tenacity~=9.1",
    "apscheduler~=3.11",
    "rich~=14.3",
    "click~=8.3",
    "flask-login~=0.6",
    "flask-limiter~=4.1",
    "sqlcipher3-binary~=0.6; sys_platform == \"linux\" and platform_machine == \"x86_64\"",
    "sqlcipher3~=0.6; (platform_machine == \"aarch64\" or platform_machine == \"arm64\") and sys_platform == \"linux\"",
    "sqlcipher3~=0.6; sys_platform != \"linux\"",
    "lxml-html-clean>=0.4.4",
    "weasyprint~=68.1",
    "jaraco-context~=6.1",
    "Pillow>=12.1.1",
    "cryptography>=46.0.5",
    "apprise~=1.9",
    "markdown~=3.10",
    "pypandoc-binary~=1.16",
    "datasets~=4.5",
    "pyarrow~=23.0",
    "langchain-experimental~=0.4",
]
version = "1.5.0"

[project.license]
file = "LICENSE"

[project.optional-dependencies]
mcp = [
    "mcp[cli]~=1.2",
]

[project.urls]
Homepage = "https://github.com/LearningCircuit/local-deep-research"
"Bug Tracker" = "https://github.com/LearningCircuit/local-deep-research/issues"

[project.scripts]
ldr = "local_deep_research.main:main"
ldr-web = "local_deep_research.web.app:main"
ldr-mcp = "local_deep_research.mcp:run_server"

[tool.setuptools]
include-package-data = true

[tool.setuptools.package-data]
"local_deep_research.web" = [
    "templates/*",
    "static/*",
    "static/**/*",
]
"local_deep_research.defaults" = [
    "*.toml",
    "*.py",
    "*.template",
    ".env.template",
]
"local_deep_research.notifications" = [
    "templates/*.jinja2",
]

[tool.pdm]
distribution = true

[tool.pdm.version]
source = "file"
path = "src/local_deep_research/__version__.py"

[[tool.pdm.source]]
url = "https://download.pytorch.org/whl/cpu"
name = "torch"
include_packages = [
    "torch",
    "torch*",
]

[tool.pdm.resolution.overrides]
requests = ">=2.33"

[tool.pytest.ini_options]
markers = [
    "requires_llm: marks tests that require a real LLM (not fallback)",
    "integration: marks integration tests",
    "slow: marks slow tests",
    "asyncio: marks async tests that use asyncio",
]
timeout = 60
timeout_method = "signal"

[tool.coverage.run]
source = [
    "src",
]
branch = true
omit = [
    "*/tests/*",
    "*/__pycache__/*",
    "*/migrations/*",
    "*/strategies/*",
    "*/cli.py",
    "*/cli/*",
    "*/visualization.py",
    "*/answer_decoding/*",
]

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
]
fail_under = 50
show_missing = true

[tool.coverage.html]
directory = "coverage/htmlcov"

[tool.ruff]
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".ipynb_checkpoints",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pyenv",
    ".pytest_cache",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    ".vscode",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "site-packages",
    "venv",
]
line-length = 80
indent-width = 4

[tool.ruff.lint]
select = [
    "E4",
    "E7",
    "E9",
    "F",
    "S",
    "G",
    "PERF",
    "RET",
    "TRY",
    "T20",
    "C4",
]
ignore = [
    "S110",
    "S112",
    "S603",
    "S606",
    "S607",
    "TRY003",
    "TRY004",
    "TRY300",
    "PERF401",
]
fixable = [
    "ALL",
]
unfixable = []
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[tool.ruff.lint.per-file-ignores]
"tests/**" = [
    "S101",
    "S104",
    "S105",
    "S106",
    "S107",
    "S108",
    "S113",
    "S311",
    "S324",
    "S602",
    "S608",
    "T20",
    "TRY201",
    "G004",
    "C420",
    "TRY002",
    "C408",
    "C401",
    "C416",
    "C414",
    "PERF102",
    "PERF402",
    "PERF403",
    "RET503",
    "RET504",
    "TRY301",
]
"examples/**" = [
    "S105",
    "S311",
    "T201",
    "TRY002",
    "C408",
    "C401",
    "C416",
    "PERF102",
    "PERF402",
    "PERF403",
    "RET503",
    "RET502",
    "RET504",
    "TRY301",
]
"cookiecutter-docker/**" = [
    "S602",
    "T201",
]
"scripts/ci/**" = [
    "S105",
]
".pre-commit-hooks/**" = [
    "T201",
    "RET503",
    "RET504",
]
"scripts/**" = [
    "T201",
]
".github/scripts/**" = [
    "T201",
]
"src/local_deep_research/cli/**" = [
    "T201",
]
"src/local_deep_research/benchmarks/**" = [
    "T201",
]
"src/local_deep_research/settings/logger.py" = [
    "T201",
]
"src/local_deep_research/web/routes/route_registry.py" = [
    "T201",
]
"src/local_deep_research/web_search_engines/rate_limiting/cli.py" = [
    "T201",
]
"src/local_deep_research/web/server_config.py" = [
    "S104",
]
"src/local_deep_research/web/routes/settings_routes.py" = [
    "S104",
]
"src/local_deep_research/security/network_utils.py" = [
    "S104",
]
"src/local_deep_research/security/notification_validator.py" = [
    "S104",
]
"src/local_deep_research/security/url_builder.py" = [
    "S104",
]
"src/local_deep_research/utilities/llm_utils.py" = [
    "S104",
]
"src/local_deep_research/web_search_engines/rate_limiting/llm/wrapper.py" = [
    "S104",
]
"src/local_deep_research/news/subscription_manager/scheduler.py" = [
    "S311",
]
"src/local_deep_research/web/auth/middleware_optimizer.py" = [
    "S311",
]
"src/local_deep_research/web_search_engines/engines/search_engine_google_pse.py" = [
    "S311",
]
"src/local_deep_research/web_search_engines/rate_limiting/tracker.py" = [
    "S311",
]
"src/local_deep_research/database/models/library.py" = [
    "S105",
]
"src/local_deep_research/web/auth/database_middleware.py" = [
    "S105",
]
"src/local_deep_research/security/safe_requests.py" = [
    "S113",
]
"src/local_deep_research/web/themes/__init__.py" = [
    "S704",
]
"src/local_deep_research/web/utils/vite_helper.py" = [
    "S704",
]
"src/local_deep_research/advanced_search_system/filters/followup_relevance_filter.py" = [
    "S608",
]
"src/local_deep_research/web_search_engines/search_engine_base.py" = [
    "S608",
]
"src/local_deep_research/research_library/downloaders/arxiv.py" = [
    "S314",
]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
check_untyped_defs = true
warn_redundant_casts = true
warn_unused_ignores = true
no_implicit_optional = true
strict_equality = true
show_error_codes = true
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "tests.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = [
    "cachetools",
    "cachetools.*",
    "dateutil",
    "dateutil.*",
]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = [
    "local_deep_research.advanced_search_system.*",
    "local_deep_research.api.*",
    "local_deep_research.benchmarks.*",
    "local_deep_research.config.*",
    "local_deep_research.database.*",
    "local_deep_research.domain_classifier.*",
    "local_deep_research.embeddings.splitters.*",
    "local_deep_research.library.*",
    "local_deep_research.metrics.*",
    "local_deep_research.research_library.*",
    "local_deep_research.security.*",
    "local_deep_research.settings.*",
    "local_deep_research.storage.*",
    "local_deep_research.utilities.*",
    "local_deep_research.web.*",
]
ignore_errors = true

[dependency-groups]
dev = [
    "virtualenv~=20.36",
    "pre-commit~=4.5",
    "cookiecutter~=2.6",
    "pandas~=3.0",
    "optuna~=4.7",
    "pytest-mock~=3.15",
    "pytest~=9.0",
    "pytest-cov~=7.0",
    "pytest-timeout~=2.4",
    "pytest-asyncio~=1.3",
    "pytest-xdist~=3.8",
    "ruff~=0.15",
    "freezegun~=1.5",
    "mypy~=1.19",
    "types-requests~=2.32",
    "types-setuptools~=82.0",
    "types-toml~=0.10",
    "types-flask~=1.1",
    "hypothesis~=6.151",
    "vulture~=2.14",
]
