vs Raw GraphQL with gql
Side-by-Side Example
Raw gql approach:Feature Comparison
| Feature | Raw gql | stash-graphql-client |
|---|---|---|
| Query construction | Manual string building | Method calls (client.find_scene()) |
| Type safety | None (runtime dicts) | Full Pydantic validation |
| IDE autocomplete | No | Yes (all fields, methods) |
| Response parsing | Dict navigation | Pydantic models |
| Mutation building | Manual GraphQL strings | .save() / .delete() methods |
| Partial updates | Include all fields or manual tracking | UNSET pattern (automatic) |
| Object identity | Manual tracking | Automatic identity map |
| Relationship handling | Manual dict navigation | Pydantic models with bidirectional sync |
| Error detection | Runtime (wrong keys, types) | Development time (Pydantic validation) |
| Code volume | ~40-50 lines for basic CRUD | ~5-10 lines for same operations |
When to Use Raw gql
✅ Use raw gql when:- Making a few simple queries
- Need maximum control over GraphQL
- Working with non-Stash GraphQL APIs
- Building a custom abstraction layer
- Building tools with complex entity relationships
- Need type safety and validation
- Making many queries for same entities
- Want ORM-like convenience
vs Apollo Client (JavaScript)
Apollo Client is the most popular GraphQL client for JavaScript/TypeScript. Here’s how stash-graphql-client compares:Architecture Comparison
| Component | Apollo Client | stash-graphql-client |
|---|---|---|
| Language | JavaScript/TypeScript | Python |
| Type system | TypeScript (compile-time) | Pydantic (runtime validation) |
| Caching | InMemoryCache (separate layer) | Wrap validators (integrated) |
| Cache normalization | Refs to normalized objects | Direct object references |
| Cache writes | cache.writeQuery() | Automatic in constructor |
| Cache reads | cache.readQuery() | Automatic object reuse |
| Optimistic updates | Manual configuration | Not built-in (manual for now) |
| Subscriptions | Via graphql-ws | Via websockets transport |
Cache Implementation
Apollo Client:Type Safety
Apollo with TypeScript:When to Use Apollo vs stash-graphql-client
Use Apollo when:- Building web applications in JavaScript/TypeScript
- Need React integration (React Query, hooks)
- Working with any GraphQL API
- Need optimistic UI updates
- Want community plugins/extensions
- Building Python tools/scripts
- Working specifically with Stash
- Need runtime type validation
- Want ORM-like entity management
- Prefer identity map over normalized cache
vs SQLAlchemy
SQLAlchemy is Python’s most popular ORM for SQL databases. stash-graphql-client borrows patterns from SQLAlchemy but applies them to GraphQL.Conceptual Mapping
| SQLAlchemy Concept | stash-graphql-client Equivalent |
|---|---|
| Session | StashEntityStore |
| Session.identity_map | Wrap validator cache |
| Model.query.filter() | store.find(Type, field__modifier=value) |
| relationship() | RelationshipMetadata + auto-sync |
| back_populates | inverse_query_field |
| Lazy loading | Field-aware population |
| session.add() | Automatic in constructor |
| session.commit() | .save(client) |
| session.flush() | No direct equivalent (saves immediately) |
Side-by-Side Example
SQLAlchemy:Key Differences
| Aspect | SQLAlchemy | stash-graphql-client |
|---|---|---|
| Backend | SQL databases (Postgres, MySQL, SQLite) | GraphQL API (Stash) |
| Query language | SQLAlchemy expressions / SQL | GraphQL / Django-style kwargs |
| Identity map | Session.identity_map dict | Pydantic wrap validators |
| Async | async_scoped_session (extension) | Native async throughout |
| Type validation | Optional (via type hints) | Required (Pydantic runtime) |
| Relationships | Foreign keys + relationship() | RelationshipMetadata + query strategies |
| Lazy loading | Database query when accessed | Field-aware populate |
| Transactions | session.begin() / commit() / rollback() | No transactions (GraphQL mutations) |
| Schema changes | Alembic migrations | N/A (server handles schema) |
Advantages of stash-graphql-client
✅ Runtime type validation - Pydantic catches errors immediately ✅ UNSET pattern - Distinguish unqueried from null (SQL can’t do this) ✅ Async-first - Not retrofitted like SQLAlchemy’s async support ✅ No ORM impedance mismatch - GraphQL already returns objectsAdvantages of SQLAlchemy
✅ Transactions - ACID guarantees, rollback support ✅ Complex queries - Joins, subqueries, window functions ✅ Database portability - Works with any SQL database ✅ Mature ecosystem - 15+ years of developmentvs Django ORM
Django ORM is built into the Django web framework but can be used standalone.Filter Syntax Comparison
Django ORM:Key Differences
| Feature | Django ORM | stash-graphql-client |
|---|---|---|
| Filter syntax | field__modifier | Same! field__modifier |
| Query chaining | .filter().filter().exclude() | Single find() call |
| Identity map | Implicit (QuerySet caching) | Explicit (StashEntityStore) |
| Async support | Limited (sync_to_async) | Native async |
| Type validation | Model field types | Pydantic runtime validation |
| Partial updates | save(update_fields=[...]) | UNSET pattern |
| Relationships | ForeignKey / ManyToMany | RelationshipMetadata |
| Database | SQL (Postgres, MySQL, SQLite) | GraphQL (Stash) |
When to Use Django ORM vs stash-graphql-client
Use Django ORM when:- Building Django web applications
- Working with SQL databases
- Need transactions and complex joins
- Want admin interface for free
- Working specifically with Stash
- Building Python tools/scripts (not web apps)
- Want GraphQL flexibility
- Need UNSET pattern for sparse updates
When to Use This Library
✅ Use stash-graphql-client when:
-
Building tools that interact with Stash
- Media organization scripts
- Batch processing tools
- Data migration utilities
- Custom integrations
-
You need type safety and validation
- Catch errors at development time
- IDE autocomplete for all fields
- Runtime validation of server responses
-
You want ORM-like convenience
.save()/.delete()methods- Relationship helpers
- Change tracking for partial updates
-
Working with complex entity relationships
- Scenes with performers, studios, tags
- Need bidirectional relationship sync
- Want object identity across queries
-
Making many queries for same entities
- Identity map prevents duplicate objects
- Read-through caching reduces network requests
- Field-aware population loads only what’s needed
❌ Don’t use stash-graphql-client when:
-
Just need a few simple queries
- Use raw
gqllibrary directly - Simpler for one-off operations
- Use raw
-
Not using Python
- Use GraphQL client for your language
- Apollo (JS), graphql-ruby (Ruby), etc.
-
Need to work with multiple GraphQL APIs
- This is specialized for Stash’s schema
- Use general-purpose GraphQL client
-
Memory constrained environment
- Identity map keeps objects in memory
- May not be suitable for very large datasets
-
Need optimistic updates / offline support
- Not built-in (would need manual implementation)
- Apollo Client better for this use case
Migration Guide
From Raw gql
Before:From Apollo Client (JS → Python)
Before (JavaScript):From SQLAlchemy
Before:From Django ORM
Before:Summary
stash-graphql-client combines the best patterns from:- Apollo Client - GraphQL caching and query management
- SQLAlchemy - Identity map and session pattern
- Django ORM - Filter syntax and query building
- Pydantic - Runtime type validation and models
- Python developers
- Building tools that interact with Stash
- Need type safety + ORM convenience + GraphQL flexibility
- General-purpose GraphQL APIs
- Web applications (use Apollo + React)
- Maximum control over every GraphQL query
Next Steps
- Overview Guide - Architecture and core concepts
- Identity Map Architecture - Deep dive on caching
- Usage Patterns - Common recipes
- API Reference - Complete method documentation