Python SDK and utilities for ShopifyQL queries
Project description
shopifyql
Python SDK for running ShopifyQL queries from Python. It aims to make it fast and easy to get your store data with minimal setup, while staying flexible for power users.
Key points:
- No hard runtime deps by default (only
requests). - Choose your result backend: dependency-free records (default), pandas, or polars.
- Optional OAuth helper to obtain an access token during development.
Quick setup with CLI template
For the easiest way to test this package, you can use our CLI template that sets up everything for you to start working in a Jupyter Notebook with access to your store:
shopify app init --template=https://github.com/Shopify/shopify-app-notebooks-template
This template will:
- Set up a Python 3.11+ environment with all dependencies needed to run
shopifyqlinside Jupyter notebooks - Handle app configuration and authentication automatically
- Provide a ready-to-use development environment powered by Shopify CLI
See the template repository for more details.
Requirements
- Python 3.11+
- A Shopify
shop(e.g.,your-shop-name) and an Admin API access token with access to theread_reportsscope.
Installation
Base (no heavy dependencies):
pip install shopifyql
With pandas support:
pip install "shopifyql[pandas]"
With polars support:
pip install "shopifyql[polars]"
With both pandas and polars:
pip install "shopifyql[all]"
Using uv for local development of this repo:
uv venv
uv sync --group dev
Quick start
The default return type is a list of dict records, so you can use the library without dependencies if needed.
from shopifyql import ShopifyQLClient
client = ShopifyQLClient(shop="your-shop", access_token="shpat_...")
records = client.query("FROM sales SHOW total_sales SINCE 2025-01-01 UNTIL 2025-12-31")
print(records[:2]) # e.g., [{"total_sales": 123.45}, {"total_sales": 67.89}]
Using pandas
pip install "shopifyql[pandas]"
from shopifyql import ShopifyQLClient, ShopifyQLPandasResult
client = ShopifyQLClient(shop="your-shop", access_token="shpat_...")
df = client.query_pandas("FROM sales SHOW orders TIMESERIES DAY SINCE -30d")
print(df.head())
Using polars
pip install "shopifyql[polars]"
from shopifyql import ShopifyQLClient, ShopifyQLPolarsResult
client = ShopifyQLClient(shop="your-shop", access_token="shpat_...")
df = client.query_polars("FROM sales SHOW total_sales group by product_title")
print(df.head())
OAuth helper (optional)
If you don’t have a token handy, you can use a local browser OAuth helper to obtain one during development:
from shopifyql import ShopifyQLClient
client = ShopifyQLClient.from_oauth(
shop="your-shop",
key="your_api_key",
secret="your_api_secret",
port = 4545
)
df = client.query_pandas("FROM sales SHOW total_sales SINCE -7d")
Steps for oauth
- Requires read_reports access scope. Also: Level 2 access to Customer data including name, address, phone, and email fields. Please refer to https://shopify.dev/docs/apps/launch/protected-customer-data. Scopes caan be modified in your app version settings: https://dev.shopify.com/dashboard/ -> Your App -> Versions -> Create a version
- A valid redirect_uri is needed for this oauth flow to work, please use
http://localhost:4545/callback - You can only receive scopes that are enabled in your app settings, if you need more you will want to submit a new app version.
- If your browser lands on
admin.shopify.com/.../oauth/authorizeand the helper never returns tohttp://localhost:4545/callback, your app’s Redirect URLs probably don’t include the exactredirect_uri. Addhttp://localhost:4545/callback(or the port you pass, configurable in from_oauth) to App setup → Redirect URLs, then try again. Theredirect_urimust match exactly. - After auth, you can confirm what the scopes are currently available with:
scopes = client.get_current_scopes()
print(scopes)
Context manager and connection reuse
When running multiple queries, use the client as a context manager to reuse a keep-alive requests.Session per thread and avoid repeated TLS/HTTP setup costs:
from shopifyql import ShopifyQLClient
SHOP = "your-shop"
ACCESS_TOKEN = "shpat_..."
with ShopifyQLClient(SHOP, ACCESS_TOKEN) as client:
df1 = client.query_pandas("FROM sales SHOW total_sales GROUP BY product_title SINCE -30d UNTIL now")
df2 = client.query_pandas("FROM sales SHOW net_sales GROUP BY product_title SINCE -30d UNTIL now")
print(df1.merge(df2, on="product_title"))
Custom result classes
You can provide your own result transformer by implementing ShopifyQLResult:
from typing import Any
from shopifyql import ShopifyQLClient, ShopifyQLResult
class MyResult(ShopifyQLResult):
@classmethod
def from_table_data(cls, table_data: dict[str, Any]) -> Any:
# Transform the ShopifyQL tableData into your preferred structure
return {c["name"]: [row[i] for i, _ in enumerate(table_data["columns"])] for i, c in enumerate(table_data["columns"])}
client = ShopifyQLClient(shop="your-shop", access_token="shpat_...")
custom = client.query("from sales show total_sales", result_class=MyResult)
Error handling & rate limits
The client will:
- Gate requests with a fixed-window rate limiter to avoid 429s; if the window is exhausted, it sleeps until the next window (with jitter).
- Backoff and retry on transient request errors (and other request exceptions) up to
max_retries.
You may see:
requests.exceptions.HTTPErrorfor non-2xx HTTP responses (after retries as applicable).requests.exceptions.RequestExceptionfor network errors (after retries).ValueError("No valid table data found in response")when the ShopifyQL response is malformed.
Configuration
- API version: defaults to
2025-10. Override via constructorversion="YYYY-MM". - Timeout: defaults to 10s per request via constructor
connect_timeout. - Retries: defaults to
max_retries=3with exponential backoff and jitter. - Rate limiting:
FixedWindowConfig(window_seconds=60, max_requests=1000)by default; override viarate_limit_config. - Connections: when used as a context manager, the client reuses a per-thread
requests.Session(keep-alive); outside a context it uses ephemeral sessions. - Connection pool:
pool_maxsize=10by default.
Development
Clone and develop with uv or pip:
uv venv
uv sync --group=dev
pytest -q
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shopifyql-0.1.2.tar.gz.
File metadata
- Download URL: shopifyql-0.1.2.tar.gz
- Upload date:
- Size: 13.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de3c73d05347e81f7e7d18393cdd82eee703fba269956637e26d5727934295d8
|
|
| MD5 |
533d51b380c8977e61125b7e388c9d31
|
|
| BLAKE2b-256 |
6d491c543a924b702f08e2894b06e27ea930cc31180e15c7b10b8f8e4d7a4c2d
|
Provenance
The following attestation bundles were made for shopifyql-0.1.2.tar.gz:
Publisher:
publish.yml on Shopify/shopifyql-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shopifyql-0.1.2.tar.gz -
Subject digest:
de3c73d05347e81f7e7d18393cdd82eee703fba269956637e26d5727934295d8 - Sigstore transparency entry: 753689651
- Sigstore integration time:
-
Permalink:
Shopify/shopifyql-py@9293f07665cd7af7d1e787a2bfff755f3b6e4d28 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Shopify
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9293f07665cd7af7d1e787a2bfff755f3b6e4d28 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file shopifyql-0.1.2-py3-none-any.whl.
File metadata
- Download URL: shopifyql-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bc463fdb13a07d488e30555cdd515058e74980700fc417d01d0d19e3ab355da
|
|
| MD5 |
87aa7f064e862672f70b77aed23642cd
|
|
| BLAKE2b-256 |
667d045afbf7ac712c99af7e892b9e92661fcd509a6f900fcf0910310ba0affd
|
Provenance
The following attestation bundles were made for shopifyql-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on Shopify/shopifyql-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shopifyql-0.1.2-py3-none-any.whl -
Subject digest:
0bc463fdb13a07d488e30555cdd515058e74980700fc417d01d0d19e3ab355da - Sigstore transparency entry: 753689660
- Sigstore integration time:
-
Permalink:
Shopify/shopifyql-py@9293f07665cd7af7d1e787a2bfff755f3b6e4d28 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Shopify
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9293f07665cd7af7d1e787a2bfff755f3b6e4d28 -
Trigger Event:
workflow_dispatch
-
Statement type: