Skip to main content
This document provides a detailed technical overview of stash-graphql-client’s architecture.

Three-Layer Architecture Diagram

Architecture Layers

This library follows a three-layer architecture:

Layer 1: StashClient (GraphQL Transport)

Located in stash_graphql_client/client/, the client provides direct access to Stash’s GraphQL API through typed mixin methods. Responsibilities:
  • Execute GraphQL queries and mutations
  • Handle HTTP/WebSocket transports
  • Manage retries and connection pooling
  • Deserialize responses to Pydantic models
Example usage:
Mixin architecture: Client composed of 20+ mixins, one per entity type (SceneClientMixin, PerformerClientMixin, etc.). This provides clear separation of concerns and makes the codebase maintainable.

Layer 2: Pydantic Types (Schema/ORM Layer)

Located in stash_graphql_client/types/, all Stash entities are Pydantic v2 models with: Features:
  • UNSET Pattern - Three-state fields distinguish “set to value”, “set to null”, and “never touched”
  • UUID4 Auto-generation - New objects get temporary IDs replaced with server IDs on save
  • Bidirectional Relationships - Automatic sync between related entities
  • Tracked Fields - __tracked_fields__ and __field_conversions__ for change detection
  • Relationship Metadata - __relationships__ documents query strategies and inverse fields
Example usage:

Layer 3: StashEntityStore (Identity Map & Caching)

Located in stash_graphql_client/store.py, provides SQLAlchemy/ActiveRecord-style data access with: Features:
  • Identity Map - Same entity ID → same object reference
  • Read-Through Caching - Fetch from cache or query on miss
  • TTL Support - Optional cache expiration
  • Field-Aware Population - Load missing fields on demand
  • Django-Style Filtering - field__modifier kwargs
  • Lazy Iteration - Paginate large result sets efficiently
  • Thread-Safe - Uses RLock for concurrent access
Example usage:

When to Use Each Layer

Use StashClient when:

  • ✅ Making one-off queries
  • ✅ Need direct control over GraphQL queries
  • ✅ Working with entity types that don’t need caching
  • ✅ Executing mutations that return complex results

Use Pydantic Types when:

  • ✅ Creating new entities with validation
  • ✅ Need ORM-like .save() / .delete() methods
  • ✅ Working with entity relationships
  • ✅ Need change tracking for partial updates

Use StashEntityStore when:

  • ✅ Need object identity across queries
  • ✅ Making repeated queries for same entities
  • ✅ Using Django-style filtering
  • ✅ Need field-aware population
  • ✅ Processing large result sets with pagination