Metadata-Version: 2.4
Name: flask-shopify-utils
Version: 0.2.13
Summary: A Flask extension for Shopify app development
Project-URL: Homepage, https://github.com/leocxy/flask-shopify-utils
Project-URL: Bug Tracker, https://github.com/leocxy/flask-shopify-utils/issues
Author: Leo Chen
Author-email: leo.cxy88@gmail.com
Maintainer: Leo Chen
Maintainer-email: leo.cxy88@gmail.com
License-File: LICENSE
Keywords: Extension,Flask,Flask Extension,SDK,Shopify,Shopify-SDK
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: cerberus<2
Requires-Dist: click==8.1.8
Requires-Dist: flask-sqlalchemy<4
Requires-Dist: flask<4
Requires-Dist: psutil<8
Requires-Dist: pyjwt<3
Requires-Dist: pytz==2026.1.post1
Requires-Dist: requests<3
Requires-Dist: sgqlc<19
Requires-Dist: simplejson<4
Provides-Extra: dev
Requires-Dist: build==1.2.1; extra == 'dev'
Requires-Dist: flake8<8; extra == 'dev'
Requires-Dist: pytest<9; extra == 'dev'
Requires-Dist: twine<7; extra == 'dev'
Description-Content-Type: text/markdown

# Flask-Shopify-Utils

[![Python 3.9](https://img.shields.io/github/actions/workflow/status/leocxy/flask-shopify-utils/CI.yml?branch=master&label=Python%203.9)](https://github.com/leocxy/flask-shopify-utils/actions/workflows/CI.yml?query=branch%3Amaster)
[![Python 3.10](https://img.shields.io/github/actions/workflow/status/leocxy/flask-shopify-utils/CI.yml?branch=master&label=Python%203.10)](https://github.com/leocxy/flask-shopify-utils/actions/workflows/CI.yml?query=branch%3Amaster)
[![Python 3.11](https://img.shields.io/github/actions/workflow/status/leocxy/flask-shopify-utils/CI.yml?branch=master&label=Python%203.11)](https://github.com/leocxy/flask-shopify-utils/actions/workflows/CI.yml?query=branch%3Amaster)
[![Python 3.12](https://img.shields.io/github/actions/workflow/status/leocxy/flask-shopify-utils/CI.yml?branch=master&label=Python%203.12)](https://github.com/leocxy/flask-shopify-utils/actions/workflows/CI.yml?query=branch%3Amaster)
[![Python 3.13](https://img.shields.io/github/actions/workflow/status/leocxy/flask-shopify-utils/CI.yml?branch=master&label=Python%203.13)](https://github.com/leocxy/flask-shopify-utils/actions/workflows/CI.yml?query=branch%3Amaster)

`flask-shopify-utils` is a Flask extension that provides common building blocks for Shopify custom apps: OAuth routes,
HMAC and webhook validation, Shopify session-token checks, GDPR webhook routes, a GraphQL client, starter models, and a
scaffold CLI.

- [Features](#features)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Starter scaffold](#starter-scaffold)
- [Configuration](#configuration)
- [Route bundles](#route-bundles)
- [Authentication decorators](#authentication-decorators)
- [Models](#models)
- [GraphQL utilities](#graphql-utilities)
- [Development](#development)
- [License](#license)

## Features

- Shopify OAuth install and callback helpers for embedded custom apps.
- HMAC validation for OAuth callbacks, embedded-app entry points, app proxies, and webhooks.
- Shopify session-token validation through `check_session_jwt`.
- Optional default blueprints for app entry, admin reference endpoints, GDPR webhooks, and GraphQL schema generation.
- A retrying Shopify GraphQL client based on `sgqlc`.
- Starter SQLAlchemy models for stores and webhook jobs.
- `lazy-dog` CLI for copying the example scaffold into a new project.

## Installation

Install the package from PyPI:

```bash
pip install -U flask-shopify-utils
```

Or install with `uv`:

```bash
uv pip install -U flask-shopify-utils
```

The package supports Python 3.9 and newer.

## Quick start

Initialize Flask-SQLAlchemy before initializing `ShopifyUtil`. The extension reads the SQLAlchemy instance from
`app.extensions['sqlalchemy']` during `init_app`.

```text
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_shopify_utils import ShopifyUtil

app = Flask(__name__)
app.config.update(
    SQLALCHEMY_DATABASE_URI="sqlite:///shopify.db",
    SHOPIFY_API_KEY="your-shopify-api-key",
    SHOPIFY_API_SECRET="your-shopify-api-secret",
    SCOPES="read_products,write_products",
)

db = SQLAlchemy()
db.init_app(app)

shopify = ShopifyUtil()
shopify.init_app(app)

# Register only the route bundles your app needs.
shopify.enroll_default_route()
shopify.enroll_gdpr_route()
shopify.enroll_admin_route()
shopify.enroll_graphql_schema_cli()
```

If you use the bundled models, import them only after `ShopifyUtil().init_app(app)` has run:

```python
from flask_shopify_utils.model import Store, Webhook
```

See `example/` for complete sample projects.

## Starter scaffold

`flask-shopify-utils` ships a `lazy-dog` console command that copies the reference scaffold from `example/example1` into
your current directory. Because it is a project bootstrapper, install it globally so the command is on your `PATH` from
any directory.

### Install lazy-dog globally

If you prefer plain `pip`, install into the global (user) environment:

```bash
pip install --user -U flask-shopify-utils
```

With `uv`, use `uv tool install`:

```bash
uv tool install flask-shopify-utils
```

After installation, verify the command is available:

```bash
lazy-dog --help
```

### Run the scaffold

Run `lazy-dog` inside the project directory you want to populate:

```bash
lazy-dog
```

The command downloads the repository archive, copies `example/example1/*` into the target directory, and prompts before
overwriting existing files or directories.

| Option              | Default                                         | Purpose                                          |
|---------------------|-------------------------------------------------|--------------------------------------------------|
| `--repo`            | `https://github.com/leocxy/flask-shopify-utils` | GitHub repository to download the scaffold from. |
| `-d`, `--directory` | `.`                                             | Target directory to copy the scaffold into.      |
| `-b`, `--branch`    | `master`                                        | Git branch to download.                          |

## Configuration

`ShopifyUtil.init_app()` sets defaults for the following Flask config values when they are not already configured:

| Key                  | Default                              | Purpose                                                                                    |
|----------------------|--------------------------------------|--------------------------------------------------------------------------------------------|
| `ROOT_PATH`          | Current working directory            | Base path for generated and temporary files.                                               |
| `BACKEND_PATH`       | `<ROOT_PATH>/backend`                | Backend application path used by helpers.                                                  |
| `TEMPORARY_PATH`     | `<BACKEND_PATH>/tmp`                 | Temporary output path.                                                                     |
| `API_VERSION`        | Latest supported Shopify API version | Shopify Admin API version used by GraphQL utilities.                                       |
| `TIMEZONE`           | `Pacific/Auckland`                   | Timezone for bundled model timestamps.                                                     |
| `SHOPIFY_API_SECRET` | `CUSTOM_APP_SECRET`                  | Shared secret for HMAC, webhook, and token validation.                                     |
| `SHOPIFY_API_KEY`    | `CUSTOM_APP_KEY`                     | Shopify app API key.                                                                       |
| `BYPASS_VALIDATE`    | `0`                                  | Local-development validation bypass. Any non-zero integer is treated as the fake store id. |
| `DEBUG`              | `False`                              | Flask debug flag fallback.                                                                 |
| `SCOPES`             | `read_products`                      | OAuth scopes requested during installation.                                                |

Set `BYPASS_VALIDATE` to `0` in production.

## Route bundles

Routes are opt-in. Call the enrollment methods you need after `init_app()`.

| Method                        | Routes and behavior                                                                                                                                    |
|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `enroll_default_route()`      | Registers `/`, `/admin`, `/install`, `/callback`, `/docs`, and a Shopify-aware `404` handler.                                                          |
| `enroll_gdpr_route()`         | Registers `/webhook/shop/redact`, `/webhook/customers/redact`, and `/webhook/customers/data_request`; requests are verified with Shopify webhook HMAC. |
| `enroll_admin_route()`        | Registers reference admin endpoints `/admin/test_jwt` and `/admin/check/reinstall`.                                                                    |
| `enroll_graphql_schema_cli()` | Registers `flask generate_schema`, which introspects a live Shopify store and emits an `sgqlc` schema module.                                          |

## Authentication decorators

Use the decorator that matches the Shopify surface you are handling. Successful decorators set `g.store_key` and
`g.store_id` for downstream handlers.

| Decorator                | Use case                                                          |
|--------------------------|-------------------------------------------------------------------|
| `check_callback`         | Shopify OAuth callback validation.                                |
| `check_hmac`             | Embedded-app entry validation.                                    |
| `check_proxy`            | Shopify app proxy signature validation.                           |
| `check_webhook`          | Shopify webhook HMAC validation through `X-Shopify-Hmac-Sha256`.  |
| `check_session_jwt`      | Shopify session-token validation from the `Authorization` header. |
| `validate_internal_hash` | Rolling internal hash validation for proxy-style endpoints.       |

Deprecated JWT helpers such as `check_jwt`, `validate_jwt`, and `create_admin_jwt_token` remain available for backward
compatibility, but new admin endpoints should use `check_session_jwt`.

## Models

The bundled `flask_shopify_utils.model` module provides:

- `Store`: shop domain, access token, scopes, and JSON-style `extra` data storage.
- `Webhook`: webhook job/event records with status helpers.
- `BasicMethod`: small create/update helper methods shared by the models.

Because the model module uses the SQLAlchemy instance registered by `ShopifyUtil`, initialize the extension before
importing these models.

## GraphQL utilities

`GraphQLClient` wraps `sgqlc.endpoint.http.HTTPEndpoint`, resolves the Shopify API version through `get_version()`, and
retries throttled or temporary HTTP failures.

```python
from flask_shopify_utils.utils import GraphQLClient

client = GraphQLClient("example.myshopify.com", "shpat_access_token")
operation = ...  # Build an sgqlc Operation from your generated schema.
data = client.fetch_data(operation)
```

To generate a typed Shopify schema after registering the CLI command:

```bash
flask generate_schema --store_id 1
```

Use `flask generate_schema --help` to see all available options.

## Development

Clone the repository, create a virtual environment, and install development dependencies:

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/dev.txt
pip install -e .
```

With `uv`:

```bash
uv venv
source .venv/bin/activate
uv pip install -r requirements/dev.txt
uv pip install -e .
```

Run linting and tests:

```bash
flake8
pytest
```

With `uv`:

```bash
uv run flake8
uv run pytest
```

Run a specific test file or test case:

```bash
pytest -vs tests/test_default_routes.py
pytest -vs tests/test_init.py::test_init_app
```

### Dependency locks

Pinned dependencies live in `requirements/`. Use `uv` for new dependency workflows:

```bash
uv pip compile pyproject.toml --extra=dev --output-file=requirements/dev.txt
uv pip compile pyproject.toml --output-file=requirements/index.txt
```

Legacy `pip-tools` commands are still supported:

```bash
pip-compile --extra=dev --output-file=requirements/dev.txt pyproject.toml
pip-compile --output-file=requirements/index.txt pyproject.toml
```

### Build and publish

Build the distribution and verify it with Twine:

```bash
python -m build
twine check dist/*
```

Publish to PyPI when you are ready:

```bash
twine upload dist/* --skip-existing
```

The package version is read from `__version__` in `src/flask_shopify_utils/__init__.py`.

## License

This project is licensed under the MIT License. See `LICENSE` for details.

## Reference

- [Python Packaging User Guide](https://packaging.python.org/en/latest/)
