Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR

### Corrigido


- A mensagem de pacote ausente de um provider do LangChain só sugere `pip install dataframeit[all]` quando o pacote do provider está no extra `all`, lido dos metadados do pacote instalado. Para Mistral, Cohere, Bedrock, Vertex AI e outros providers sem extra próprio, ela sugere só o `langchain-<provider>`, porque o `all` não os instala (#279).

## [0.11.1] - 2026-10-29

Expand Down
4 changes: 2 additions & 2 deletions docs/en/guides/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

### `ImportError` asking you to install a package

The chosen provider needs its package, and the message says what to install. For LangChain providers, it suggests the provider's package or the `all` extra:
The chosen provider needs its package, and the message says what to install. For LangChain providers, it suggests the provider's package and, when the package is in the `all` extra, `all` as well:

```bash
pip install langchain-anthropic
# or
pip install dataframeit[all]
```

The provider's extra, such as `dataframeit[anthropic]`, installs the same package. `all` brings the providers that have an extra, except Codex; LangChain providers without their own extra, such as Cohere and Mistral, need the `langchain-<provider>` package, which `all` does not include. For `claude_code` and `codex`, the message asks for the provider's extra (`dataframeit[claude-code]`, `dataframeit[codex]`), and for web search, the search provider's package. The extras are listed in [Installation](../getting-started/installation.md).
The provider's extra, such as `dataframeit[anthropic]`, installs the same package. `all` brings the providers that have an extra, except Codex; LangChain providers without their own extra, such as Cohere and Mistral, need the `langchain-<provider>` package, which `all` does not include, and the message suggests only that package. For `claude_code` and `codex`, the message asks for the provider's extra (`dataframeit[claude-code]`, `dataframeit[codex]`), and for web search, the search provider's package. The extras are listed in [Installation](../getting-started/installation.md).

### Authentication error (401 or 403)

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

### `ImportError` pedindo para instalar um pacote

O provider escolhido precisa do pacote dele, e a mensagem diz o que instalar. Nos providers do LangChain, ela sugere o pacote do provider ou o extra `all`:
O provider escolhido precisa do pacote dele, e a mensagem diz o que instalar. Nos providers do LangChain, ela sugere o pacote do provider e, quando o pacote está no extra `all`, também o `all`:

```bash
pip install langchain-anthropic
# ou
pip install dataframeit[all]
```

O extra do provider, como `dataframeit[anthropic]`, instala o mesmo pacote. O `all` traz os providers que têm extra, menos o Codex; os providers do LangChain sem extra próprio, como Cohere e Mistral, precisam do pacote `langchain-<provider>`, que o `all` não inclui. No `claude_code` e no `codex`, a mensagem pede o extra do provider (`dataframeit[claude-code]`, `dataframeit[codex]`), e na busca web, o pacote do provedor de busca. Os extras estão em [Instalação](../getting-started/installation.md).
O extra do provider, como `dataframeit[anthropic]`, instala o mesmo pacote. O `all` traz os providers que têm extra, menos o Codex; os providers do LangChain sem extra próprio, como Cohere e Mistral, precisam do pacote `langchain-<provider>`, que o `all` não inclui, e a mensagem sugere só ele. No `claude_code` e no `codex`, a mensagem pede o extra do provider (`dataframeit[claude-code]`, `dataframeit[codex]`), e na busca web, o pacote do provedor de busca. Os extras estão em [Instalação](../getting-started/installation.md).

### Erro de autenticação (401 ou 403)

Expand Down
43 changes: 42 additions & 1 deletion src/dataframeit/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from __future__ import annotations

import importlib
import importlib.metadata
import os
import random
import re
Expand Down Expand Up @@ -275,6 +276,39 @@ def _infer_provider_info(provider: str | None) -> dict:
}


_EXTRA_ALL_MARKER = re.compile(r"""extra\s*==\s*["']all["']""")
_REQUIREMENT_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*")


def _normalize_distribution_name(name: str) -> str:
# Normalização da PEP 503: "langchain_openai" e "LangChain-OpenAI" são o
# mesmo pacote, e é assim que o nome de instalação se compara ao requisito.
return re.sub(r"[-_.]+", "-", name).lower()


def _all_extra_packages() -> frozenset[str]:
"""Nomes normalizados dos pacotes que o extra ``all`` instala.

Lidos dos metadados do dataframeit instalado, que o build gera do
``pyproject.toml``, para que a dica de ``dataframeit[all]`` não divirja do
extra. Sem os metadados, como ao rodar o código fonte sem instalar o
pacote, devolve vazio: não há como saber o que o extra traz, e sugeri-lo
poderia mandar instalar algo que não resolve.
"""
try:
requirements = importlib.metadata.requires("dataframeit") or []
except importlib.metadata.PackageNotFoundError:
return frozenset()
packages = set()
for requirement in requirements:
# O marcador de ambiente vem depois do ";", e é nele que o extra aparece.
name_part, _, marker = requirement.partition(";")
match = _REQUIREMENT_NAME.match(name_part.strip())
if match and _EXTRA_ALL_MARKER.search(marker):
packages.add(_normalize_distribution_name(match.group()))
return frozenset(packages)


def _get_missing_package_message(
package: str,
install_name: str,
Expand Down Expand Up @@ -360,8 +394,15 @@ def validate_provider_dependencies(provider: str | None) -> None:
try:
importlib.import_module(package)
except ImportError as err:
# O extra all só traz os providers que têm extra próprio; para os
# demais, como mistralai e cohere, sugeri-lo não resolveria.
alternative = (
"dataframeit[all]"
if _normalize_distribution_name(install) in _all_extra_packages()
else None
)
raise ImportError(
_get_missing_package_message(package, install, name, "dataframeit[all]")
_get_missing_package_message(package, install, name, alternative)
) from err


Expand Down
63 changes: 63 additions & 0 deletions tests/test_erros_de_provider.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""Classificação e mensagens de erro dos providers."""

import importlib
import importlib.metadata
import importlib.util
import tomllib
from pathlib import Path
from types import SimpleNamespace

import pytest
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name

from dataframeit import errors
from dataframeit.claude_code import _raise_for_result_error
Expand Down Expand Up @@ -350,6 +355,64 @@ def importar(nome):
assert "dataframeit[all]" in str(exc_info.value)


def _sem_o_pacote(monkeypatch, pacote):
import_module = importlib.import_module

def importar(nome):
if nome == pacote:
raise ImportError(nome)
return import_module(nome)

monkeypatch.setattr(importlib, "import_module", importar)


@pytest.mark.parametrize(
("provider", "pacote", "instalar", "sugere_all"),
[
("openai", "langchain_openai", "langchain-openai", True),
("azure_openai", "langchain_openai", "langchain-openai", True),
("mistralai", "langchain_mistralai", "langchain-mistralai", False),
("cohere", "langchain_cohere", "langchain-cohere", False),
("bedrock", "langchain_aws", "langchain-aws", False),
],
)
def test_dica_do_extra_all_so_para_provider_cujo_pacote_esta_nele(
monkeypatch, provider, pacote, instalar, sugere_all
):
_sem_o_pacote(monkeypatch, pacote)

with pytest.raises(ImportError, match=f"pip install {instalar} ") as exc_info:
validate_provider_dependencies(provider)

assert ("dataframeit[all]" in str(exc_info.value)) is sugere_all


def test_sem_metadados_do_pacote_a_dica_do_extra_all_some(monkeypatch):
"""Sem os metadados, não há como saber o que o extra traz, e a dica não sai."""

def sem_metadados(nome):
raise importlib.metadata.PackageNotFoundError(nome)

monkeypatch.setattr(importlib.metadata, "requires", sem_metadados)
_sem_o_pacote(monkeypatch, "langchain_openai")

with pytest.raises(ImportError, match="pip install langchain-openai ") as exc_info:
validate_provider_dependencies("openai")

assert "dataframeit[all]" not in str(exc_info.value)


def test_pacotes_do_extra_all_lidos_dos_metadados_batem_com_o_pyproject():
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
declarados = tomllib.loads(pyproject.read_text(encoding="utf-8"))["project"][
"optional-dependencies"
]["all"]

assert errors._all_extra_packages() == {
canonicalize_name(Requirement(r).name) for r in declarados
}


def test_sem_provider_valida_so_o_langchain(monkeypatch):
"""Com provider=None, o init_chat_model infere o provider pelo nome do modelo."""
importados = []
Expand Down
Loading