많은 응용 프로그램이 연결 문자열을 정적인 구성 값으로 저장하는 대신 동적으로 구축해야 합니다. 배치에 맞는 접근 방식을 선택하세요:
- 환경 변수: 컨테이너, CI/CD, 12단계 앱에 가장 적합합니다. 간단하고 널리 지지받는 기능입니다.
- JSON/YAML 설정 파일: 여러 환경(개발, 스테이징, 프로덕션)이 필요한 구조화된 구성이 필요한 애플리케이션에 가장 적합합니다.
- Azure Key Vault: 비밀이 중앙에서 관리되고 감사되어야 하는 운영 배포에 가장 적합합니다.
- 빌더 클래스: 사용자 입력으로 자동 이스케이프를 통해 연결 문자열을 구성해야 하는 라이브러리나 프레임워크에 가장 적합합니다.
기본 문자열 구성
F줄 사용
F-스트링은 빠른 스크립트와 프로토타입에 흔히 사용되는 접근법입니다. 값이 사용자 입력에서 오는 경우에는 이 패턴을 피하세요. mydb;Server=evil.com와 같은 악의적인 값이 연결 대상을 변경할 수 있기 때문입니다:
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)
조인 사용법
이 접근법은 join 키-값 쌍을 사전식 스타일의 함수 호출로 분리하는데, 긴 f-문자열보다 읽고 유지하기 쉽습니다. 또한 None 값도 자동으로 걸러 주므로 별도의 조건부 로직 없이도 옵션 매개변수를 전달할 수 있습니다:
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)
연결 문자열 빌더 클래스
빌더 클래스는 자동 이스케이프가 가능한 유창한 API를 제공합니다. 이 방법은 연결 매개변수가 서로 다른 출처에서 오는 라이브러리나 다중 테넌트 애플리케이션에서 유용합니다:
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())
환경 기반 구성
환경 변수에서
환경 변수에서 연결 매개변수를 읽으면 자격 증명이 소스 코드에 포함되지 않고, 로컬 개발, 컨테이너, CI/CD 파이프라인 전반에 걸쳐 작동합니다. 이 함수는 설정된 변수를 바탕으로 어떤 인증 방법을 사용할지 확인합니다:
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()
python-dotenv을 사용하여
패키지는 python-dotenv 파일에서 .env 키-값 쌍을 환경 변수로 로드하므로, 코드가 로컬 개발과 프로덕션 환경에서 자격 증명을 읽는 방식이 동일합니다.
.env 파일은 소스 제어에 포함되지 않으며(.gitignore에 추가), 반면 배포된 환경은 플랫폼의 시크릿 저장소를 통해 동일한 변수를 주입합니다.
pip install python-dotenv를 사용하여 설치합니다.
프로젝트 루트에 연결 매개변수를 포함한 파일을 생성하세요 .env :
# .env - add this file to .gitignore
SQL_SERVER=<server>.database.windows.net
SQL_DATABASE=<database>
SQL_USE_MSI=true
그 값을 불러와서 스크립트에 사용하세요:
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)
팁 (조언)
load_dotenv() 이미 환경에 설정된 변수를 덮어쓰지 않습니다. 운영 환경에서는 플랫폼을 통해 동일한 변수 이름(예: App Service 애플리케이션 설정 또는 컨테이너 환경 변수)을 설정하고 .env 파일은 아예 사용하지 마세요.
파일 기반 구성
JSON 구성에서
JSON 구성 파일을 통해 여러 환경(개발, 스테이징, 프로덕션)에 대한 연결 설정을 한 곳에서 정의할 수 있습니다. 함수는 파일을 읽고, 대상 환경을 선택한 뒤, 구조화된 설정에서 연결 문자열을 생성합니다:
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}")
YAML 설정에서
YAML 구성 파일은 JSON의 읽기 쉬운 대안입니다. 이들은 Python 프로젝트와 Kubernetes 배포에서 흔히 사용됩니다. 이 방법은 구조화된 YAML 파일에서 연결 설정을 읽고, 설정에서 정의된 인증 유형을 기반으로 연결 문자열을 생성합니다.
pip install pyyaml을 실행하여 패키지를 설치합니다.
프로젝트 내에서 파일을 생성하세요 database.yml :
database:
server: <server>.database.windows.net
name: <database>
authentication: msi
encrypt: true
그 다음 스크립트에서 해당 설정을 불러와서 사용하세요:
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")
Azure Key Vault 통합
운영 배포의 경우, 연결 자격 증명을 구성 파일이나 환경 변수 대신 Azure Key Vault에 저장하세요. Key Vault는 중앙 집중식 비밀 관리, 접근 감사, 자동 순환을 제공합니다. 필요한 패키지를 실행하는 pip install azure-keyvault-secrets azure-identity방법으로 설치하세요. 전체 공략은 Python용 Quickstart: Azure Key Vault 비밀 클라이언트 라이브러리를 참고하세요.
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)
특수 문자 처리
세미콜론과 중괄호 이스케이프
특수 문자가 포함된 연결 문자열 값은 이스케이프 처리해야 합니다. 값을 중괄호로 감싸고 {} 내부의 닫는 중괄호는 모두 두 번 쓰세요}:
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};"
자동 이스케이프가 있는 빌더
이 빌더 클래스는 모든 값을 자동으로 감싸므로 호출하는 쪽에서 이스케이프 규칙을 기억할 필요가 없습니다. 사용자 폼, 구성 API, 또는 세미콜론이나 대괄호가 포함된 값이 포함된 비밀 저장소 등 외부 입력에서 연결 매개변수를 사용할 때 사용하세요:
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
동적으로 생성된 연결 문자열을 사용하기 전에 실제로 연결되는지 확인하세요. 이 헬퍼 함수는 가벼운 쿼리를 시도하며 불리언 결과를 반환합니다:
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")