Construir cadeias de ligação programaticamente

Muitas aplicações precisam de construir cadeias de ligação dinamicamente em vez de as armazenar como valores de configuração estáticos. Escolha a abordagem que corresponda à sua implementação:

  • Variáveis de ambiente: Melhores para containers, CI/CD e aplicações de 12 fatores. Simples e amplamente suportado.
  • Ficheiros de configuração JSON/YAML: Melhores para aplicações com múltiplos ambientes (desenvolvimento, staging, produção) que necessitam de configuração estruturada.
  • Azure Key Vault: Ideal para implementações em produção onde os segredos têm de ser geridos e auditados centralmente.
  • Classe builder: Ideal para bibliotecas ou frameworks que precisam de construir strings de ligação a partir de input do utilizador com escape automático.

Construção básica das cordas

Use f-strings

As F-strings são uma abordagem comum para scripts rápidos e protótipos. Evite este padrão quando os valores provêm de input do utilizador, porque um valor malicioso como mydb;Server=evil.com pode alterar o destino da ligação:

import mssql_python

server = "<server>.database.windows.net"
database = "<database>"

connection_string = f"Server={server};Database={database};Authentication=ActiveDirectoryDefault;Encrypt=yes;"

conn = mssql_python.connect(connection_string)

Utilize a junção

A abordagem join separa pares chave-valor numa chamada de função do tipo dicionário, que é mais fácil de ler e manter do que uma f-string longa. Também filtra automaticamente os valores None, pelo que pode passar parâmetros opcionais sem lógica condicional adicional:

def build_connection_string(**kwargs) -> str:
    """Build connection string from keyword arguments."""
    return ";".join(f"{key}={value}" for key, value in kwargs.items() if value is not None)

conn_str = build_connection_string(
    Server="<server>.database.windows.net",
    Database="<database>",
    Authentication="ActiveDirectoryDefault",
    Encrypt="yes"
)

conn = mssql_python.connect(conn_str)

Classe construtora de cordas de ligação

Uma classe builder fornece uma API fluente com escape automático. Esta abordagem é útil em bibliotecas ou aplicações multitenant onde os parâmetros de ligação provêm de diferentes fontes:

import mssql_python

class ConnectionStringBuilder:
    """Builder for SQL Server connection strings."""

    def __init__(self):
        self._params = {}

    def server(self, value: str) -> "ConnectionStringBuilder":
        self._params["Server"] = value
        return self

    def database(self, value: str) -> "ConnectionStringBuilder":
        self._params["Database"] = value
        return self

    def trusted_connection(self) -> "ConnectionStringBuilder":
        self._params["Trusted_Connection"] = "yes"
        return self

    def sql_auth(self, username: str, password: str) -> "ConnectionStringBuilder":
        self._params["UID"] = username
        self._params["PWD"] = password
        return self

    def entra_default(self) -> "ConnectionStringBuilder":
        self._params["Authentication"] = "ActiveDirectoryDefault"
        return self

    def entra_msi(self, client_id: str = None) -> "ConnectionStringBuilder":
        self._params["Authentication"] = "ActiveDirectoryMSI"
        if client_id:
            self._params["UID"] = client_id
        return self

    def encrypt(self, value: bool = True) -> "ConnectionStringBuilder":
        self._params["Encrypt"] = "yes" if value else "no"
        return self

    def trust_server_certificate(self, value: bool = True) -> "ConnectionStringBuilder":
        self._params["TrustServerCertificate"] = "yes" if value else "no"
        return self

    def connect_timeout(self, seconds: int) -> "ConnectionStringBuilder":
        self._timeout = seconds
        return self

    def build(self) -> str:
        """Build the connection string."""
        return ";".join(f"{k}={v}" for k, v in self._params.items())

    def connect(self) -> mssql_python.Connection:
        """Build and connect."""
        return mssql_python.connect(self.build(), timeout=getattr(self, '_timeout', 0))


# Usage examples
# Microsoft Entra authentication (recommended)
conn = (ConnectionStringBuilder()
    .server("<server>.database.windows.net")
    .database("<database>")
    .entra_default()
    .encrypt()
    .connect())

# Azure with managed identity
conn = (ConnectionStringBuilder()
    .server("<server>.database.windows.net")
    .database("<database>")
    .entra_msi()
    .encrypt()
    .connect())

Configuração baseada no ambiente

A partir de variáveis ambientais

Ler os parâmetros de ligação das variáveis de ambiente mantém as credenciais fora do código-fonte e funciona tanto no desenvolvimento local como em contentores e pipelines de CI/CD. A função verifica qual o método de autenticação a usar com base nas variáveis que são definidas:

import os
import mssql_python

def get_connection_from_env() -> mssql_python.Connection:
    """Build connection from environment variables."""
    server = os.environ.get("SQL_SERVER")
    database = os.environ.get("SQL_DATABASE")

    if not server or not database:
        raise ValueError("SQL_SERVER and SQL_DATABASE environment variables required")

    # Check for authentication method
    if os.environ.get("SQL_USE_MSI", "").lower() == "true":
        # Azure Managed Identity
        conn_str = f"Server={server};Database={database};Authentication=ActiveDirectoryMSI;Encrypt=yes;"
    elif os.environ.get("SQL_TRUSTED_CONNECTION", "").lower() == "true":
        # Windows authentication
        conn_str = f"Server={server};Database={database};Trusted_Connection=yes;Encrypt=yes;"
    else:
        # SQL authentication
        username = os.environ.get("SQL_USERNAME")
        password = os.environ.get("SQL_PASSWORD")
        if not username or not password:
            raise ValueError("SQL_USERNAME and SQL_PASSWORD required for SQL authentication")
        conn_str = f"Server={server};Database={database};UID={username};PWD={password};Encrypt=yes;"

    return mssql_python.connect(conn_str)

# Usage
conn = get_connection_from_env()

Com python-dotenv

O pacote python-dotenv carrega pares chave-valor de um ficheiro .env para variáveis de ambiente, para que o seu código leia as credenciais do mesmo modo em desenvolvimento local e em produção. O .env ficheiro mantém-se fora do controlo de versões (adicione-o a .gitignore), enquanto ambientes implementados injetam as mesmas variáveis através do armazenamento secreto da plataforma.

Instale com pip install python-dotenv.

Crie um .env ficheiro na raiz do seu projeto com os parâmetros de ligação:

# .env - add this file to .gitignore
SQL_SERVER=<server>.database.windows.net
SQL_DATABASE=<database>
SQL_USE_MSI=true

Depois carregue e use esses valores no seu script:

from dotenv import load_dotenv
import os
import mssql_python

# Load .env file into os.environ (no-op if the file doesn't exist)
load_dotenv()

server = os.getenv("SQL_SERVER")
database = os.getenv("SQL_DATABASE")

if not server or not database:
    raise ValueError("SQL_SERVER and SQL_DATABASE must be set in .env or as environment variables")

use_msi = os.getenv("SQL_USE_MSI", "false").lower() == "true"

if use_msi:
    conn_str = f"Server={server};Database={database};Authentication=ActiveDirectoryMSI;Encrypt=yes;"
else:
    conn_str = f"Server={server};Database={database};Authentication=ActiveDirectoryDefault;Encrypt=yes;"

conn = mssql_python.connect(conn_str)

Sugestão

load_dotenv() não sobrescreve variáveis que já estão definidas no ambiente. Em produção, defina os mesmos nomes de variáveis na sua plataforma (por exemplo, definições da aplicação no App Service ou variáveis de ambiente do contentor) e ignore o ficheiro .env.

Configuração baseada em arquivo

A partir da configuração JSON

Um ficheiro de configuração JSON permite-lhe definir definições de ligação para múltiplos ambientes (desenvolvimento, staging, produção) num só local. A função lê o ficheiro, seleciona o ambiente alvo e constrói a cadeia de ligação a partir das definições estruturadas:

import json
import io
import mssql_python

def load_connection_from_json(config_file, environment: str = "development") -> str:
    """Load connection settings from a JSON config file or file-like object."""
    config = json.load(config_file)

    env_config = config.get(environment, {})
    db_config = env_config.get("database", {})

    params = {
        "Server": db_config.get("server"),
        "Database": db_config.get("database"),
        "Encrypt": "yes" if db_config.get("encrypt", True) else "no",
    }

    auth_type = db_config.get("authentication", "sql")
    if auth_type == "msi":
        params["Authentication"] = "ActiveDirectoryMSI"
    elif auth_type == "default":
        params["Authentication"] = "ActiveDirectoryDefault"
    elif auth_type == "windows":
        params["Trusted_Connection"] = "yes"
    else:
        params["UID"] = db_config.get("username")
        params["PWD"] = db_config.get("password")

    return ";".join(f"{k}={v}" for k, v in params.items() if v)

# Example: load from an inline JSON config (in production, use open("config.json"))
sample_config = json.dumps({
    "development": {
        "database": {
            "server": "localhost",
            "database": "devdb",
            "authentication": "windows",
            "encrypt": False
        }
    },
    "production": {
        "database": {
            "server": "prod.database.windows.net",
            "database": "proddb",
            "authentication": "msi",
            "encrypt": True
        }
    }
})

conn_str = load_connection_from_json(io.StringIO(sample_config), "production")
print(f"Connection string: {conn_str}")

A partir da configuração YAML

Os ficheiros de configuração YAML são uma alternativa legível ao JSON. São frequentemente usados em projetos Python e implementações do Kubernetes. Esta abordagem lê as definições de ligação de um ficheiro YAML estruturado e constrói a cadeia de ligação com base no tipo de autenticação definido na configuração.

Instale o pacote executando pip install pyyaml.

Crie um database.yml ficheiro no seu projeto:

database:
  server: <server>.database.windows.net
  name: <database>
  authentication: msi
  encrypt: true

Depois carrega e usa essas definições no teu script:

import yaml
import mssql_python

def load_from_yaml(config_path: str) -> mssql_python.Connection:
    """Load connection from YAML config."""
    with open(config_path) as f:
        config = yaml.safe_load(f)

    db = config["database"]

    parts = [
        f"Server={db['server']}",
        f"Database={db['name']}",
    ]

    if db.get("trusted_connection"):
        parts.append("Trusted_Connection=yes")
    elif db.get("authentication") == "msi":
        parts.append("Authentication=ActiveDirectoryMSI")
    else:
        parts.append(f"UID={db['username']}")
        parts.append(f"PWD={db['password']}")

    if db.get("encrypt", True):
        parts.append("Encrypt=yes")
    if db.get("trust_server_certificate"):
        parts.append("TrustServerCertificate=yes")

    return mssql_python.connect(";".join(parts))

conn = load_from_yaml("database.yml")

Integração com Azure Key Vault

Para implementações em produção, armazene as credenciais de ligação no Azure Key Vault em vez de em ficheiros de configuração ou variáveis de ambiente. O Key Vault oferece gestão centralizada de segredos, auditoria de acessos e rotação automática. Instale os pacotes necessários executando pip install azure-keyvault-secrets azure-identity. Para um guia completo, consulte Quickstart: Azure Key Vault secret client library for Python.

import os
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
import mssql_python

def get_connection_from_keyvault(vault_url: str) -> mssql_python.Connection:
    """Build connection using secrets from Azure Key Vault."""
    credential = DefaultAzureCredential()
    client = SecretClient(vault_url=vault_url, credential=credential)

    server = client.get_secret("sql-server").value
    database = client.get_secret("sql-database").value
    username = client.get_secret("sql-username").value
    password = client.get_secret("sql-password").value

    conn_str = f"Server={server};Database={database};UID={username};PWD={password};Encrypt=yes;"
    return mssql_python.connect(conn_str)

vault_url = os.environ.get("AZURE_KEY_VAULT_URL")
if vault_url:
    conn = get_connection_from_keyvault(vault_url)

Manipular caracteres especiais

Escape ponto e vírgulas e chavetas

Precisas de escapar dos valores de cadeia de ligação que contêm caracteres especiais. Coloque o valor entre chavetas {} e duplique quaisquer chavetas de fecho internas }:

def escape_value(value: str) -> str:
    """Escape special characters in connection string values."""
    if ";" in value or "{" in value or "}" in value:
        # Wrap in braces and escape internal braces
        value = value.replace("}", "}}")
        return "{" + value + "}"
    return value

# Password with semicolon
password = "my;complex;password"
escaped_password = escape_value(password)  # {my;complex;password}

conn_str = f"Server=<server>;Database=<database>;UID=<login>;PWD={escaped_password};"

Construtor com escape automático

Esta classe de construção encapsula automaticamente todos os valores, pelo que quem a utiliza não precisa de se lembrar das regras de escape. Use-o quando os parâmetros de ligação provêm de entradas externas, como formulários de utilizador, APIs de configuração ou armazenamentos secretos onde os valores podem conter pontos e vírgulas ou colchetes:

class SafeConnectionStringBuilder:
    """Connection string builder with automatic escaping."""

    SPECIAL_CHARS = {";", "{", "}"}

    def __init__(self):
        self._params = {}

    def _escape(self, value: str) -> str:
        if any(c in value for c in self.SPECIAL_CHARS):
            value = value.replace("}", "}}")
            return "{" + value + "}"
        return value

    def set(self, key: str, value: str) -> "SafeConnectionStringBuilder":
        self._params[key] = self._escape(value)
        return self

    def build(self) -> str:
        return ";".join(f"{k}={v}" for k, v in self._params.items())

# Safely handles special characters
builder = SafeConnectionStringBuilder()
builder.set("Server", "<server>.database.windows.net")
builder.set("Database", "<database>")
builder.set("PWD", "pass;word{with}special")  # Automatically escaped

conn_str = builder.build()

Validation

Antes de usar uma cadeia de ligação dinamicamente construída na sua aplicação, verifique se ela realmente se liga. Esta função auxiliar tenta uma consulta leve e retorna um resultado booleano:

import mssql_python

def validate_connection_string(conn_str: str) -> bool:
    """Validate a connection string by attempting to connect."""
    try:
        conn = mssql_python.connect(conn_str)
        cursor = conn.cursor()
        cursor.execute("SELECT 1")
        cursor.fetchone()
        conn.close()
        return True
    except mssql_python.Error as e:
        print(f"Connection failed: {e}")
        return False

# Test before using
conn_str = "Server=<server>.database.windows.net;Database=<database>;Authentication=ActiveDirectoryDefault;Encrypt=yes;"
if validate_connection_string(conn_str):
    print("Connection string is valid")