Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
planetscale-sdk【部署与运维】Integrates PlanetScale using mysql-connector-python with patterns for database branching, deploy requests, schema management, connection pooling, and insights for serverless MySQL.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: planetscale-sdk description: Integrates PlanetScale using mysql-connector-python with patterns for database branching, deploy requests, schema management, connection pooling, and insights for serverless MySQL. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: planetscale, mysql connector, database branching, planetscale api, how do i connect to planetscale from python, serverless mysql, deploy request, vitess archetypes:
Integrates PlanetScale serverless MySQL using mysql-connector-python (and the PlanetScale API via HTTP) with patterns for database branching, deploy requests, schema management, connection with SSL, connection pooling for serverless environments, and querying with Python.
mysql.connector.connect() with SSL for database connections — never without SSLmysql.connector.pooling.MySQLConnectionPool for connection pooling in web apps/v1/databases, /v1/branches) for branch and deploy request managementALTER TABLE ... ONLINE for schema changes (PlanetScale's non-blocking DDL)sqlparse or sqlglot for linting migrations before deploy requestsSELECT /*+ SET_VAR(session_timeout=60) */ for long-running queries in productionUse this skill when:
import mysql.connector
from mysql.connector import Error as MySQLError
from mysql.connector.pooling import MySQLConnectionPool
import os
# PlanetScale requires SSL — always use ssl_ca or ssl_mode
config = {
"host": os.environ["PLANETSCALE_HOST"], # e.g. us-east.connect.psdb.cloud
"port": 3306,
"database": os.environ["PLANETSCALE_DATABASE"],
"user": os.environ["PLANETSCALE_USERNAME"],
"password": os.environ["PLANETSCALE_PASSWORD"],
"ssl_ca": "/etc/ssl/cert.pem", # Path to CA certificate
"ssl_mode": "VERIFY_IDENTITY", # or "REQUIRED" if CA path fails
"connection_timeout": 10,
"pool_name": "planetscale_pool",
"pool_size": 5,
}
# Single connection
conn = mysql.connector.connect(**config)
Checkpoint: Verify the connection with conn.is_connected(). PlanetScale requires SSL — never set ssl_mode to DISABLED. Use VERIFY_IDENTITY for production (prevents MITM) and REQUIRED if you can't configure the CA path.
def query_orders(
conn,
customer_id: str,
limit: int = 50,
) -> list[dict]:
"""Query orders with parameterized SQL."""
query = """
SELECT id, order_date, total, status, currency
FROM orders
WHERE customer_id = %s
ORDER BY order_date DESC
LIMIT %s
"""
with conn.cursor(dictionary=True) as cursor:
cursor.execute(query, (customer_id, limit))
return cursor.fetchall()
def create_order(conn, order_data: dict) -> int:
"""Insert a new order and return its ID."""
query = """
INSERT INTO orders (customer_id, total, status, currency)
VALUES (%(customer_id)s, %(total)s, %(status)s, %(currency)s)
"""
with conn.cursor() as cursor:
cursor.execute(query, order_data)
conn.commit()
return cursor.lastrowid
Checkpoint: PlanetScale runs MySQL 8.0+ via Vitess. Use cursor(dictionary=True) for dict results. Always call conn.commit() for write operations. PlanetScale autocommits DDL but explicit commit is needed for DML.
class PlanetScalePool:
"""Thread-safe connection pool for PlanetScale."""
def __init__(self, pool_size: int = 10):
self._pool = MySQLConnectionPool(
pool_name="pscale_pool",
pool_size=pool_size,
pool_reset_session=True,
**get_connection_config(),
)
def execute(self, query: str, params: tuple | dict | None = None) -> list[dict]:
"""Acquire a connection, execute, return results."""
conn = self._pool.get_connection()
try:
with conn.cursor(dictionary=True) as cursor:
cursor.execute(query, params or ())
if cursor.description: # SELECT-like query
return cursor.fetchall()
conn.commit()
return []
except MySQLError as exc:
conn.rollback()
raise RuntimeError(f"Query failed: {exc}") from exc
finally:
conn.close() # Returns to pool
pool = PlanetScalePool()
def get_user_orders(user_id: str) -> list[dict]:
return pool.execute(
"SELECT * FROM orders WHERE customer_id = %s ORDER BY created_at DESC LIMIT 50",
(user_id,),
)
Checkpoint: PlanetScale's connection proxy handles serverless connection surges. Pool size of 5-10 is typically sufficient. Close connections with conn.close() to return them to the pool — never leave connections open in serverless environments.
import httpx
class PlanetScaleAPI:
"""HTTP client for PlanetScale management API."""
BASE_URL = "https://api.planetscale.com/v1"
def __init__(self, service_token: str):
self._client = httpx.Client(
base_url=self.BASE_URL,
headers={
"Authorization": f"Bearer {service_token}",
"Content-Type": "application/json",
},
)
def list_branches(self, org: str, db: str) -> list[dict]:
"""List all branches for a database."""
resp = self._client.get(f"/organizations/{org}/databases/{db}/branches")
resp.raise_for_status()
return resp.json()["data"]
def create_branch(self, org: str, db: str, name: str, parent: str = "main") -> dict:
"""Create a new database branch from a parent branch."""
resp = self._client.post(
f"/organizations/{org}/databases/{db}/branches",
json={"name": name, "parent": parent},
)
resp.raise_for_status()
return resp.json()["data"]
def create_deploy_request(
self,
org: str,
db: str,
into_branch: str,
from_branch: str,
notes: str = "",
) -> dict:
"""Create a deploy request to merge schema changes."""
resp = self._client.post(
f"/organizations/{org}/databases/{db}/deploy-requests",
json={
"into_branch": into_branch,
"from_branch": from_branch,
"notes": notes,
},
)
resp.raise_for_status()
return resp.json()["data"]
def deploy_request_status(self, org: str, db: str, dr_id: int) -> dict:
"""Check deploy request status (pending, ready, deploying, complete)."""
resp = self._client.get(
f"/organizations/{org}/databases/{db}/deploy-requests/{dr_id}",
)
resp.raise_for_status()
return resp.json()["data"]
Checkpoint: The PlanetScale API requires a service token (not the database password). Create tokens in the PlanetScale dashboard under Settings > Service Tokens. Deploy requests have states: pending → ready → deploying → complete (or closed on conflict).
def run_migration(
api: PlanetScaleAPI,
org: str,
db: str,
branch_name: str,
migration_sql: str,
) -> dict:
"""Run a schema migration through PlanetScale's branching workflow."""
# 1. Create a branch for the migration
branch = api.create_branch(org, db, branch_name)
# 2. Connect to the branch and run migration
branch_config = get_connection_config(branch_name=branch_name)
conn = mysql.connector.connect(**branch_config)
try:
with conn.cursor() as cursor:
for statement in migration_sql.split(";"):
statement = statement.strip()
if statement:
cursor.execute(statement)
finally:
conn.close()
# 3. Create a deploy request
dr = api.create_deploy_request(org, db, "main", branch_name, notes=f"Migration: {branch_name}")
return {"branch": branch["name"], "deploy_request_id": dr["id"]}
from sqlalchemy import create_engine, text
def create_planetscale_engine() -> Engine:
"""Create a SQLAlchemy engine for PlanetScale."""
connection_string = (
f"mysql+mysqlconnector://{os.environ['PLANETSCALE_USERNAME']}:"
f"{os.environ['PLANETSCALE_PASSWORD']}@"
f"{os.environ['PLANETSCALE_HOST']}:3306/"
f"{os.environ['PLANETSCALE_DATABASE']}"
"?ssl_ca=/etc/ssl/cert.pem&ssl_mode=VERIFY_IDENTITY"
)
return create_engine(connection_string, pool_size=5, max_overflow=10)
def query_with_sqlalchemy(engine: Engine, user_id: str) -> list[dict]:
with engine.connect() as conn:
result = conn.execute(
text("SELECT * FROM orders WHERE customer_id = :uid ORDER BY created_at DESC"),
{"uid": user_id},
)
return [row._asdict() for row in result]
# ❌ BAD — Connecting without SSL
def connect_bad():
return mysql.connector.connect(
host="us-east.connect.psdb.cloud",
user="user",
password="pass",
database="mydb",
# No SSL config — will fail on PlanetScale!
)
# ✅ GOOD — SSL connection with VERIFY_IDENTITY
def connect_good():
return mysql.connector.connect(
host=os.environ["PLANETSCALE_HOST"],
user=os.environ["PLANETSCALE_USERNAME"],
password=os.environ["PLANETSCALE_PASSWORD"],
database=os.environ["PLANETSCALE_DATABASE"],
ssl_ca="/etc/ssl/cert.pem",
ssl_mode="VERIFY_IDENTITY",
)
# ❌ BAD — Blocking DDL (might lock table on MySQL, unsupported on Vitess)
# ALTER TABLE orders MODIFY COLUMN status VARCHAR(50) NOT NULL;
# ✅ GOOD — Online DDL using PlanetScale branching + deploy request
# 1. Create a branch: pscale branch create myapp-add-status-index
# 2. Make schema change on the branch
# 3. Create a deploy request
# 4. Deploy (zero-downtime)
ssl_ca + ssl_mode=VERIFY_IDENTITY) — PlanetScale rejects non-SSL connectionsconn.commit() explicitly for write operations%s placeholderscursor(dictionary=True) for readable resultsWhen writing PlanetScale integration code, structure your output as:
| Skill | Purpose |
|---|---|
coding-postgresql-sdk | PostgreSQL SDK patterns (alternative relational database) |
coding-supabase-sdk | Supabase BaaS patterns for PostgreSQL |
coding-database-migrations | Migration workflow patterns compatible with PlanetScale branching |