[project]
name = "notebooklm-py"
version = "0.8.0"
description = "Unofficial Python library for automating Google NotebookLM"
dynamic = ["readme"]
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "Teng Lin", email = "teng.lin@gmail.com"}
]
keywords = ["notebooklm", "google", "ai", "automation", "rpc", "client", "api"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"httpx>=0.27.0,<0.29",
"click>=8.0.0,<9",
"rich>=13.0.0,<16",
"filelock>=3.13,<4",
]
[project.urls]
Homepage = "https://github.com/teng-lin/notebooklm-py"
Repository = "https://github.com/teng-lin/notebooklm-py"
Documentation = "https://github.com/teng-lin/notebooklm-py#readme"
Issues = "https://github.com/teng-lin/notebooklm-py/issues"
[project.optional-dependencies]
markdown = ["markdownify>=0.14.1,<2"]
browser = ["playwright>=1.40.0,<2"]
cookies = ["rookiepy>=0.1.0,<1"]
impersonate = ["curl_cffi>=0.11,<1"]
headless = ["gpsoauth>=1.1.0"]
mcp = ["fastmcp>=3.0,<4"]
server = [
"fastapi>=0.115,<1",
"uvicorn[standard]>=0.34,<1",
"python-multipart>=0.0.20,<1",
]
dev = [
"pytest>=8.0.0,<10",
"pytest-asyncio>=0.23.0,<2",
"pytest-httpx>=0.30.0,<0.37",
"pytest-cov>=4.0.0,<8",
"pytest-rerunfailures>=14.0,<17",
"pytest-timeout>=2.3.0,<3",
"pytest-xdist>=3.6.0,<4",
"python-dotenv>=1.0.0,<2",
"mypy>=1.0.0,<3",
"pre-commit>=4.5.1,<5",
"ruff==0.15.20",
"tomli>=2.0.0,<3; python_version < '3.11'",
"vcrpy>=6.0.0,<9",
"packaging>=23.0,<27",
]
all = ["notebooklm-py[browser,dev,headless,markdown,mcp,server]"]
[project.scripts]
notebooklm = "notebooklm.notebooklm_cli:main"
notebooklm-mcp = "notebooklm.mcp.__main__:main"
notebooklm-server = "notebooklm.server.__main__:main"
[build-system]
requires = ["hatchling", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(docs/'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/docs/'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(CHANGELOG\.md\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/CHANGELOG.md)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(SECURITY\.md\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/SECURITY.md)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(LICENSE\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/LICENSE)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(SKILL\.md\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/SKILL.md)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(AGENTS\.md\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/AGENTS.md)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\]\(CONTRIBUTING\.md\)'
replacement = '](https://github.com/teng-lin/notebooklm-py/blob/v$HFPR_VERSION/CONTRIBUTING.md)'
[tool.hatch.build.targets.wheel]
packages = ["src/notebooklm"]
force-include = {"SKILL.md" = "notebooklm/data/SKILL.md", "AGENTS.md" = "notebooklm/data/CODEX.md"}
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
addopts = "--ignore=tests/e2e"
timeout = 60
markers = [
"e2e: end-to-end tests requiring authentication (run with pytest tests/e2e -m e2e)",
"variants: parameter variant tests (skip to save quota)",
"readonly: read-only tests against user's test notebook",
"impersonate_smoke: minimal live smoke run under NOTEBOOKLM_TRANSPORT=curl_cffi in the nightly (read/ask/upload/download — one per transport-critical op)",
"vcr: tests using VCR.py recorded cassettes (run with NOTEBOOKLM_VCR_RECORD=1 to record)",
"no_keepalive_disable: opt a VCR test out of the global NOTEBOOKLM_DISABLE_KEEPALIVE_POKE=1 autouse fixture in tests/integration/conftest.py",
"allow_no_vcr: opt a tests/integration/ test out of the integration-tree enforcement hook; use for mock-only tests that legitimately live under tests/integration/",
"characterization: behavior-pinning unit tests that lock in current observable contracts and would be too synthetic to cassette-back",
"repo_lint: repo-wide audit/compat checks (cassette shapes, public surface, release gates, doc-sync, CI script audits). Slow; skip locally with `-m \"not repo_lint\"`. CI still runs them by default.",
]
[tool.coverage.run]
source = ["src/notebooklm"]
branch = true
[tool.coverage.report]
show_missing = true
fail_under = 90
[tool.notebooklm.per_file_coverage_floors]
"src/notebooklm/__main__.py" = 0
"src/notebooklm/cli/_firefox_containers.py" = 95
"src/notebooklm/cli/doctor_cmd.py" = 63
"src/notebooklm/cli/profile_cmd.py" = 74
"src/notebooklm/cli/session_cmd.py" = 83
[tool.mypy]
python_version = "3.10"
warn_return_any = false
warn_unused_ignores = true
disallow_untyped_defs = false
check_untyped_defs = true
ignore_missing_imports = true
files = ["src/notebooklm"]
exclude = ["tests/"]
[[tool.mypy.overrides]]
module = [
"notebooklm",
"notebooklm._types.*",
"notebooklm.artifacts",
"notebooklm.auth",
"notebooklm.client",
"notebooklm.config",
"notebooklm.exceptions",
"notebooklm.io",
"notebooklm.log",
"notebooklm.migration",
"notebooklm.paths",
"notebooklm.research",
"notebooklm.types",
"notebooklm.urls",
"notebooklm.utils",
]
disallow_untyped_defs = true
disallow_any_generics = true
warn_return_any = true
strict_optional = true
[[tool.mypy.overrides]]
module = "notebooklm.cli.*"
warn_return_any = false
strict_optional = true
[[tool.mypy.overrides]]
module = "notebooklm.rpc.*"
disallow_untyped_defs = true
warn_return_any = true
strict_optional = true
[tool.ruff]
target-version = "py310"
line-length = 100
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E",
"W",
"F",
"I",
"B",
"C4",
"UP",
"SIM",
]
ignore = [
"E501",
"B008",
"SIM102",
"SIM105",
]
per-file-ignores = {"src/notebooklm/__init__.py" = ["E402"], "src/notebooklm/notebooklm_cli.py" = ["E402"]}
[tool.ruff.lint.isort]
known-first-party = ["notebooklm"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"