Skip to main content

WowSQL Python SDK

Official Python client for WowSQL. All data operations communicate directly with PostgREST over HTTPS. No legacy API layers.

Requirements

  • Python 3.8 or later
  • requests and websocket-client

Installation

pip install wowsql

Quick Start

from wowsql import WowSQLClient

client = WowSQLClient(
    project_url="myproject",        # slug, domain, or full URL
    api_key="wowsql_anon_...",      # anon or service_role key
)

result = client.table("users").get()
print(result["data"])

Configuration

Parameter Type Default Description
project_url str required Project slug, domain, or full URL
api_key str required Anonymous or service role API key
base_domain str wowsqlconnect.com Base domain when project_url is a slug
secure bool True Use HTTPS
timeout int 30 Request timeout in seconds
verify_ssl bool True Verify SSL certificates

project_url formats

# Slug only
WowSQLClient(project_url="myproject", api_key="...")

# Full domain
WowSQLClient(project_url="myproject.wowsqlconnect.com", api_key="...")

# Full URL
WowSQLClient(project_url="https://myproject.wowsqlconnect.com", api_key="...")

API Keys

Key Type Prefix Usage
Anonymous wowsql_anon_... Client-side, public access with RLS
Service Role wowsql_service_... Server-side, full access, bypasses RLS

Database Operations

Querying Records

# Get all records
result = client.table("products").get()
data = result["data"]  # list of records
total = result["total"]  # total count

# Select specific columns
result = client.table("products").select("id", "name", "price").get()

# Filter with equality
result = client.table("products").eq("category", "electronics").get()

# Multiple filters (all ANDed by default)
result = (
    client.table("products")
    .gte("price", 100)
    .lte("price", 500)
    .eq("in_stock", True)
    .get()
)

# OR filter
result = (
    client.table("products")
    .eq("status", "active")
    .or_("status", "eq", "featured")
    .get()
)

Filter Operators

Operator Method Description
Equal .eq(col, val) column = value
Not equal .neq(col, val) column != value
Greater than .gt(col, val) column > value
Greater or equal .gte(col, val) column >= value
Less than .lt(col, val) column < value
Less or equal .lte(col, val) column <= value
LIKE .like(col, pattern) case-sensitive pattern match
ILIKE .ilike(col, pattern) case-insensitive pattern match
Is null .is_null(col) column IS NULL
Is not null .is_not_null(col) column IS NOT NULL
In list .in_(col, [vals]) column IN (...)
Not in list .not_in(col, [vals]) column NOT IN (...)
Between .between(col, lo, hi) lo <= column <= hi

Ordering and Pagination

# Order by column
result = client.table("products").order_by("price", "desc").get()

# Multiple column ordering
result = client.table("products").order_by([
    ("category", "asc"),
    ("price", "desc"),
]).get()

# Limit and offset
result = client.table("products").limit(20).offset(40).get()

# Paginate
result = client.table("products").paginate(page=2, per_page=20)
# result["data"], result["page"], result["per_page"]
# result["total"], result["total_pages"]

Single Record Fetch

# By primary key
user = client.table("users").get_by_id("uuid-here")

# First matching record
user = client.table("users").eq("email", "user@example.com").first()

# Exactly one record — raises WowSQLError if 0 or more than 1
user = client.table("users").eq("email", "user@example.com").single()

Creating Records

# Single record
response = client.table("users").create({
    "email": "new@example.com",
    "name": "New User",
})
new_id = response["id"]

# Alias
response = client.table("users").insert({"email": "new@example.com"})

# Bulk insert
responses = client.table("users").bulk_insert([
    {"email": "a@example.com", "name": "Alice"},
    {"email": "b@example.com", "name": "Bob"},
])

Updating Records

# Update by primary key
response = client.table("users").update("user-uuid", {"name": "Updated Name"})
print(response["affected_rows"])

Deleting Records

# Delete by primary key
response = client.table("users").delete("user-uuid")
print(response["affected_rows"])

Upsert

response = client.table("users").upsert(
    {"id": "existing-uuid", "name": "Updated"},
    on_conflict="id",  # default: "id"
)

Aggregates

# Count
total = client.table("orders").eq("status", "active").count()

# Sum
revenue = client.table("orders").eq("status", "paid").sum("amount")

# Average
avg_price = client.table("products").eq("category", "electronics").avg("price")

Group By

result = (
    client.table("orders")
    .select("status", "COUNT(*) as count")
    .group_by("status")
    .get()
)

Context Manager

with WowSQLClient(project_url="myproject", api_key="...") as client:
    result = client.table("users").get()

Authentication

from wowsql.auth import ProjectAuthClient

auth = ProjectAuthClient(
    project_url="myproject",
    api_key="wowsql_anon_...",
)

Email / Password

# Sign up
response = auth.sign_up(email="user@example.com", password="secure-password")
session = response.session
user = response.user

# Sign in
response = auth.sign_in(email="user@example.com", password="secure-password")

# Get current user
user = auth.get_user(access_token=session.access_token)

# Sign out
auth.logout(access_token=session.access_token)

CAPTCHA (Turnstile)

Optional. Only required when dashboard Attack Protection → Turnstile is enabled.

Create a Cloudflare Turnstile widget for your app domain at dash.cloudflare.com/turnstile and paste the site key and secret in Attack Protection. WoWSQL does not provide a shared widget — each project must use its own keys bound to that hostname. GET /auth/v1/settings returns your captcha.site_key. Render Turnstile with that key, then pass the token. Omit it when captcha is off — existing calls without captcha_token are unchanged.

auth.sign_up(email="user@example.com", password="secure-password", captcha_token=turnstile_token)
auth.sign_in(email="user@example.com", password="secure-password", captcha_token=turnstile_token)
auth.forgot_password(email="user@example.com", captcha_token=turnstile_token)
auth.send_otp(email="user@example.com", captcha_token=turnstile_token)
auth.send_magic_link(email="user@example.com", captcha_token=turnstile_token)
auth.resend_verification(email="user@example.com", captcha_token=turnstile_token)

OAuth

# Step 1 — get the redirect URL
result = auth.get_oauth_authorization_url(
    provider="google",
    redirect_uri="https://myapp.com/auth/callback",
)
# Redirect user to: result["authorization_url"]

# Step 2 — after OAuth callback, exchange code for tokens
response = auth.exchange_oauth_callback(
    provider="google",
    code=request.args["code"],
)
session = response.session
user = response.user

Token Management

# Refresh tokens
response = auth.refresh_token(refresh_token=session.refresh_token)

# Password reset
auth.forgot_password(email="user@example.com")
auth.reset_password(token="reset-token", new_password="new-password")
# Email OTP
auth.send_otp(email="user@example.com")
response = auth.verify_otp(email="user@example.com", otp="123456")

# Phone OTP (SMS) — provide phone instead of email
auth.send_otp(phone="+15551234567")
auth.verify_otp(phone="+15551234567", otp="123456")

# Magic link (email only)
auth.send_magic_link(email="user@example.com")

File Storage

from wowsql.storage import WowSQLStorage

storage = WowSQLStorage(
    project_url="myproject",
    api_key="wowsql_anon_...",
)

# Create bucket
storage.create_bucket("avatars", public=True)

# Upload file
with open("photo.jpg", "rb") as f:
    result = storage.upload("avatars", f, path="users/profile.jpg")

# Get public URL
url = storage.get_public_url("avatars", "users/profile.jpg")

# List files
files = storage.list_files("avatars", prefix="users/")

# Download
data = storage.download("avatars", "users/profile.jpg")

# Delete
storage.delete_file("avatars", "users/profile.jpg")

Schema Management

Requires a service role key.

from wowsql.schema import WowSQLSchema

schema = WowSQLSchema(
    project_url="myproject",
    service_key="wowsql_service_...",
)

# Create table
schema.create_table("products", [
    {"name": "id",         "type": "UUID",         "auto_increment": True},
    {"name": "name",       "type": "VARCHAR(255)",  "nullable": False},
    {"name": "price",      "type": "DECIMAL(10,2)", "nullable": False},
    {"name": "metadata",   "type": "JSONB",         "default": "'{}'"},
    {"name": "created_at", "type": "TIMESTAMPTZ",   "default": "CURRENT_TIMESTAMP"},
], primary_key="id", indexes=["name"])

# Add column
schema.add_column("products", "sku", "VARCHAR(100)")

# Drop column
schema.drop_column("products", "old_column")

# List tables
tables = schema.list_tables()

# Get table schema
info = schema.get_table_schema("products")

# Execute raw SQL (DDL only)
schema.execute_sql("CREATE INDEX ON products(name)")

# Drop table
schema.drop_table("products")

Realtime

Subscribe to INSERT / UPDATE / DELETE, broadcast ephemeral events, and track presence. Uses the same anon or service_role key as REST.

The SDK connects to:

wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>

Enable the table first (POST /realtime/v1/enable). Channel send / presence do not need a Postgres trigger. Payloads are capped at 64 KiB. Missing key closes 4001; invalid key closes 4003. Requires websocket-client (installed with the package).

from wowsql import WowSQLClient

client = WowSQLClient(project_url="myproject", api_key="wowsql_anon_...")

def on_change(change):
    print(change["event"], change.get("new") or change.get("old"))

sub = client.realtime.subscribe("messages", on_change, schema="public", event="*")

channel = client.realtime.channel("chat")
channel.on("broadcast", {"event": "typing"}, lambda msg: print("typing", msg["payload"]))
channel.on("presence", {}, lambda msg: print("online", channel.presence_state()))
channel.subscribe()
channel.track({"user": "alice"})
channel.send(event="typing", payload={"user": "alice"})

# later
sub["unsubscribe"]()
channel.unsubscribe()
client.close()

Error Handling

from wowsql import WowSQLError

try:
    result = client.table("orders").eq("id", "uuid").get()
except WowSQLError as err:
    print(err)             # Human-readable message
    print(err.status_code) # HTTP status code
    print(err.response)    # Raw PostgREST error dict

Architecture

All requests go directly to the PostgREST endpoint (/rest/v1). SDK filter expressions are translated into PostgREST query parameters. Pagination counts are read from the Content-Range response header.

Operation HTTP Method PostgREST Path
GET GET /{table}?col=op.val
CREATE POST /{table} with Prefer: return=representation
UPDATE PATCH /{table}?id=eq.{id}
DELETE DELETE /{table}?id=eq.{id}
UPSERT POST /{table} with Prefer: resolution=merge-duplicates
COUNT GET /{table}?limit=0 with Prefer: count=exact

License

MIT

Metadata

Release files for wowsql 3.9.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wowsql 3.9.3
File Size Uploaded
wowsql-3.9.3.tar.gz 32.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wowsql 3.9.3
File Interpreter ABI Platform
wowsql-3.9.3-py3-none-any.whl Python 3 none any Details

Total release size: 64.6 kB

Release files / wowsql-3.9.3.tar.gz

Download URL wowsql-3.9.3.tar.gz
Size 32.7 kB
Tags Source
SHA-256 checksum
How to use checksums
92f8db88050ba6818e5d820b15cfc855e983fc450c7621b04792123d769fa3d6
BLAKE2b-256 checksum
How to use checksums
d07964e686fb5e9194b562955da75fb508e8ed77bcf24dede20c75e009c3aad1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / wowsql-3.9.3-py3-none-any.whl

Download URL wowsql-3.9.3-py3-none-any.whl
Size 31.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f716a536c5f22add685ba0d32dc8f4577295047991d04106d85ce2b347207b6a
BLAKE2b-256 checksum
How to use checksums
e0d7b089f586062aad5f835d7e12953bdd210dc060e5dd0bb8c83ababb8547f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

3.10.0

2 release files

This release

3.9.3 This release

2 release files

3.9.2

2 release files

3.9.1

2 release files

3.9.0

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.1.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page