Skip to main content
This guide walks you through installing stash-graphql-client and using it the way it’s meant to be used: through the EntityStore. The store is what turns this library from “a typed GraphQL client” into an ORM-like layer — an identity map so the same entity ID is always the same Python object, read-through caching, selective field loading, Django-style filtering, and batched saves that figure out create-before-update ordering for you. You can always drop down to raw client calls (covered near the end), but reach for the store first.

Prerequisites

  • Python 3.12 or higher
  • Stash server v0.30.0 or later (appSchema 75+). Newer features are gated via introspection; currently tracking v0.31.x.
  • Poetry (optional, for development)

Installation

Option 2: Install with Poetry

Option 3: Install from Source

Verify Installation

Configuration

Create a connection dictionary with your Stash server details:
If your Stash instance requires authentication, generate an API key in the Stash web interface under Settings → Security, and add it as ApiKey above.

Your First Script

StashContext is an async context manager. Entering it yields a ready-to-use StashClient, and the context exposes a single store singleton (context.store) wired to the identity map. This is the pattern you’ll use most:
Keep a reference to the context (don’t write async with StashContext(...) as client: if you need the store) — context.store is the identity-map-wired singleton, and it’s only initialized once the context is entered.

Working with the EntityStore

The store is the recommended entry point for everything: reads, searches, and writes.

Identity Map + Caching

store.get() checks the cache first and fetches on a miss. Because of the identity map, the same entity ID always returns the same object reference, so a change made anywhere is visible everywhere:

Django-Style Filtering

store.find() accepts familiar field__lookup filters instead of hand-built GraphQL filter objects:

Selective Field Loading

Entities may arrive as “stubs” with only base fields. store.populate() fetches only the fields you actually need (it tracks what’s already present), and supports nested field__subfield specs:

Saving Changes

Modify entities in place, then persist. store.save() saves one entity; store.save_all() flushes every dirty or new entity the store is tracking in a single batched request (handling create-before-update ordering automatically):

Lazy Iteration Over Large Sets

For large result sets, store.find_iter() yields entities as it pages through them, so you never hold the whole set in memory:

Get or Create

store.get_or_create() finds an entity by search criteria or builds a new one from those same criteria. New entities are not auto-saved — persist them with store.save():

Core Workflows

These are the same workflows you’ll reach for daily — expressed store-first.

Workflow 1: Find and Update

Build several entities, link them, and let save_all() persist everything in one batch with correct ordering (new tags get server IDs before the scene that references them):

Workflow 3: Bulk Processing with Progress

Understanding UNSET

The UNSET pattern is how the library does precise partial updates: a field is either set to a value, explicitly None, or UNSET (never touched, so it’s excluded from mutations).
Because saves only send fields you actually changed, you can load an entity, set one field, and save without clobbering anything else:
See the UNSET Pattern Guide for comprehensive examples.

Working with Relationships

Relationships may be UNSET until populated. Use store.populate() to load them, and is_set() before accessing:
Many-to-many helpers register local changes; persist them with a save:

Lower-Level: Using the Client Directly

The store is built on top of StashClient, and you can use the client directly when you don’t need caching, the identity map, or batching — for example a one-off lookup or a quick script. Entities save against the client with entity.save(client):
Prefer the store when you touch the same entities more than once, batch writes, or rely on relationships staying consistent. Reach for the raw client for simple one-shot reads or writes.

Error Handling

Best Practices

1. Use the store singleton

Access the store via context.store so every part of your code shares one identity map and cache:

2. Batch your writes

Make all your edits, then flush once:

3. Populate only what you need

4. Check for UNSET before accessing

5. Handle None responses

Common Patterns

Pattern: Get or Create

Pattern: Batch Processing with the Store

Next Steps

Now that you’ve completed the getting started guide, explore these topics:

Troubleshooting

Connection Issues

Problem: ConnectError: Cannot connect to server Solution:
  • Verify Stash is running
  • Check host and port in connection config
  • Try accessing Stash web interface manually

Import Errors

Problem: ModuleNotFoundError: No module named 'stash_graphql_client' Solution:

Type Errors

Problem: IDE shows type errors for entity fields Solution:
  • Ensure Python 3.12+ is being used
  • Check that Pydantic v2 is installed
  • May need to restart IDE/language server

Getting Help