Présentation du SDK Python
Présentation du SDK Python
Section intitulée « Présentation du SDK Python »Le SDK Python NextPDF s’adresse aux applications Python qui ont besoin d’une extraction PDF avec provenance. Il renvoie des blocs structurés avec des ancres de citation, comme l’index de page, la confiance, des boîtes englobantes optionnelles et des identifiants de nœuds sémantiques lorsque le PDF source expose cette structure.
Utilise le SDK lorsque ton pipeline doit répondre à des questions comme « de quelle page provient ce texte ? », « quel tableau étaye cette valeur ? » ou « qu’est-ce qui a changé entre ces deux PDF ? » sans traiter l’extraction PDF comme du texte brut anonyme.
Ce qu’il fournit
Section intitulée « Ce qu’il fournit »- Un client
NextPDFsynchrone pour les scripts, les traitements par lots et les notebooks. - Un client
AsyncNextPDFasynchrone pourasyncio, FastAPI et les autres environnements d’exécution asynchrones. - Une interface en ligne de commande (CLI)
nextpdfpour réaliser une extraction ponctuelle depuis un chemin de fichier ou l’entrée standard, avec sortie vers la sortie standard ou un fichier. - Un serveur Model Context Protocol (MCP) optionnel pour que les agents d’intelligence artificielle (IA) puissent appeler directement les outils d’extraction PDF.
- Un backend distant pour la production avec NextPDF Connect.
- Un backend local pour l’extraction hors ligne, uniquement via la bibliothèque, avec
pypdf.
Choix du backend
Section intitulée « Choix du backend »Le backend distant envoie les octets du PDF à un serveur NextPDF Connect. C’est l’option recommandée en production, car elle centralise le comportement d’extraction, l’authentification, les quotas et les contrôles opérationnels.
Le backend local s’exécute dans le processus Python et lit les PDF via pypdf. Il est utile pour le développement hors ligne et les PDF balisés, mais il ne peut pas fournir de boîtes englobantes précises et utilise une extraction heuristique au niveau du paragraphe pour les PDF non balisés. Le backend local est réservé à l’usage par bibliothèque : tu l’utilises en injectant un LocalBackend dans AsyncNextPDF. La CLI nextpdf et le serveur MCP ne peuvent pas l’utiliser. Consulte la Matrice de choix du backend pour la comparaison complète.
Le SDK n’effectue pas de reconnaissance optique de caractères (OCR, Optical Character Recognition). Les PDF numérisés ou composés uniquement d’images nécessitent une étape OCR avant que NextPDF puisse en extraire le texte intégré. Les mises en page complexes, le texte qui se chevauche et les producteurs de PDF inhabituels peuvent eux aussi réduire la qualité de l’extraction.
La CLI nextpdf fonctionne exclusivement avec le backend distant et n’est pas une interface de streaming. Chaque commande lit l’intégralité du PDF en mémoire (depuis un chemin de fichier ou l’entrée standard), l’envoie à un serveur NextPDF Connect, construit le résultat complet en mémoire, puis le sérialise en une seule écriture. Tu peux rediriger cette sortie vers un fichier avec --output (ou -o) ou vers la sortie standard, mais le résultat est entièrement mis en mémoire tampon, et non produit de façon incrémentale. La CLI ne peut pas utiliser le backend local pypdf.
Choisir un client : synchrone ou asynchrone
Section intitulée « Choisir un client : synchrone ou asynchrone »Les deux clients partagent le même espace de noms de méthodes ast et renvoient les mêmes modèles Pydantic. Leur différence tient au modèle de concurrence.
| Ton contexte | Utilise | Pourquoi |
|---|---|---|
| Scripts et traitements par lots | NextPDF (synchrone) | Flux de contrôle linéaire ; aucune boucle d’événements à gérer. |
| Notebooks Jupyter | NextPDF (synchrone) | run_sync détecte la boucle d’événements en cours et délègue à un thread de travail, de sorte que les appels bloquants fonctionnent à l’intérieur des cellules. |
L’interface nextpdf en ligne de commande | NextPDF (synchrone, interne) | La CLI construit un client synchrone pour toi. |
Services asyncio | AsyncNextPDF | Avec await natif ; aucun passage de relais entre threads. |
| FastAPI, Starlette, ASGI | AsyncNextPDF | Partage la boucle d’événements de la requête et le même pool de connexions. |
| Traitement à forte concurrence | AsyncNextPDF | Exécute de nombreuses extractions en parallèle avec asyncio.gather sur un seul client à pool partagé. |
NextPDF encapsule un AsyncNextPDF interne et exécute chaque appel via run_sync. À l’intérieur d’une boucle d’événements en cours (par exemple, un notebook), run_sync délègue la coroutine à un thread de travail unique doté de sa propre boucle, ce qui t’évite l’erreur liée à un asyncio.run imbriqué. Dans un service asyncio ou ASGI, appelle directement AsyncNextPDF plutôt que de supporter ce passage de relais entre threads à chaque appel.
Le client asynchrone utilise un httpx.AsyncClient pour le pooling de connexions ; réutilise donc une seule instance AsyncNextPDF et ferme-la à la fin. Le client synchrone NextPDF n’expose pas de méthode close(). Pour les charges de travail asynchrones de longue durée, privilégie AsyncNextPDF et gère explicitement son cycle de vie (voir Modèle opérationnel de production).
Matrice de choix du backend
Section intitulée « Matrice de choix du backend »Un backend implémente le protocole PdfBackend. Le backend distant (RemoteBackend) est sélectionné automatiquement lorsque tu passes base_url et api_key. Le backend local (LocalBackend) doit être injecté explicitement via le paramètre backend= de AsyncNextPDF ; il n’est pas exporté depuis le package nextpdf de premier niveau et n’est pas accessible depuis la CLI ni le serveur MCP.
| Capacité | Distant (RemoteBackend) | Local (LocalBackend) |
|---|---|---|
| Sélectionné par | base_url + api_key | AsyncNextPDF(backend=LocalBackend(...)) |
| Réseau | NextPDF Connect via HyperText Transfer Protocol Secure (HTTPS) | Aucun ; s’exécute dans le processus |
| Authentification, quotas, mesure de la consommation | Centralisées sur le serveur | Aucune |
| Observabilité et contrôles opérationnels | Côté serveur | Aucun |
| Extraction de PDF balisé (StructTree) | Oui | Oui |
| Extraction de PDF non balisé | Moteur du serveur | Découpage heuristique en paragraphes, confiance à 0.5 |
| Boîtes englobantes | Oui (quand le serveur les fournit) | Non (bbox vaut None) |
| Extraction de tableaux sur PDF non balisé | Moteur du serveur | Ne renvoie aucun tableau |
| Accessible depuis la CLI / le serveur MCP | Oui | Non (purement par bibliothèque) |
| Recommandé pour | Production | Développement hors ligne, tests sur PDF balisé |
Utilise le backend distant pour la production : c’est la seule option qui te donne une authentification, une application des quotas, une mesure de la consommation et une observabilité centralisées. Utilise le backend local pour le développement hors ligne et les tests sur PDF balisés, en acceptant des résultats heuristiques, l’absence de boîtes englobantes et l’absence de tableaux sur les entrées non balisées.
"""Inject the local backend for offline, library-only extraction."""
from nextpdf import AsyncNextPDFfrom nextpdf.backends.local import LocalBackend
async def extract_offline(pdf_bytes: bytes) -> None: """Extract cited text without a NextPDF Connect server.""" async with AsyncNextPDF(backend=LocalBackend()) as client: blocks = await client.ast.extract_cited_text(pdf_bytes) for block in blocks: # Heuristic blocks on untagged PDFs report confidence 0.5. print(block.citation.confidence, block.text)Modèle opérationnel de production
Section intitulée « Modèle opérationnel de production »En production, tu exécutes le backend distant avec NextPDF Connect. Les modèles ci-dessous couvrent la réutilisation du client, la gestion des erreurs, les nouvelles tentatives, la gestion des quotas et les délais d’expiration. Tous les symboles utilisés ici existent dans le SDK ; le SDK ne réessaie pas pour toi, donc la boucle de nouvelles tentatives relève de ta responsabilité.
Réutilise le client et mets les connexions en pool
Section intitulée « Réutilise le client et mets les connexions en pool »RemoteBackend conserve un httpx.AsyncClient persistant pour le pooling de connexions. Construis AsyncNextPDF une seule fois, partage-le entre les requêtes et ferme-le à l’arrêt. Ne crée pas de client par requête.
"""Reuse one pooled async client for the lifetime of the process."""
import asyncioimport osfrom pathlib import Path
from nextpdf import AsyncNextPDF
async def main() -> None: """Run several extractions over a single pooled client.""" base_url = os.environ["NEXTPDF_BASE_URL"] # Treat the API key as a secret; read it from the environment, never hard-code it. api_key = os.environ["NEXTPDF_API_KEY"]
async with AsyncNextPDF(base_url=base_url, api_key=api_key) as client: pdf_paths = (Path("a.pdf"), Path("b.pdf"), Path("c.pdf")) tasks = [ client.ast.get_document_ast(path.read_bytes()) for path in pdf_paths ] documents = await asyncio.gather(*tasks) for document in documents: print(document.page_count, document.estimated_tokens)
if __name__ == "__main__": asyncio.run(main())Le gestionnaire de contexte asynchrone appelle close() à la sortie, ce qui ferme le transport sous-jacent. Sans gestionnaire de contexte, appelle directement await client.close().
Gère les erreurs avec la hiérarchie d’exceptions
Section intitulée « Gère les erreurs avec la hiérarchie d’exceptions »Le SDK lève une hiérarchie typée. Toutes les erreurs dérivent de NextPDFError ; les échecs HTTP dérivent de NextPDFAPIError et portent un status_code. Intercepte les types spécifiques sur lesquels tu peux agir, puis rabats-toi sur le type de base.
| Exception | Levée quand | Attributs clés |
|---|---|---|
NextPDFError | Type de base de toute erreur du SDK | status_code |
NextPDFAPIError | Toute erreur HTTP renvoyée par le serveur | status_code, error_code |
NextPDFLicenseError | HTTP 402 ; la fonctionnalité nécessite un niveau de serveur supérieur | status_code (402) |
QuotaExceededError | HTTP 429 ; limite de débit ou quota dépassé | retry_after |
AstNoStructTreeError | HTTP 422 ; PDF non balisé avec le mode heuristique désactivé | status_code (422) |
AstBuildTimeoutError | HTTP 504 ; la construction de l’AST a expiré | status_code (504) |
"""Map SDK exceptions to caller-facing outcomes."""
from nextpdf import ( AstBuildTimeoutError, AstNoStructTreeError, AsyncNextPDF, NextPDFAPIError, NextPDFError, NextPDFLicenseError, QuotaExceededError,)
async def safe_extract(client: AsyncNextPDF, pdf_bytes: bytes) -> str: """Extract text, translating known failures into a stable status string.""" try: blocks = await client.ast.extract_cited_text(pdf_bytes) except QuotaExceededError as exc: # exc.retry_after holds the server Retry-After value in seconds, or None. return f"rate-limited; retry after {exc.retry_after}s" except NextPDFLicenseError: return "feature requires a higher server tier" except AstNoStructTreeError: return "untagged PDF; enable heuristic mode or use a tagged PDF" except AstBuildTimeoutError: return "build timed out; reduce the page range" except NextPDFAPIError as exc: return f"server error (status {exc.status_code})" except NextPDFError: return "extraction failed" return "\n".join(block.text for block in blocks)Réessaie les échecs transitoires avec un backoff
Section intitulée « Réessaie les échecs transitoires avec un backoff »Le SDK ne réessaie pas automatiquement. Encapsule les appels dans ta propre boucle de réessai, qui retente les échecs HTTP transitoires et respecte la valeur Retry-After du serveur, que QuotaExceededError expose sous la forme retry_after (un nombre entier de secondes, ou None). Utilise un backoff exponentiel pour les autres statuts transitoires, et ne réessaie pas NextPDFLicenseError.
"""Retry transient failures with exponential backoff and Retry-After support."""
import asynciofrom collections.abc import Awaitable, Callablefrom typing import TypeVar
from nextpdf import NextPDFAPIError, QuotaExceededError
_RETRYABLE_STATUS = frozenset({500, 502, 503, 504})
_T = TypeVar("_T")
async def with_retry( coro_factory: Callable[[], Awaitable[_T]], *, max_attempts: int = 4,) -> _T: """Call coro_factory() with bounded retries on transient server errors.
Args: coro_factory: A zero-argument callable returning a fresh awaitable. max_attempts: Maximum number of attempts before giving up.
Returns: The awaited result of the first successful attempt.
Raises: NextPDFAPIError: When all attempts fail or the error is not retryable. """ delay = 1.0 for attempt in range(1, max_attempts + 1): try: return await coro_factory() except QuotaExceededError as exc: if attempt == max_attempts: raise await asyncio.sleep(exc.retry_after if exc.retry_after is not None else delay) delay *= 2.0 except NextPDFAPIError as exc: if attempt == max_attempts or exc.status_code not in _RETRYABLE_STATUS: raise await asyncio.sleep(delay) delay *= 2.0 raise RuntimeError("unreachable")Gère les quotas, les limites de débit et les délais d’expiration
Section intitulée « Gère les quotas, les limites de débit et les délais d’expiration »L’application des quotas et des limites de débit se fait côté serveur. Sur un HTTP 429, le SDK lève QuotaExceededError et analyse l’en-tête Retry-After en retry_after. Le backend distant expose aussi des en-têtes X-RateLimit-* sur les réponses de rendu, ce qui te permet de limiter ton débit de façon proactive avant d’atteindre une limite stricte.
Les délais d’expiration des requêtes utilisent une valeur par défaut fixe de 60 secondes au total avec un délai de connexion de 10 secondes (httpx.Timeout(60.0, connect=10.0)). Pour limiter les longues constructions d’AST, privilégie la réduction du travail avec page_range_start, page_range_end ou token_budget plutôt que de te reposer sur le seul délai d’expiration ; une construction trop longue renvoie AstBuildTimeoutError (HTTP 504).
Exemples d’architectures
Section intitulée « Exemples d’architectures »Traitement par lots
Section intitulée « Traitement par lots »Un worker de traitement par lots lit les PDF, extrait le texte cité et écrit une sortie structurée. Réutilise un seul client à pool partagé, borne la concurrence avec un sémaphore et applique l’assistant de réessai ci-dessus.
"""Batch-extract a directory of PDFs over one pooled async client."""
import asyncioimport osfrom pathlib import Path
from nextpdf import AsyncNextPDF
async def run_batch(input_dir: Path, concurrency: int = 8) -> None: """Extract cited text for every PDF in input_dir, bounded by concurrency.""" semaphore = asyncio.Semaphore(concurrency)
async def worker(client: AsyncNextPDF, path: Path) -> None: async with semaphore: blocks = await client.ast.extract_cited_text(path.read_bytes()) out = path.with_suffix(".txt") out.write_text("\n".join(b.text for b in blocks), encoding="utf-8")
async with AsyncNextPDF( base_url=os.environ["NEXTPDF_BASE_URL"], api_key=os.environ["NEXTPDF_API_KEY"], ) as client: await asyncio.gather(*(worker(client, p) for p in input_dir.glob("*.pdf")))Service web
Section intitulée « Service web »Un service FastAPI partage un seul AsyncNextPDF entre les requêtes pendant toute la durée de vie de l’application, de sorte que chaque requête réutilise le pool de connexions. Lis les identifiants depuis l’environnement et traite la clé d’API comme un secret.
"""FastAPI service that shares one pooled NextPDF client across requests."""
import osfrom contextlib import asynccontextmanager
from fastapi import FastAPI, UploadFile
from nextpdf import AsyncNextPDF
@asynccontextmanagerasync def lifespan(app: FastAPI): """Create the pooled client on startup and close it on shutdown.""" app.state.client = AsyncNextPDF( base_url=os.environ["NEXTPDF_BASE_URL"], api_key=os.environ["NEXTPDF_API_KEY"], ) try: yield finally: await app.state.client.close()
app = FastAPI(lifespan=lifespan)
@app.post("/extract")async def extract(file: UploadFile) -> dict[str, list[str]]: """Return cited text blocks for an uploaded PDF.""" pdf_bytes = await file.read() blocks = await app.state.client.ast.extract_cited_text(pdf_bytes) return {"text": [block.text for block in blocks]}Outil d’agent
Section intitulée « Outil d’agent »Pour les agents IA, exécute le serveur MCP. Il expose des outils PDF (par exemple nextpdf_extract_text, nextpdf_extract_tables, nextpdf_get_ast, nextpdf_info, nextpdf_search, nextpdf_get_outline, nextpdf_diff et nextpdf_health) via l’entrée/sortie standard. Le serveur lit NEXTPDF_BASE_URL et NEXTPDF_API_KEY depuis l’environnement et est donc adossé au backend distant ; comme la CLI, il ne peut pas utiliser le backend local. Installe l’extra optionnel et exécute le module.
pip install "nextpdf[mcp]"python -m nextpdf.mcpConsulte Serveur MCP Python pour le guide d’intégration des agents, CLI Python pour l’utilisation en terminal, et Référence de l’API Python pour l’ensemble complet des clients, modèles et exceptions.