refactor: modernize release process with dynamic versioning and CI workflows (#28)
* refactor: modernize release process with dynamic versioning and CI workflows - Add dynamic version sourcing from pyproject.toml via importlib.metadata - Create docs/releasing.md with step-by-step release checklist - Add .github/workflows/verify-package.yml for TestPyPI/PyPI verification - Simplify docs/development.md releasing section (references releasing.md) - Update CONTRIBUTING.md doc structure to include releasing.md - Remove obsolete scripts/pre_release_check.sh (superseded by CI workflows) - Auto-format example scripts with ruff The release process now uses CI workflows for verification instead of local scripts, with a checklist-based approach for Claude Code automation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: separate TestPyPI publish and verify workflows - Add testpypi-publish.yml for uploading to TestPyPI (one-time per version) - Simplify verify-package.yml to only verify (can run multiple times) - Update releasing.md with clearer workflow steps and failure recovery - Update development.md workflows table This separation addresses the constraint that TestPyPI doesn't allow re-uploading the same version - publish once, verify as many times as needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address code review findings from polish pipeline 1. Replace fragile grep/sed version extraction with Python tomllib parser 2. Fix reference to non-existent basic-usage.py (now quickstart.py) 3. Add shell: bash directive for portability in verify-package.yml 4. Add repository check for E2E tests (fork protection) 5. Add tag validation in publish.yml to ensure tag matches pyproject.toml 6. Move import outside try block in __init__.py (code simplification) 7. Consolidate test dependency installation steps Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review findings 1. Fix PEP 440 version format: 0.0.0.dev → 0.0.0.dev0 2. Fix example script check: use py_compile instead of --help 3. Add logging for version fallback to aid debugging 4. Add fork skip notification with warning in workflow summary 5. Add secrets validation before E2E tests 6. Add force push warnings in troubleshooting section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
af212b4fc6
commit
5abb134d46
14 changed files with 456 additions and 147 deletions
10
.github/workflows/publish.yml
vendored
10
.github/workflows/publish.yml
vendored
|
|
@ -21,6 +21,16 @@ jobs:
|
|||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Validate tag matches version
|
||||
run: |
|
||||
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
||||
TOML_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
||||
if [ "$TAG_VERSION" != "$TOML_VERSION" ]; then
|
||||
echo "Error: Tag version ($TAG_VERSION) doesn't match pyproject.toml ($TOML_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Version validated: $TOML_VERSION"
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
|
|
|||
50
.github/workflows/testpypi-publish.yml
vendored
Normal file
50
.github/workflows/testpypi-publish.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: Publish to TestPyPI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build and publish to TestPyPI
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Get version from pyproject.toml
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Publishing version: $VERSION"
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build
|
||||
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
|
||||
- name: Upload to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## Published to TestPyPI" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Package:** https://test.pypi.org/project/notebooklm-py/${{ steps.version.outputs.version }}/" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Next Step:** Run the **Verify Package** workflow with source=testpypi" >> $GITHUB_STEP_SUMMARY
|
||||
123
.github/workflows/verify-package.yml
vendored
Normal file
123
.github/workflows/verify-package.yml
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
name: Verify Package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source:
|
||||
description: 'Package source'
|
||||
required: true
|
||||
default: 'testpypi'
|
||||
type: choice
|
||||
options:
|
||||
- testpypi
|
||||
- pypi
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify from ${{ inputs.source }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip'
|
||||
|
||||
- name: Get version from pyproject.toml
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create verification venv
|
||||
run: python -m venv verify-venv
|
||||
|
||||
- name: Install from TestPyPI
|
||||
if: inputs.source == 'testpypi'
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
pip install --index-url https://test.pypi.org/simple/ \
|
||||
--extra-index-url https://pypi.org/simple/ \
|
||||
notebooklm-py==${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Install from PyPI
|
||||
if: inputs.source == 'pypi'
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
pip install notebooklm-py==${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Verify version
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
INSTALLED=$(python -c "from notebooklm import __version__; print(__version__)")
|
||||
EXPECTED="${{ steps.version.outputs.version }}"
|
||||
if [ "$INSTALLED" != "$EXPECTED" ]; then
|
||||
echo "Version mismatch: installed=$INSTALLED expected=$EXPECTED"
|
||||
exit 1
|
||||
fi
|
||||
echo "Version verified: $INSTALLED"
|
||||
|
||||
- name: Verify CLI
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
notebooklm --version
|
||||
notebooklm --help
|
||||
|
||||
- name: Verify imports
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
python -c "from notebooklm import NotebookLMClient, Notebook, Source, Artifact"
|
||||
echo "Core imports verified"
|
||||
|
||||
- name: Install test dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
pip install pytest pytest-asyncio pytest-httpx pytest-cov pytest-rerunfailures python-dotenv vcrpy playwright
|
||||
playwright install chromium
|
||||
playwright install-deps
|
||||
|
||||
- name: Run unit tests
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
pytest tests/unit -v
|
||||
|
||||
- name: Run integration tests
|
||||
shell: bash
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
pytest tests/integration -v
|
||||
|
||||
- name: Skip E2E tests (fork)
|
||||
if: github.repository != 'teng-lin/notebooklm-py'
|
||||
run: |
|
||||
echo "::warning::E2E tests skipped - secrets not available in fork repositories"
|
||||
echo "## E2E Tests Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "E2E tests were not run because this workflow is executing on a fork." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Run E2E tests
|
||||
if: github.repository == 'teng-lin/notebooklm-py'
|
||||
shell: bash
|
||||
env:
|
||||
NOTEBOOKLM_AUTH_JSON: ${{ secrets.NOTEBOOKLM_AUTH_JSON }}
|
||||
NOTEBOOKLM_READ_ONLY_NOTEBOOK_ID: ${{ secrets.NOTEBOOKLM_READ_ONLY_NOTEBOOK_ID }}
|
||||
run: |
|
||||
source verify-venv/bin/activate
|
||||
if [ -z "$NOTEBOOKLM_AUTH_JSON" ]; then
|
||||
echo "::error::NOTEBOOKLM_AUTH_JSON secret is not configured"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$NOTEBOOKLM_READ_ONLY_NOTEBOOK_ID" ]; then
|
||||
echo "::error::NOTEBOOKLM_READ_ONLY_NOTEBOOK_ID secret is not configured"
|
||||
exit 1
|
||||
fi
|
||||
pytest tests/e2e -m "not variants" --reruns 2 -v
|
||||
|
|
@ -143,7 +143,8 @@ docs/
|
|||
├── configuration.md # Storage and settings
|
||||
├── troubleshooting.md # Common issues and solutions
|
||||
├── stability.md # API versioning policy
|
||||
├── development.md # Architecture, testing, releasing
|
||||
├── development.md # Architecture and testing
|
||||
├── releasing.md # Release checklist
|
||||
├── rpc-development.md # RPC capture and debugging
|
||||
├── rpc-reference.md # RPC payload structures
|
||||
└── examples/ # Runnable example scripts
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Contributing Guide
|
||||
|
||||
**Status:** Active
|
||||
**Last Updated:** 2026-01-13
|
||||
**Last Updated:** 2026-01-14
|
||||
|
||||
This guide covers everything you need to contribute to `notebooklm-py`: architecture overview, testing, and releasing.
|
||||
|
||||
|
|
@ -47,12 +47,12 @@ src/notebooklm/
|
|||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CLI Layer │
|
||||
│ cli/session.py, cli/notebook.py, cli/generate.py, etc. │
|
||||
│ cli/session.py, cli/notebook.py, cli/generate.py, etc. │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────▼─────────────────────────────────┐
|
||||
│ Client Layer │
|
||||
│ NotebookLMClient → NotebooksAPI, SourcesAPI, ArtifactsAPI │
|
||||
│ NotebookLMClient → NotebooksAPI, SourcesAPI, ArtifactsAPI │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────▼─────────────────────────────────┐
|
||||
|
|
@ -187,58 +187,22 @@ Need network?
|
|||
|
||||
## Releasing
|
||||
|
||||
### Pre-release Checklist
|
||||
See **[releasing.md](releasing.md)** for the complete release checklist.
|
||||
|
||||
- [ ] All tests pass: `pytest`
|
||||
- [ ] E2E readonly tests pass: `pytest tests/e2e -m readonly`
|
||||
- [ ] No uncommitted changes: `git status`
|
||||
- [ ] On `main` branch with latest changes
|
||||
- [ ] Version updated in `src/notebooklm/__init__.py`
|
||||
- [ ] CHANGELOG.md updated
|
||||
### Quick Reference
|
||||
|
||||
### Release Steps
|
||||
1. Validate documentation is up to date
|
||||
2. Update version in `pyproject.toml`
|
||||
3. Generate changelog entries and update `CHANGELOG.md`
|
||||
4. Commit and push to main
|
||||
5. Wait for CI + trigger E2E on main
|
||||
6. Verify on TestPyPI (verify-package workflow)
|
||||
7. Tag and push → publishes to PyPI
|
||||
8. Verify on PyPI (verify-package workflow)
|
||||
|
||||
1. **Update version** in `src/notebooklm/__init__.py`:
|
||||
```python
|
||||
__version__ = "X.Y.Z"
|
||||
```
|
||||
### Version Source of Truth
|
||||
|
||||
2. **Update CHANGELOG.md** with release notes
|
||||
|
||||
3. **Commit and push:**
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: release vX.Y.Z"
|
||||
git push
|
||||
```
|
||||
|
||||
4. **Test on TestPyPI:**
|
||||
```bash
|
||||
python -m build
|
||||
twine upload --repository testpypi dist/*
|
||||
pip install --index-url https://test.pypi.org/simple/ \
|
||||
--extra-index-url https://pypi.org/simple notebooklm-py
|
||||
notebooklm --version
|
||||
```
|
||||
|
||||
5. **Create release tag:**
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
This triggers GitHub Actions to publish to PyPI automatically.
|
||||
|
||||
### Versioning Policy
|
||||
|
||||
| Change Type | Bump | Example |
|
||||
|-------------|------|---------|
|
||||
| RPC method ID fixes | PATCH | 0.1.0 → 0.1.1 |
|
||||
| Bug fixes | PATCH | 0.1.1 → 0.1.2 |
|
||||
| New features (backward compatible) | MINOR | 0.1.2 → 0.2.0 |
|
||||
| Breaking API changes | MAJOR | 0.2.0 → 1.0.0 |
|
||||
|
||||
See [stability.md](stability.md) for full versioning policy.
|
||||
Version is defined in `pyproject.toml`. The `__version__` in `__init__.py` is dynamically read using `importlib.metadata`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -250,6 +214,8 @@ See [stability.md](stability.md) for full versioning policy.
|
|||
|----------|---------|---------|
|
||||
| `test.yml` | Push/PR | Unit tests, linting, type checking |
|
||||
| `nightly.yml` | Daily 6 AM UTC | E2E tests with real API |
|
||||
| `testpypi-publish.yml` | Manual dispatch | Publish to TestPyPI |
|
||||
| `verify-package.yml` | Manual dispatch | Verify TestPyPI or PyPI install + E2E |
|
||||
| `publish.yml` | Tag push | Publish to PyPI |
|
||||
|
||||
### Setting Up Nightly E2E Tests
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ Usage:
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from notebooklm import NotebookLMClient
|
||||
|
||||
from notebooklm import NotebookLMClient
|
||||
|
||||
# Example sources to import
|
||||
SOURCES = {
|
||||
|
|
@ -80,9 +79,7 @@ async def main():
|
|||
print("\nImporting text content...")
|
||||
for item in SOURCES["text"]:
|
||||
try:
|
||||
source = await client.sources.add_text(
|
||||
nb.id, item["title"], item["content"]
|
||||
)
|
||||
source = await client.sources.add_text(nb.id, item["title"], item["content"])
|
||||
results["success"].append(f"Text: {source.title}")
|
||||
print(f" + {source.title}")
|
||||
except Exception as e:
|
||||
|
|
@ -91,7 +88,7 @@ async def main():
|
|||
|
||||
# 5. Report results
|
||||
print("\n" + "=" * 40)
|
||||
print(f"Import complete!")
|
||||
print("Import complete!")
|
||||
print(f" Successful: {len(results['success'])}")
|
||||
print(f" Failed: {len(results['failed'])}")
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ Prerequisites:
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
from notebooklm import NotebookLMClient, ChatMode, ChatGoal, ChatResponseLength
|
||||
|
||||
from notebooklm import ChatGoal, ChatMode, ChatResponseLength, NotebookLMClient
|
||||
|
||||
|
||||
async def main():
|
||||
"""Demonstrate chat and conversation features."""
|
||||
|
||||
async with await NotebookLMClient.from_storage() as client:
|
||||
|
||||
# Create a notebook with some content
|
||||
print("Setting up notebook with sources...")
|
||||
notebook = await client.notebooks.create("Python Learning")
|
||||
|
|
@ -47,7 +47,7 @@ async def main():
|
|||
"What are the main features of Python?",
|
||||
)
|
||||
|
||||
print(f"Question: What are the main features of Python?")
|
||||
print("Question: What are the main features of Python?")
|
||||
print(f"Answer: {result.answer[:500]}...")
|
||||
print(f"Conversation ID: {result.conversation_id}")
|
||||
print(f"Turn number: {result.turn_number}")
|
||||
|
|
@ -66,7 +66,7 @@ async def main():
|
|||
conversation_id=result.conversation_id, # Continue the conversation
|
||||
)
|
||||
|
||||
print(f"Follow-up: How does it compare to other programming languages?")
|
||||
print("Follow-up: How does it compare to other programming languages?")
|
||||
print(f"Answer: {followup.answer[:500]}...")
|
||||
print(f"Is follow-up: {followup.is_follow_up}")
|
||||
print(f"Turn number: {followup.turn_number}")
|
||||
|
|
@ -78,7 +78,7 @@ async def main():
|
|||
conversation_id=result.conversation_id,
|
||||
)
|
||||
|
||||
print(f"\nFollow-up 2: What about for data science specifically?")
|
||||
print("\nFollow-up 2: What about for data science specifically?")
|
||||
print(f"Answer: {followup2.answer[:400]}...")
|
||||
|
||||
# =====================================================================
|
||||
|
|
@ -143,8 +143,8 @@ async def main():
|
|||
goal=ChatGoal.CUSTOM,
|
||||
response_length=ChatResponseLength.DEFAULT,
|
||||
custom_prompt="You are an experienced Python developer. "
|
||||
"Explain concepts with practical code examples. "
|
||||
"Focus on best practices and real-world usage.",
|
||||
"Explain concepts with practical code examples. "
|
||||
"Focus on best practices and real-world usage.",
|
||||
)
|
||||
|
||||
custom_result = await client.chat.ask(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Prerequisites:
|
|||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from notebooklm import NotebookLMClient, ReportFormat
|
||||
|
||||
|
||||
|
|
@ -23,7 +24,6 @@ async def main():
|
|||
"""Demonstrate notes and mind map functionality."""
|
||||
|
||||
async with await NotebookLMClient.from_storage() as client:
|
||||
|
||||
# Create a notebook for our examples
|
||||
print("Creating notebook...")
|
||||
notebook = await client.notebooks.create("Study Notes Demo")
|
||||
|
|
@ -51,10 +51,10 @@ async def main():
|
|||
notebook.id,
|
||||
title="Key Concepts",
|
||||
content="# Data Structures Overview\n\n"
|
||||
"- Arrays: Sequential memory storage\n"
|
||||
"- Linked Lists: Node-based storage\n"
|
||||
"- Trees: Hierarchical organization\n"
|
||||
"- Hash Tables: Key-value mapping",
|
||||
"- Arrays: Sequential memory storage\n"
|
||||
"- Linked Lists: Node-based storage\n"
|
||||
"- Trees: Hierarchical organization\n"
|
||||
"- Hash Tables: Key-value mapping",
|
||||
)
|
||||
print(f"Created note: {note1.title} (ID: {note1.id})")
|
||||
|
||||
|
|
@ -63,8 +63,8 @@ async def main():
|
|||
notebook.id,
|
||||
title="Study Questions",
|
||||
content="1. What is time complexity?\n"
|
||||
"2. When to use arrays vs linked lists?\n"
|
||||
"3. How do hash collisions work?",
|
||||
"2. When to use arrays vs linked lists?\n"
|
||||
"3. How do hash collisions work?",
|
||||
)
|
||||
print(f"Created note: {note2.title} (ID: {note2.id})")
|
||||
|
||||
|
|
@ -79,12 +79,12 @@ async def main():
|
|||
notebook.id,
|
||||
note1.id,
|
||||
content="# Data Structures Overview (Updated)\n\n"
|
||||
"## Linear Structures\n"
|
||||
"- Arrays: O(1) access, O(n) insertion\n"
|
||||
"- Linked Lists: O(n) access, O(1) insertion\n\n"
|
||||
"## Non-Linear Structures\n"
|
||||
"- Trees: Hierarchical, O(log n) search\n"
|
||||
"- Graphs: Network relationships",
|
||||
"## Linear Structures\n"
|
||||
"- Arrays: O(1) access, O(n) insertion\n"
|
||||
"- Linked Lists: O(n) access, O(1) insertion\n\n"
|
||||
"## Non-Linear Structures\n"
|
||||
"- Trees: Hierarchical, O(log n) search\n"
|
||||
"- Graphs: Network relationships",
|
||||
title="Key Concepts (Revised)",
|
||||
)
|
||||
print(f"Updated note: {note1.id}")
|
||||
|
|
@ -199,7 +199,7 @@ async def main():
|
|||
|
||||
print("\n--- Generating Quiz ---")
|
||||
|
||||
from notebooklm import QuizQuantity, QuizDifficulty
|
||||
from notebooklm import QuizDifficulty, QuizQuantity
|
||||
|
||||
quiz_gen = await client.artifacts.generate_quiz(
|
||||
notebook.id,
|
||||
|
|
@ -243,6 +243,7 @@ async def main():
|
|||
|
||||
# Categorize by type
|
||||
from notebooklm.rpc import StudioContentType
|
||||
|
||||
type_counts = {}
|
||||
for art in all_artifacts:
|
||||
type_name = StudioContentType(art.artifact_type).name
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Usage:
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from notebooklm import NotebookLMClient
|
||||
|
||||
|
||||
|
|
@ -44,17 +45,13 @@ async def main():
|
|||
# 4. Generate an audio overview
|
||||
print("Generating podcast (this may take a few minutes)...")
|
||||
status = await client.artifacts.generate_audio(
|
||||
nb.id,
|
||||
instructions="Focus on the history and key milestones"
|
||||
nb.id, instructions="Focus on the history and key milestones"
|
||||
)
|
||||
print(f" Started generation, task_id: {status.task_id}")
|
||||
|
||||
# Wait for completion
|
||||
final = await client.artifacts.wait_for_completion(
|
||||
nb.id,
|
||||
status.task_id,
|
||||
timeout=300,
|
||||
poll_interval=10
|
||||
nb.id, status.task_id, timeout=300, poll_interval=10
|
||||
)
|
||||
|
||||
if final.is_complete:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Usage:
|
|||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from notebooklm import NotebookLMClient
|
||||
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ async def main(topic: str):
|
|||
for i in range(max_polls):
|
||||
status = await client.research.poll(nb.id)
|
||||
state = status.get("status", "unknown")
|
||||
print(f" Poll {i+1}/{max_polls}: {state}")
|
||||
print(f" Poll {i + 1}/{max_polls}: {state}")
|
||||
|
||||
if state == "completed":
|
||||
sources = status.get("sources", [])
|
||||
|
|
@ -62,20 +63,15 @@ async def main(topic: str):
|
|||
# 5. Generate podcast
|
||||
print("Generating podcast...")
|
||||
gen_status = await client.artifacts.generate_audio(
|
||||
nb.id,
|
||||
instructions=f"Create an engaging overview of {topic}"
|
||||
nb.id, instructions=f"Create an engaging overview of {topic}"
|
||||
)
|
||||
|
||||
print("Waiting for audio generation...")
|
||||
final = await client.artifacts.wait_for_completion(
|
||||
nb.id,
|
||||
gen_status.task_id,
|
||||
timeout=600
|
||||
)
|
||||
final = await client.artifacts.wait_for_completion(nb.id, gen_status.task_id, timeout=600)
|
||||
|
||||
if final.is_complete:
|
||||
print(f"\n Success! Audio URL: {final.url}")
|
||||
print(f"\n Use 'notebooklm download audio' to save the file")
|
||||
print("\n Use 'notebooklm download audio' to save the file")
|
||||
else:
|
||||
print(f"\n Generation ended with status: {final.status}")
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Prerequisites:
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from notebooklm import NotebookLMClient, VideoFormat, VideoStyle
|
||||
|
||||
|
||||
|
|
@ -22,7 +23,6 @@ async def main():
|
|||
"""Generate a video overview from notebook sources."""
|
||||
|
||||
async with await NotebookLMClient.from_storage() as client:
|
||||
|
||||
# Step 1: Create a notebook with content
|
||||
print("Creating notebook...")
|
||||
notebook = await client.notebooks.create("Video Demo Notebook")
|
||||
|
|
@ -79,8 +79,8 @@ async def main():
|
|||
notebook.id,
|
||||
generation.task_id,
|
||||
initial_interval=10.0, # Check every 10 seconds initially
|
||||
max_interval=30.0, # Max 30 seconds between checks
|
||||
timeout=900.0, # 15 minute timeout for videos
|
||||
max_interval=30.0, # Max 30 seconds between checks
|
||||
timeout=900.0, # 15 minute timeout for videos
|
||||
)
|
||||
|
||||
if final_status.is_complete:
|
||||
|
|
|
|||
203
docs/releasing.md
Normal file
203
docs/releasing.md
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
# Release Checklist
|
||||
|
||||
**Status:** Active
|
||||
**Last Updated:** 2026-01-14
|
||||
|
||||
Checklist for releasing a new version of `notebooklm-py`.
|
||||
|
||||
> **For Claude Code:** When asked to prepare a release, follow this checklist step by step. Complete each checkbox before moving to the next. Ask the user to confirm before pushing or tagging.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Release
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] Verify README.md reflects current features
|
||||
- [ ] Check CLI reference matches `notebooklm --help` output
|
||||
- [ ] Verify Python API docs match public exports in `__init__.py`
|
||||
- [ ] Update `Last Updated` dates in modified docs
|
||||
- [ ] Verify example scripts have valid syntax:
|
||||
```bash
|
||||
python -m py_compile docs/examples/*.py
|
||||
```
|
||||
|
||||
### Version Bump
|
||||
|
||||
- [ ] Determine version bump type (see [Version Numbering](#version-numbering) for details)
|
||||
- [ ] Update version in `pyproject.toml`:
|
||||
```toml
|
||||
version = "X.Y.Z"
|
||||
```
|
||||
|
||||
### Changelog
|
||||
|
||||
- [ ] Get commits since last release:
|
||||
```bash
|
||||
git log $(git describe --tags --abbrev=0)..HEAD --oneline
|
||||
```
|
||||
- [ ] Generate changelog entries in Keep a Changelog format:
|
||||
- **Added** - New features
|
||||
- **Fixed** - Bug fixes
|
||||
- **Changed** - Changes in existing functionality
|
||||
- **Deprecated** - Soon-to-be removed features
|
||||
- **Removed** - Removed features
|
||||
- **Security** - Security fixes
|
||||
- [ ] Add entries under `## [Unreleased]` in `CHANGELOG.md`
|
||||
- [ ] Move `[Unreleased]` content to new version section:
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
## [X.Y.Z] - YYYY-MM-DD
|
||||
```
|
||||
- [ ] Update comparison links at bottom of `CHANGELOG.md`:
|
||||
```markdown
|
||||
[Unreleased]: https://github.com/teng-lin/notebooklm-py/compare/vX.Y.Z...HEAD
|
||||
[X.Y.Z]: https://github.com/teng-lin/notebooklm-py/compare/vPREV...vX.Y.Z
|
||||
```
|
||||
|
||||
### Commit
|
||||
|
||||
- [ ] Verify changes:
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
- [ ] Commit:
|
||||
```bash
|
||||
git add pyproject.toml CHANGELOG.md
|
||||
git commit -m "chore: release vX.Y.Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI Verification
|
||||
|
||||
### Push to Main
|
||||
|
||||
- [ ] Push to main:
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
- [ ] Wait for **test.yml** to pass:
|
||||
- Linting and formatting
|
||||
- Type checking
|
||||
- Unit and integration tests (Python 3.10-3.14, all platforms)
|
||||
|
||||
### E2E Tests on Main
|
||||
|
||||
- [ ] Go to **Actions** → **Nightly E2E**
|
||||
- [ ] Click **Run workflow**, select `main` branch
|
||||
- [ ] Wait for E2E tests to pass
|
||||
|
||||
---
|
||||
|
||||
## Package Verification
|
||||
|
||||
### Publish to TestPyPI
|
||||
|
||||
- [ ] Go to **Actions** → **Publish to TestPyPI**
|
||||
- [ ] Click **Run workflow**
|
||||
- [ ] Wait for upload to complete
|
||||
- [ ] Verify package appears: https://test.pypi.org/project/notebooklm-py/
|
||||
|
||||
> **Note:** TestPyPI does not allow re-uploading the same version. If you need to fix issues after publishing, bump the patch version and start over.
|
||||
|
||||
### Verify TestPyPI Package
|
||||
|
||||
- [ ] Go to **Actions** → **Verify Package**
|
||||
- [ ] Click **Run workflow** with **source**: `testpypi`
|
||||
- [ ] Wait for all tests to pass (unit, integration, E2E)
|
||||
- [ ] If verification fails:
|
||||
1. Fix issues locally
|
||||
2. Bump patch version in `pyproject.toml`
|
||||
3. Update `CHANGELOG.md` with fix
|
||||
4. Amend or create new commit
|
||||
5. Push and re-run **Publish to TestPyPI**
|
||||
|
||||
---
|
||||
|
||||
## Release
|
||||
|
||||
### Tag and Publish
|
||||
|
||||
- [ ] Create tag:
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
```
|
||||
- [ ] Push tag:
|
||||
```bash
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
- [ ] Wait for **publish.yml** to complete
|
||||
- [ ] Verify on PyPI: https://pypi.org/project/notebooklm-py/
|
||||
|
||||
### PyPI Verification
|
||||
|
||||
- [ ] Go to **Actions** → **Verify Package**
|
||||
- [ ] Click **Run workflow** with:
|
||||
- **source**: `pypi`
|
||||
- [ ] Wait for all tests to pass
|
||||
|
||||
### GitHub Release (Optional)
|
||||
|
||||
- [ ] Go to **Releases** → **Draft a new release**
|
||||
- [ ] Select tag `vX.Y.Z`
|
||||
- [ ] Title: `vX.Y.Z`
|
||||
- [ ] Copy release notes from `CHANGELOG.md`
|
||||
- [ ] Publish release
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CI fails after push
|
||||
|
||||
> **Warning:** Only do this immediately after your own push, before anyone else pulls.
|
||||
|
||||
```bash
|
||||
# Fix locally, then amend
|
||||
git add -A
|
||||
git commit --amend --no-edit
|
||||
git push --force origin main
|
||||
```
|
||||
|
||||
### Need to abort after commit
|
||||
|
||||
> **Warning:** Force pushing rewrites history. Only do this if you haven't shared the commit.
|
||||
|
||||
```bash
|
||||
# Undo release commit (local only)
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# If already pushed (use with caution)
|
||||
git push --force origin main
|
||||
```
|
||||
|
||||
### Tag already exists
|
||||
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d vX.Y.Z
|
||||
|
||||
# Delete remote tag (if pushed)
|
||||
git push origin :refs/tags/vX.Y.Z
|
||||
```
|
||||
|
||||
### TestPyPI upload fails
|
||||
|
||||
- Check if version already exists on TestPyPI
|
||||
- TestPyPI doesn't allow re-uploading same version
|
||||
- Bump to next patch version if needed
|
||||
|
||||
---
|
||||
|
||||
## Version Numbering
|
||||
|
||||
| Change Type | Bump | Example |
|
||||
|-------------|------|---------|
|
||||
| RPC method ID fixes | PATCH | 0.1.0 → 0.1.1 |
|
||||
| Bug fixes | PATCH | 0.1.1 → 0.1.2 |
|
||||
| New features | MINOR | 0.1.2 → 0.2.0 |
|
||||
| Breaking changes | MAJOR | 0.2.0 → 1.0.0 |
|
||||
|
||||
See [stability.md](stability.md) for full versioning policy.
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Pre-release checks for notebooklm-py
|
||||
set -e
|
||||
|
||||
echo "🔍 Running pre-release checks..."
|
||||
echo ""
|
||||
|
||||
# Type checking
|
||||
echo "📝 Checking types with mypy..."
|
||||
mypy src/notebooklm || { echo "❌ Type errors found"; exit 1; }
|
||||
echo "✅ Types OK"
|
||||
echo ""
|
||||
|
||||
# Unit tests
|
||||
echo "🧪 Running unit tests..."
|
||||
pytest tests/unit -q || { echo "❌ Unit tests failed"; exit 1; }
|
||||
echo "✅ Unit tests passed"
|
||||
echo ""
|
||||
|
||||
# Integration tests
|
||||
echo "🔗 Running integration tests..."
|
||||
pytest tests/integration -q || { echo "❌ Integration tests failed"; exit 1; }
|
||||
echo "✅ Integration tests passed"
|
||||
echo ""
|
||||
|
||||
# Coverage check
|
||||
echo "📊 Checking test coverage..."
|
||||
coverage run -m pytest tests/unit tests/integration -q
|
||||
coverage report --fail-under=70 || { echo "❌ Coverage below 70%"; exit 1; }
|
||||
echo "✅ Coverage OK"
|
||||
echo ""
|
||||
|
||||
# Check GitHub URLs
|
||||
echo "🔗 Verifying GitHub URLs..."
|
||||
if grep -q "notebooklm-clinet" pyproject.toml; then
|
||||
echo "❌ ERROR: Found 'clinet' typo in pyproject.toml"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ URLs OK"
|
||||
echo ""
|
||||
|
||||
# Build check
|
||||
echo "📦 Building package..."
|
||||
hatch build || { echo "❌ Build failed"; exit 1; }
|
||||
echo "✅ Build OK"
|
||||
echo ""
|
||||
|
||||
echo "🎉 All pre-release checks passed!"
|
||||
echo "Ready to release. Run: hatch publish"
|
||||
|
|
@ -18,7 +18,21 @@ from ._logging import configure_logging
|
|||
|
||||
configure_logging()
|
||||
|
||||
__version__ = "0.2.0"
|
||||
# Version sourced from pyproject.toml via importlib.metadata
|
||||
import logging
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
__version__ = version("notebooklm-py")
|
||||
except PackageNotFoundError:
|
||||
__version__ = "0.0.0.dev0" # Fallback when package is not installed
|
||||
_logger.debug(
|
||||
"Package 'notebooklm-py' not found in metadata. "
|
||||
"Using fallback version '%s'. This is normal during development.",
|
||||
__version__,
|
||||
)
|
||||
|
||||
# Public API: Authentication
|
||||
from .auth import DEFAULT_STORAGE_PATH, AuthTokens
|
||||
|
|
|
|||
Loading…
Reference in a new issue