GSpreadManager es un wrapper de Python para Google Sheets con una interfaz simple y pythonic para lectura, escritura, formato, validación y gestión de hojas y documentos.
📚 Documentación completa: https://pabloalaniz.github.io/GSpreadManager/
API 2.0: el punto de entrada es
SheetManager.mgr.worksheet("Hoja1")devuelve un handle inmutable a una pestaña; no hay "hoja activa" global ni efectos colaterales. (La claseGoogleSheetConectorde la 1.x fue eliminada — ver CHANGELOG.)
- 🔐 Autenticación flexible: service account (archivo o dict), credenciales de
google-auth, cliente ya autorizado o ADC - 📖 Lectura flexible: listas, diccionarios,
pandas/polarso modelos de fila tipados (@dataclasso Pydantic v2) - ✏️ Escritura y actualización: celdas, filas, rangos, append, insert y lotes
- 📐 Estructura de la hoja: insertar/eliminar/redimensionar/ocultar filas y columnas, orden y filtro, merge/unmerge, color de pestaña
- 🗂️ Gestión de hojas y documentos (Drive): crear, copiar, listar, borrar, compartir/permisos y exportar (PDF/CSV/XLSX/...)
- 🏷️ Metadata: notas de celda, named ranges y protected ranges
- 🎨 Formato propio (sin dependencias): colores, fuentes, números, freeze, validación y formato condicional
- 🐼 DataFrames pluggable:
pandasopolars, con lectura avanzada (drop_empty_*,index_col) y escritura anclada - ⚡ Robustez de cuota: reintentos con backoff (429/500/503) + rate limiting proactivo (token bucket) + caché de lecturas
- 🧪 Testeable sin red: backend en memoria (
gspreadmanager.testing) que implementa los mismos puertos - ⌨️ CLI:
gspreadmanager read/append/export/share - 🔌 Cliente nativo por defecto (REST sobre
google-auth); gspread disponible como extra - ⚡ API async real (
AsyncSheetManager, extra[async]con httpx): datos, streaming, tabla y modelos sin bloquear el loop - 🧱 Arquitectura hexagonal (dominio / aplicación / infraestructura / puertos) con type hints (PEP 561)
- 📦 Dependencias mínimas: solo
gspreadygoogle-auth(pandas/polarsopcionales)
pip install GSpreadManager # núcleo (cliente nativo, solo google-auth)
# Extras opcionales
pip install "GSpreadManager[gspread]" # backend de gspread (default si está instalado)
pip install "GSpreadManager[pandas]" # DataFrames con pandas
pip install "GSpreadManager[polars]" # DataFrames con polars
pip install "GSpreadManager[pydantic]" # modelos de fila con Pydantic v2
pip install "GSpreadManager[async]" # API async (httpx)Desde la 3.0 el transporte por defecto es el cliente nativo (REST sobre
google-auth);gspreades un extra opcional con la misma API detrás de los mismos puertos (backend="gspread"obackend="auto"para el comportamiento 2.x). Ver la guía de migración.
- En Google Cloud Console, creá un proyecto y una cuenta de servicio; descargá su clave JSON.
- Habilitá Google Sheets API y Google Drive API.
- Compartí tu hoja con el email de la cuenta de servicio, con permiso de Editor.
from gspreadmanager import SheetManager
mgr = SheetManager("Mi Hoja de Cálculo", json_google_file="credentials.json")
ws = mgr.worksheet("Hoja1") # handle inmutable a la pestaña
# Leer
datos = ws.read(output_format="dict")
# Escribir
ws.append([["Juan", "juan@example.com"]])
ws.update_cell(2, 1, "María")
# Otra pestaña, handle independiente
ws2 = mgr.worksheet("Hoja2")ws.read(output_format="list") # lista de listas
ws.read(output_format="dict") # lista de dicts (1ª fila = encabezados)
ws.read(output_format="pandas") # DataFrame (extra [pandas])
ws.read_range(1, 10, "A", "D") # rango por índices de fila/columnaws.append([["Ana", "ana@example.com"]])
ws.update_cell(3, 2, "Nuevo Valor")
ws.update_row(5, ["X", "Y", "Z"], start_column=3)
ws.insert([["A", "B"]], fila=10) # inserta en una fila (o al final)
ws.batch_update([{"range": "Hoja1!A1:B1", "values": [["Mes", "Total"]]}])filas = ws.rows_where_column_equals(0, "Activo") # [(nro_fila, fila), ...]
ultima = ws.last_row()
fila, idx = ws.row_with_empty_in_column("B")
celda = ws.find("Total")from gspreadmanager import CellFormat, TextFormat, Color
ws.format_header() # negrita + fondo, primera fila
ws.freeze(rows=1)
ws.format_range("A1:D1", CellFormat(
text_format=TextFormat(bold=True, foreground_color=Color.from_hex("#FFFFFF")),
background_color=Color.from_hex("#0B5394"),
horizontal_alignment="CENTER",
))
ws.set_background("A2:A100", Color.from_hex("#FFF2CC"))
ws.set_number_format("C2:C100", "#,##0.00", number_type="CURRENCY")
ws.merge("A1:D1")
ws.add_dropdown("E2:E100", ["Pendiente", "En curso", "Hecho"])
ws.add_checkbox("F2:F100")
ws.add_conditional_format("C2:C100", "NUMBER_LESS", [0],
CellFormat(background_color=Color.from_hex("#F4CCCC")))df = ws.read_dataframe() # backend por defecto: pandas
ws.write_dataframe(df) # limpia la hoja y escribe desde A1
ws.write_dataframe(df, start_cell="B2", include_index=True, clear=False)
ws.read_dataframe(drop_empty_rows=True, drop_empty_cols=True, index_col="id")
mgr = SheetManager("Mi Hoja", "creds.json", dataframe_backend="polars")ws.insert_rows(2, number=3); ws.delete_cols(5) # filas/columnas (1-based)
ws.sort_range("A2:C100", (1, "asc"), (3, "desc")) # ordenar por columnas
ws.set_basic_filter("A1:C100") # filtro básico
ws.set_tab_color(Color.from_hex("#D9EAD3"))
ws.update_note("B2", "revisar"); ws.define_named_range("Datos", "A1:B100")
from gspreadmanager import ExportFormat
pdf = mgr.export() # bytes (PDF por defecto)
xlsx = mgr.export(ExportFormat.EXCEL)from dataclasses import dataclass
@dataclass
class Persona:
nombre: str
edad: int
activo: bool
personas = ws.read_as(Persona) # -> list[Persona], con tipos convertidos
ws.append_models([Persona("Ana", 30, True)])mgr = SheetManager("Mi Hoja", "creds.json",
cache=True, # memoiza lecturas, se invalida al escribir
rate_limit=1) # token bucket: ~1 operación/seg (no choca la cuota)
mgr.clear_cache() # refresco manual ante cambios externosfrom gspreadmanager.testing import InMemoryBackend
backend = InMemoryBackend()
backend.add_spreadsheet("MiDoc", {"Hoja1": [["nombre", "email"], ["Ana", "ana@x.com"]]})
mgr = backend.manager("MiDoc") # un SheetManager que no toca la redgspreadmanager read "Mi Doc" Hoja1 --format json --json-file creds.json
gspreadmanager append "Mi Doc" Hoja1 Ana ana@example.com --json-file creds.json
gspreadmanager export "Mi Doc" --format xlsx -o reporte.xlsx --json-file creds.jsonnueva = mgr.create_sheet("Reporte 2026", rows=500, cols=10) # devuelve un handle
mgr.delete_sheet("Borrador")
nuevo = mgr.create_spreadsheet("Reporte mensual")
copia = mgr.copy_spreadsheet(nuevo.id, title="Reporte (copia)")
mgr.list_spreadsheets(title="Reporte")
mgr.delete_spreadsheet(copia.id)
mgr.share("alguien@example.com", role="writer")
mgr.list_permissions()
mgr.remove_permission("alguien@example.com")with SheetManager("Mi Hoja", "creds.json") as mgr:
df = mgr.worksheet("Hoja1").read_dataframe()Las operaciones reintentan automáticamente ante errores transitorios (HTTP 429/500/503) con
backoff exponencial, configurable con max_retries y retry_backoff en SheetManager.
from gspreadmanager import InsertError
try:
ws.insert([["A", "B"]])
except InsertError as e:
print(f"No se pudo insertar: {e}")GSpreadManager sigue una arquitectura por capas (Clean Architecture / DDD táctico). La
dependencia de gspread queda aislada en infrastructure/: el dominio y la capa de
aplicación no la importan, lo que facilita testear con fakes y, eventualmente, sustituir el
cliente subyacente.
gspreadmanager/
├── domain/ # value objects (formato, rangos, reglas), schema, numericise, export, errores — sin I/O
├── ports/ # Protocols: sheets (client/spreadsheet/worksheet), auth, retry, rate_limit, dataframe
├── application/ # servicios de casos de uso (data, formatting, validation, worksheet,
│ # document, sharing, metadata, dataframe, row_model) — sin gspread
├── infrastructure/ # gspread adapters/client, auth, retry, rate_limit, cache, request_builders,
│ # pandas/polars adapters, native/ (spike de cliente REST)
├── testing/ # backend en memoria (InMemoryBackend) que implementa los puertos
├── facade.py # SheetManager + WorksheetContext (API público)
├── cli.py # CLI `gspreadmanager`
├── config.py
└── retry.py
Los puertos nominales (ClientPort/SpreadsheetPort/WorksheetPort) tienen cuatro
implementaciones intercambiables, verificadas por un test de contrato: adaptador de gspread
(por defecto), cliente REST nativo (spike), backend en memoria y wrappers de caché.
Dependencias: gspread (>=3.0), google-auth (>=2.0); pandas (>=1.2.4) y polars
(>=0.20) opcionales.
pip install -e ".[dev]"
ruff check . # lint
ruff format --check . # formato
mypy # type-check estricto
pytest # tests (con cobertura)- Hacé un fork y creá una branch (
git checkout -b feature/mi-feature). - Mantené verdes
ruff,mypyypytest, y agregá tests para lo nuevo. - Abrí un Pull Request.
MIT License — ver LICENSE.
- gspread — cliente de Google Sheets API
- google-auth — autenticación oficial de Google
- pandas — análisis de datos
Hecho con ❤️ por Pablo Alaniz