Metadata-Version: 2.4
Name: postgresqlfacil
Version: 0.3.0
Summary: O pacote PostgreSQLFacil oferece uma interface simplificada para interagir com o banco de dados PostgreSQL, abstraindo as operações comuns de SELECT, INSERT, UPDATE e DELETE em métodos convenientes e com integrações em DataFrames do pandas.
License: MIT
License-File: LICENSE
Author: eduardo.barreto
Author-email: edugobarreto@gmail.com
Requires-Python: >=3.8,<4
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Provides-Extra: psycopg2
Provides-Extra: psycopg2-binary
Requires-Dist: pandas (>=2.0.3,<3.0.0)
Description-Content-Type: text/markdown

# PostgreSQLFacil
O pacote PostgreSQLFacil oferece uma interface simplificada para interagir com o banco de dados PostgreSQL, abstraindo as operações comuns de SELECT, INSERT, UPDATE e DELETE em métodos convenientes e com integrações em DataFrames do pandas.

# Instalação
Para utilizar o pacote CalendarioFinanceiro, você precisa instalá-lo primeiro. Isso pode ser feito utilizando o gerenciador de pacotes pip. Execute o seguinte comando no terminal:

```shell
pip install PostgreSQLFacil
```

# Uso Básico
Aqui está um exemplo de como você pode usar o PostgreSQLFacil para realizar operações com o banco de dados PostgreSQL:

```py
from PostgreSQLFacil import ConectorPostgreSQL
import pandas as pd


# Configuração das informações de conexão
config = {
    "database": "seu_banco_de_dados",
    "user": "seu_usuario",
    "password": "sua_senha",
    "host": "seu_host",
    "port": "sua_porta",
}

# Inicialização do ConectorPostgreSQL
with ConectorPostgreSQL(**config) as SQL:
    # Executa uma consulta SELECT e obtém o resultado como um DataFrame
    df_consulta = SQL.executa_query_select('SELECT * FROM sua_tabela;')

    # Executa uma inserção de dados
    query_insert = 'INSERT INTO sua_tabela (coluna1, coluna2) VALUES (valor1, valor2);'
    SQL.executa_query_insert(query_insert)

    # Executa um update
    query_update = 'UPDATE sua_tabela SET coluna1 = novo_valor WHERE coluna2 = valor_alvo;'
    SQL.executa_query_update(query_update)

    # Executa um delete
    query_delete = 'DELETE FROM sua_tabela WHERE coluna = valor_alvo;'
    SQL.executa_query_delete(query_delete)
```

# Métodos Disponíveis
### executa_query_select(query: str) -> pd.DataFrame
Executa um SELECT statement e retorna o resultado como um DataFrame. Em caso de erro, retorna um DataFrame vazio.

### executa_query_insert(query: str, returning=False) -> int
Executa um INSERT statement e retorna o valor solicitado (geralmente o último ID inserido) se especificado. Retorna True em caso de sucesso.

### executa_query_update(query: str) -> bool
Executa um UPDATE statement e retorna True em caso de sucesso.

### executa_query_delete(query: str) -> bool
Executa um DELETE statement e retorna True em caso de sucesso.

### transforma_df_em_insert_statement(df: pd.DataFrame, tabela: str) -> str
Transforma um DataFrame em um INSERT statement para a tabela especificada.

# Novidades da 0.3.0
Tudo abaixo é **aditivo**: o que existia na 0.2.x continua com a mesma assinatura e o mesmo comportamento (inclusive engolir erros). Quem atualiza sem ler este README não percebe diferença.

## Por que existe
O padrão `TRUNCATE` + `transforma_df_em_insert_statement` + `executa_query_insert` tem três problemas em tabelas grandes:

1. `TRUNCATE` pede lock `ACCESS EXCLUSIVE` e o segura até o `COMMIT` — **bloqueia até SELECT** durante toda a carga.
2. O INSERT é uma string SQL gigante, montada **com o lock já preso**.
3. `executa_query_insert/update/delete` engolem exceção (`print` + `False`). Dentro de um `BEGIN`/`COMMIT` manual, um INSERT que falha passa em branco: a task termina "verde" com o dado velho.

## Erros de verdade: `levantar_erro=True`
```py
SQL.executa_query_insert(query, levantar_erro=True)   # propaga a exceção
SQL.executa_query_select(query, levantar_erro=True)   # "vazio" ≠ "falhou"
```
Também existe `SQL.executa_query(query, params=None) -> int` (sempre levanta; devolve `rowcount`; aceita `%s`).

## Transação: `transacao()`
```py
with SQL.transacao(lock_timeout_ms=5000):
    SQL.executa_query("DELETE FROM s.t WHERE dia = CURRENT_DATE")
    SQL.copia_df(df, "s.t")
# COMMIT ao sair; ROLLBACK + re-raise se algo falhar
```
Se já houver transação aberta (código antigo com `executa_query_update("BEGIN")`), `transacao()` apenas participa dela e **não** dá COMMIT.

## Carga rápida: `copia_df(df, tabela, colunas=None) -> int`
`COPY ... FROM STDIN` em streaming. Levanta exceção em erro; é atômico. Coluna inteira aceita float "redondo" (`5.0`, comum quando o pandas troca int por float por causa de NaN); `5.5` falha. `dict`/`list` viram JSON; `\x00` em texto é removido.

## Trocar a tabela sem bloquear leitores: `sincroniza_df`
```py
resultado = SQL.sincroniza_df(
    df, "schema.tabela", chaves=["id"],
    max_delecao_pct=50,          # aborta se a carga removeria >50% da tabela
)
# {"inseridas": 12, "atualizadas": 3, "removidas": 1, "inalteradas": 4180}
```
Carrega o df numa tabela temporária e, **numa transação curta**, faz `UPDATE` (só linhas que mudaram, via `IS DISTINCT FROM`), `INSERT` das novas e `DELETE` das que sumiram. Só lock de linha — **leitores não esperam**. Não exige PK/UNIQUE no banco, mas o df não pode ter chave nula/repetida (`ErroDadosInvalidos`).

Proteções: df vazio + deleção lança `ErroProtecaoDeDados` (use `permitir_vazio=True` se for intencional); `max_delecao_pct` (vale a partir de `delecao_pct_min_linhas`=50); `lock_timeout_ms` (padrão 30 s). Pressupõe **um único escritor** na tabela. Colunas `json` (não `jsonb`) não suportam o `IS DISTINCT FROM`.

`upsert_df(df, tabela, chaves)` é o mesmo sem o DELETE — pra carga **incremental** (o df só traz o que mudou).

## Tabela sem chave estável: `substitui_df`
```py
SQL.substitui_df(df, "schema.tabela")                       # TRUNCATE curto
SQL.substitui_df(df, "schema.tabela", usar_truncate=False)  # DELETE: não bloqueia leitor, gera bloat
```
Carrega antes numa temporária; o lock na tabela real dura só o `TRUNCATE` + `INSERT ... SELECT` dentro do servidor.

## Permissões necessárias
`sincroniza_df`/`upsert_df`/`substitui_df` criam tabela temporária (`TEMPORARY` no banco) e usam `UPDATE` (e `DELETE`, `TRUNCATE` conforme o caso) na tabela de destino.

## Compatibilidade
- Python `>=3.8`, pandas `^2.0.3` (inalterado); nenhuma dependência nova.
- Nenhum método, parâmetro posicional, campo do construtor ou formato de saída da 0.2.x mudou — `tests/test_compat_0_2.py` trava isso (inclusive que `transforma_df_em_insert_statement` gera o mesmo SQL byte a byte).
- Testes de integração contra Postgres real: `tests/test_integracao_pg.py` (ver cabeçalho do arquivo).

