pintos-utils
Utilitários Python reutilizáveis para aplicações. A biblioteca é independente de frameworks, oferece uma API tipada e mantém integrações externas em extras opcionais. Requer Python 3.11 ou superior.
Este README é uma referência prática para quem integra o pacote ou contribui com ele. Cada cartão mostra quando usar a API, um exemplo e o resultado ou efeito esperado.
Instalação
Instale somente o que sua aplicação usa:
| Recurso | Instalação | API principal |
|---|---|---|
| Modelos de email e contrato de envio | python -m pip install pintos-utils |
pintos_utils.email |
| Adaptador Gmail | python -m pip install "pintos-utils[gmail]" |
GmailEmailSender |
| Scripts SQL MySQL | python -m pip install "pintos-utils[mysql]" |
SqlScriptExecutor |
| Converter resultados para Polars | python -m pip install "pintos-utils[polars]" |
sql_script_result_to_dataframe |
| Exportar Polars para Excel | python -m pip install "pintos-utils[polars]" |
exportar_excel, exportar_excel_bytes |
| SQL, consultas/importações Polars + MySQL | python -m pip install "pintos-utils[mysql,polars]" |
execute_dataframe, execute_file_dataframe, query_dataframe, import_dataframe |
O pacote base não instala dependências de runtime. A API de email pode ser importada sem Google SDK; a API de banco pode ser importada sem o driver MySQL; as operações que usam essas integrações exigem os extras correspondentes.
Índice das APIs públicas
| Módulo | Nomes públicos |
|---|---|
pintos_utils.email |
EmailAddress, EmailAttachment, EmailMessage, EmailSender, SendResult, EmailError, EmailValidationError, EmailConfigurationError, EmailAuthenticationError, EmailProviderError |
pintos_utils.email.providers.gmail |
GmailEmailSender, GMAIL_SEND_SCOPE |
pintos_utils.database |
SqlScriptExecutor, SqlScriptResult, SqlScriptError, sql_script_result_to_dataframe, query_dataframe, import_dataframe, ImportMode, DataFrameImportResult, DataFrameDatabaseError |
pintos_utils.polars_excel |
exportar_excel, exportar_excel_bytes |
Cartão: instalar com uv
uv add "pintos-utils[gmail,mysql,polars]"
Resultado: o pyproject.toml da aplicação registra o pacote com os três
extras. Remova da lista qualquer integração que a aplicação não use; para
instalar somente o pacote base, execute uv add pintos-utils.
Cartão: instalar deste checkout
uv pip install -e ".[gmail,mysql,polars]"
Resultado: o pacote fica instalado em modo editável com todas as integrações opcionais disponíveis.
Para instalar de um repositório Git por SSH:
uv add "pintos-utils[gmail] @ git+ssh://git@github.com/PINTOS-LTDA/python-utils.git@REVISION"
Substitua REVISION por uma tag revisada ou por um hash completo de commit.
Configure o acesso SSH no ambiente consumidor; não inclua senhas ou tokens na
URL nem no arquivo de dependências.
Referência de email
Os modelos públicos são EmailAddress, EmailAttachment, EmailMessage e
SendResult. Eles são dataclasses imutáveis. O protocolo EmailSender permite
trocar o provedor sem mudar o código que constrói a mensagem.
| API | Assinatura |
|---|---|
EmailAddress |
EmailAddress(address, display_name=None) |
EmailAttachment |
EmailAttachment(filename, content, content_type=None) |
EmailMessage |
EmailMessage(sender, subject, to=(), cc=(), bcc=(), text_body=None, html_body=None, attachments=()) |
EmailSender |
send(message, /) -> SendResult |
SendResult |
SendResult(provider, provider_message_id=None) |
Cartão: criar uma mensagem
Quando usar: para montar uma mensagem independente do provedor.
from pintos_utils.email import EmailAddress, EmailMessage
message = EmailMessage(
sender=EmailAddress("relatorios@example.com", "Relatórios"),
to=(EmailAddress("ana@example.net", "Ana"),),
cc=(EmailAddress("equipe@example.net"),),
subject="Relatório mensal",
text_body="O relatório está pronto.",
html_body="<p>O relatório está pronto.</p>",
)
print(message.subject)
print(message.to[0].address)
Saída esperada:
Relatório mensal
ana@example.net
Notas: informe ao menos um destinatário entre to, cc e bcc, e ao
menos um entre text_body e html_body. Se fornecer ambos os formatos, o
provedor poderá enviar as versões como alternativas. Os campos de cabeçalho
rejeitam quebras de linha.
Cartão: anexar bytes
Quando usar: quando a aplicação já tem o conteúdo do anexo em memória.
from pintos_utils.email import EmailAddress, EmailAttachment, EmailMessage
attachment = EmailAttachment(
filename="resumo.csv",
content=b"sku,quantity\nA-100,12\n",
content_type="text/csv",
)
message = EmailMessage(
sender=EmailAddress("relatorios@example.com"),
to=(EmailAddress("ana@example.net"),),
subject="Resumo de estoque",
text_body="Segue o arquivo.",
attachments=(attachment,),
)
print(message.attachments[0].filename, message.attachments[0].content_type)
Saída esperada: resumo.csv text/csv
Notas: content deve ser bytes. O pacote não abre caminhos de arquivos;
a aplicação decide como carregar ou gerar o conteúdo.
Cartão: implementar outro provedor
Quando usar: para integrar um provedor próprio sem acoplar a aplicação ao Gmail.
from pintos_utils.email import EmailAddress, EmailMessage, EmailSender, SendResult
message = EmailMessage(
sender=EmailAddress("relatorios@example.com"),
to=(EmailAddress("ana@example.net"),),
subject="Resumo de estoque",
text_body="Segue o resumo.",
)
class ConsoleSender:
def send(self, message: EmailMessage, /) -> SendResult:
print(f"Enviando: {message.subject}")
return SendResult(provider="console")
def send_report(sender: EmailSender, message: EmailMessage) -> SendResult:
return sender.send(message)
result = send_report(ConsoleSender(), message)
print(result)
Saída esperada:
Enviando: Resumo de estoque
SendResult(provider='console', provider_message_id=None)
EmailSender é um protocolo estrutural: uma implementação precisa oferecer
send(message, /) -> SendResult.
Cartão: enviar com Gmail
Quando usar: para submeter uma mensagem pelo adaptador Gmail opcional.
import os
from google.oauth2.credentials import Credentials
from pintos_utils.email import EmailAddress, EmailMessage
from pintos_utils.email.providers.gmail import GMAIL_SEND_SCOPE, GmailEmailSender
message = EmailMessage(
sender=EmailAddress("relatorios@example.com"),
to=(EmailAddress("ana@example.net"),),
subject="Relatório mensal",
text_body="O relatório está pronto.",
)
credentials = Credentials(
token=None,
refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
token_uri=os.environ["GOOGLE_TOKEN_URI"],
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
scopes=[GMAIL_SEND_SCOPE],
)
sender = GmailEmailSender(credentials)
result = sender.send(message)
print(result.provider)
print(result.provider_message_id)
Resultado: provider será gmail; provider_message_id poderá conter o
identificador opaco devolvido pelo Gmail.
Notas: a aplicação é responsável pelo consentimento OAuth e pelo
armazenamento/renovação persistente do refresh token. O adaptador usa o escopo
gmail.send, pode renovar credenciais com refresh token e não inicia um fluxo
interativo. O resultado indica que o Gmail aceitou a solicitação, não confirma
a entrega final. O adaptador não tenta reenvio automático.
Cartão: tratar erros de email
Quando usar: para tratar falhas do contrato de email sem depender dos detalhes do SDK do provedor.
from pintos_utils.email import (
EmailAuthenticationError,
EmailConfigurationError,
EmailError,
EmailProviderError,
EmailMessage,
EmailSender,
EmailValidationError,
SendResult,
)
def send_safely(sender: EmailSender, message: EmailMessage) -> SendResult | None:
try:
return sender.send(message)
except EmailValidationError as error:
print("Revise os dados da mensagem:", error)
except EmailConfigurationError as error:
print("Revise a configuração:", error)
except EmailAuthenticationError as error:
print("Revise ou renove as credenciais:", error)
except EmailProviderError as error:
print(error.provider, error.retryable)
except EmailError as error:
print("Falha de email:", error)
return None
EmailError é a classe base das exceções públicas de email.
Em caso de sucesso, send_safely retorna SendResult; quando trata uma dessas
exceções, retorna None.
Referência: EmailProviderError.retryable é uma indicação True, False
ou None; não é uma política de retry. Um timeout pode ocorrer depois de o
provedor aceitar a mensagem, então considere o risco de envio duplicado antes
de tentar novamente.
Referência de banco de dados
O consumidor cria, configura e fecha a conexão MySQL. pintos-utils cria e
fecha cursores, mas não fecha a conexão fornecida.
| API | Assinatura |
|---|---|
SqlScriptExecutor |
SqlScriptExecutor(connection) |
execute |
execute(script, params=None, commit=False) -> SqlScriptResult |
execute_file |
execute_file(file_path, params=None, commit=False, encoding="utf-8") -> SqlScriptResult |
execute_dataframe |
execute_dataframe(script, params=None, commit=False) -> polars.DataFrame |
execute_file_dataframe |
execute_file_dataframe(file_path, params=None, commit=False, encoding="utf-8") -> polars.DataFrame |
sql_script_result_to_dataframe |
sql_script_result_to_dataframe(result) -> polars.DataFrame |
query_dataframe |
query_dataframe(connection, query, params=None) -> polars.DataFrame |
import_dataframe |
import_dataframe(connection, dataframe, table_name, *, mode, key_columns=None, batch_size=1000) -> DataFrameImportResult |
Cartão: abrir uma conexão MySQL
Quando usar: antes de chamar o executor SQL ou as operações Polars.
import os
import mysql.connector
connection = mysql.connector.connect(
host=os.environ["MYSQL_HOST"],
port=int(os.environ.get("MYSQL_PORT", "3306")),
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"],
autocommit=False,
)
Resultado: uma conexão configurada para o banco da aplicação. Feche-a no
código consumidor, normalmente em um bloco try/finally ou context manager
suportado pelo driver.
Cartão: executar SQL com parâmetros nomeados
Quando usar: para executar um script SQL em memória e receber as linhas do último comando que retornou resultado.
from pintos_utils.database import SqlScriptExecutor
executor = SqlScriptExecutor(connection)
result = executor.execute(
"SELECT :store_id AS store_id, :sku AS sku",
params={"store_id": 18, "sku": "A-100"},
)
print(result.rows)
Saída esperada: [{'store_id': 18, 'sku': 'A-100'}]
Use :name para valores. O driver recebe os valores separadamente do SQL; os
marcadores não substituem nomes de tabela ou coluna. Os nomes de identificadores
dinâmicos devem vir de uma allowlist da aplicação.
Cartão: executar um arquivo SQL
Quando usar: para manter consultas e scripts versionados em arquivos.
Este exemplo usa executor criado no cartão de parâmetros nomeados.
from datetime import date
from pathlib import Path
result = executor.execute_file(
Path("sql/estoque_por_loja.sql"),
params={"data": date(2026, 10, 1), "store_id": 18},
encoding="utf-8",
)
print(result.statements_executed)
print(result.rows[:1])
Resultado: execute_file() lê o arquivo e devolve um SqlScriptResult,
assim como execute().
O arquivo de exemplo pode conter:
SELECT :data AS report_date, :store_id AS store_id;
Cartão: controlar o commit do script
Quando usar: quando o consumidor quer que o executor confirme o trabalho após todas as instruções terem sucesso.
result = executor.execute(
"UPDATE products SET quantity = :quantity WHERE sku = :sku",
params={"quantity": 12, "sku": "A-100"},
commit=True,
)
print(result.statements_executed, result.affected_rows)
Resultado esperado: 1 1 se uma linha foi alterada.
Notas: commit é False por padrão. Com False, o executor não faz commit
nem rollback; o consumidor controla a transação. Com True, ele rejeita uma
conexão que já esteja em uma transação e faz um commit após o sucesso. Falhas
não provocam rollback automático. DDL e outros comandos podem causar commits
implícitos no MySQL.
Cartão: consultar SqlScriptResult
Quando usar: para ler contagens de execução e linhas retornadas pelo script.
result = executor.execute(
"UPDATE products SET quantity = :quantity WHERE sku = :sku",
params={"quantity": 12, "sku": "A-100"},
)
print(result.statements_executed)
print(result.affected_rows)
print(result.rows)
Campos: statements_executed conta comandos concluídos; affected_rows
soma contagens não negativas de comandos DML; rows e columns mantêm as
linhas e nomes ordenados do último comando que produziu resultados. As linhas
são materializadas em memória.
Cartão: converter o resultado SQL para Polars
Quando usar: quando o código precisa das contagens de execução e também quer continuar o processamento dos dados com Polars.
from pintos_utils.database import sql_script_result_to_dataframe
result = executor.execute("SELECT sku, quantity FROM products")
frame = sql_script_result_to_dataframe(result)
print(result.affected_rows)
print(frame.shape, frame.columns)
Resultado: frame contém somente as linhas e colunas do último resultado;
statements_executed e affected_rows continuam no SqlScriptResult.
Notas: instale pintos-utils[polars]. Colunas conhecidas são preservadas
mesmo quando não há linhas, mas seus dtypes não são especificados. Para um
resultado criado manualmente sem metadados, a ordem das chaves da primeira linha
define as colunas. Linhas com chaves diferentes e valores que Polars não consiga
representar fielmente geram DataFrameDatabaseError com operation="convert".
Cartão: executar SQL e receber um DataFrame
Quando usar: quando o resultado final do script é suficiente e as contagens de execução não são necessárias.
frame = executor.execute_dataframe(
"SELECT sku, quantity FROM products WHERE store_id = :store_id",
params={"store_id": 18},
commit=True,
)
print(frame.shape)
print(frame.head())
Resultado: um polars.DataFrame com as linhas e colunas ordenadas do último
comando que produziu resultados. Um comando posterior sem linhas não apaga esse
resultado. Um script sem conjunto de resultados devolve um DataFrame com shape
(0, 0).
Notas: instale pintos-utils[mysql,polars]. Com commit=True, o executor
converte o resultado antes do commit; scripts vazios ou só com comentários são
no-op. Se a conversão falhar, tenta rollback e guarda uma falha de rollback em
cleanup_exception. Com commit=False, não faz commit nem rollback. Falhas SQL,
de leitura ou de fechamento do cursor mantêm o comportamento atual do executor
e não adicionam rollback automático.
Cartão: executar um arquivo SQL para Polars
Quando usar: para manter consultas versionadas em arquivos e receber um
DataFrame sem materializar um SqlScriptResult na aplicação.
frame = executor.execute_file_dataframe(
"reports/replenishments.sql",
params={"store_id": 18},
encoding="utf-8",
)
Resultado: execute_file_dataframe() lê o arquivo com a codificação
informada e aplica as mesmas regras de binding, seleção do último resultado e
transação de execute_dataframe().
Cartão: tratar erros SQL
Quando usar: para identificar a instrução com falha e inspecionar a causa original.
from pintos_utils.database import SqlScriptError
try:
executor.execute("SELECT * FROM products WHERE sku = :sku", params={})
except SqlScriptError as error:
print(error.statement_index)
print(error.original_exception)
statement_index é baseado em 1 quando um comando específico falha.
original_exception e __cause__ expõem a causa disponível. A mensagem padrão
não copia os valores vinculados.
Cartão: conhecer os limites do executor SQL
Quando usar: antes de escolher o executor para scripts com DDL, procedures ou instruções não convencionais.
SqlScriptExecutor processa instruções convencionais em ordem usando uma
conexão e uma sessão. Suporta USE, SELECT, INSERT, UPDATE, DELETE,
CREATE TEMPORARY TABLE e DROP TEMPORARY TABLE; devolve somente o resultado
final produzido pelo script.
Ele rejeita controles transacionais e atribuições à variável de sistema
autocommit. Procedures, corpos de trigger, diretivas de cliente DELIMITER e
comentários executáveis MySQL não são suportados. O parser não valida toda a
sintaxe MySQL. Para detalhes, consulte o guia de execução SQL.
Referência de DataFrames Polars
Instale ambos os extras para conectar ao MySQL e usar Polars:
python -m pip install "pintos-utils[mysql,polars]"
Para executar os cartões de importação, prepare uma tabela existente por uma migração ou ferramenta de schema da aplicação:
CREATE TABLE products (
sku VARCHAR(20) NOT NULL PRIMARY KEY,
quantity INT NOT NULL
) ENGINE=InnoDB;
import_dataframe não cria nem altera essa tabela.
Cartão: consultar SQL para um DataFrame
Quando usar: para receber em memória o resultado de uma instrução em um
polars.DataFrame.
from pintos_utils.database import query_dataframe
frame = query_dataframe(
connection,
"SELECT :store_id AS store_id, :sku AS sku",
params={"store_id": 18, "sku": "A-100"},
)
print(frame.shape)
print(frame.to_dicts())
Saída esperada:
(1, 2)
[{'store_id': 18, 'sku': 'A-100'}]
Notas: valores são vinculados, não interpolados. A operação aceita uma
instrução suportada que retorna um único conjunto de resultados, materializa
todo o DataFrame, inicia e controla uma transação, faz commit após o sucesso e
tentará rollback após uma falha. Ela rejeita uma transação pré-existente,
CALL, controle transacional, comandos com commit implícito e múltiplas
instruções. Resultado vazio preserva nomes e ordem das colunas; os dtypes não
são garantidos.
Cartão: importar com APPEND
Quando usar: para inserir todas as linhas em uma tabela existente.
import polars as pl
from pintos_utils.database import ImportMode, import_dataframe
incoming = pl.DataFrame({"sku": ["A-100", "B-200"], "quantity": [12, 7]})
result = import_dataframe(
connection,
incoming,
"products",
mode=ImportMode.APPEND,
batch_size=1000,
)
print(result)
Saída esperada: DataFrameImportResult(rows_processed=2, batches_processed=1)
Notas: o alvo deve ser uma tabela existente com engine transacional. A operação converte e valida os dados, envia lotes parametrizados, faz um commit ao final e tenta reverter todos os lotes quando ocorre falha. Um DataFrame vazio não abre uma transação.
Cartão: importar com UPSERT
Quando usar: para atualizar registros existentes e inserir os novos por uma chave definida pela aplicação.
import polars as pl
from pintos_utils.database import ImportMode, import_dataframe
changes = pl.DataFrame({"sku": ["A-100", "C-300"], "quantity": [15, 4]})
result = import_dataframe(
connection,
changes,
"products",
mode=ImportMode.UPSERT,
key_columns=["sku"],
batch_size=1000,
)
print(result.rows_processed, result.batches_processed)
Saída esperada: 2 1
Notas: key_columns precisa corresponder a uma chave primária ou índice
único completo do alvo. Chaves nulas ou duplicadas nos dados de origem são
rejeitadas. Os dados passam por uma tabela temporária da sessão, depois linhas
correspondentes são atualizadas e as demais inseridas. Conflitos em outro
índice único falham e revertem a transação.
Cartão: conversões e erros de DataFrame
Quando usar: para entender os tipos aceitos e inspecionar falhas de importação.
O import valida tipo e faixa antes de gravar: inteiros, booleanos, floats
finitos, Decimal, strings, bytes, Date, Datetime sem fuso e Time são
aceitos quando representáveis sem perda pelo tipo de destino. Conversões
incompatíveis, truncamento de texto/bytes, precisão decimal ou temporal
insuficiente e tipos aninhados são rejeitados.
import polars as pl
from pintos_utils.database import (
DataFrameDatabaseError,
ImportMode,
import_dataframe,
)
bad_frame = pl.DataFrame({"sku": ["A-100"], "quantity": [12.5]})
try:
result = import_dataframe(
connection,
bad_frame,
"products",
mode=ImportMode.APPEND,
)
except DataFrameDatabaseError as error:
print(error.operation)
print(error.original_exception)
print(error.cleanup_exception)
DataFrameDatabaseError.operation será query, import ou convert. Causas
originais e falhas de limpeza ficam disponíveis nos atributos correspondentes;
mensagens e logs padrão não incluem valores vinculados nem linhas.
Para detalhes sobre transações, conversões e limitações, consulte o guia de operações de banco de dados.
Exportação de DataFrames Polars para Excel
Instale o extra polars para habilitar as duas funções de exportação; a
instalação base continua sem dependências de runtime:
python -m pip install "pintos-utils[polars]"
| API | Assinatura |
|---|---|
exportar_excel |
exportar_excel(df, arquivo, aba="Dados") -> None |
exportar_excel_bytes |
exportar_excel_bytes(df, aba="Dados") -> bytes |
Cartão: salvar um DataFrame em arquivo Excel
Quando usar: para criar ou substituir um arquivo .xlsx no caminho escolhido.
from pathlib import Path
import polars as pl
from pintos_utils.polars_excel import exportar_excel
relatorio = pl.DataFrame(
{
"produto": ["Café", "Chá"],
"quantidade": [12, 7],
}
)
exportar_excel(relatorio, Path("relatorio.xlsx"))
exportar_excel(relatorio, "resumo.xlsx", aba="Resumo")
Resultado: o primeiro workbook usa a planilha Dados; o segundo usa
Resumo. Cada workbook contém uma planilha e preserva a ordem das colunas e
linhas.
Notas: datas usam dd/mm/yyyy, data e hora usam
dd/mm/yyyy hh:mm:ss, horas usam hh:mm:ss, inteiros usam #,##0 e floats e
decimais usam #,##0.00. A planilha aplica o estilo de tabela Medium 2,
dimensiona colunas pelo conteúdo, inclui filtros, congela a primeira linha e
oculta as linhas de grade. Um DataFrame sem colunas gera uma planilha vazia sem
tabela sintética.
Cartão: obter um workbook Excel como bytes
Quando usar: para enviar o workbook a outro componente sem gravar primeiro um arquivo temporário.
import polars as pl
from pintos_utils.polars_excel import exportar_excel_bytes
relatorio = pl.DataFrame({"produto": ["Café"], "quantidade": [12]})
payload = exportar_excel_bytes(relatorio, aba="Resumo")
print(type(payload).__name__, len(payload))
Saída esperada: bytes contendo um arquivo .xlsx completo com a planilha
Resumo. O workbook é finalizado antes de os bytes serem retornados, e a
operação não cria arquivo no disco.
Erros: destinos que não podem ser gravados, nomes inválidos de planilha e valores não suportados pelo writer geram exceções observáveis. A exportação não descarta dados silenciosamente. Nenhuma das funções modifica o DataFrame de entrada.
Desenvolvimento do pacote
Use Python 3.11 e uv. Para executar a suíte completa com todas as integrações opcionais instaladas:
uv sync --locked --group dev --extra gmail --extra mysql --extra polars
uv run pytest --cov=pintos_utils --cov-report=term-missing
uv run ruff check .
uv run ruff format --check .
uv run mypy src scripts/release_preflight.py
uv build --no-sources
O pacote segue Semantic Versioning: correções compatíveis incrementam patch, adições compatíveis incrementam minor e mudanças incompatíveis na API pública incrementam major. Consulte também os guias de email, banco de dados e publicação.
Metadata
Release files for pintos-utils 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pintos_utils-1.4.0.tar.gz | 66.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pintos_utils-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.1 kB
Release files / pintos_utils-1.4.0.tar.gz
| Download URL | pintos_utils-1.4.0.tar.gz |
|---|---|
| Size | 66.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
384db9e2a145da7f7b4224e965e038d5f0bed91e7b868e465553eb615c014f00
|
|
BLAKE2b-256 checksum How to use checksums |
be38f5b4f8ddb9ad84e9948a61e3e66d3856016e041068865c28a855ca3f7784
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / pintos_utils-1.4.0-py3-none-any.whl
| Download URL | pintos_utils-1.4.0-py3-none-any.whl |
|---|---|
| Size | 35.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d38e1eee2d0f4c84436b8e2727934bfdb69dea1a7fad8a31da2529cf4e92b6e8
|
|
BLAKE2b-256 checksum How to use checksums |
059cc2810aa77436e1cfdf680f21ac36a987c5765d713d7acb2c1c6841cf9718
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for pintos-utils 1.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pintos_utils-1.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.