save_batch(entities)— persist many entities in a single GraphQL round-trip, respecting creates-before-updates ordering and side-mutations.save_all()— flush every dirty or new entity the store is tracking.filter_and_populate(...)— query with filter + batched relationship population in one call, with nested field specs (e.g.files__path).
StashEntityStore
Example
Attributes
DEFAULT_QUERY_BATCH
DEFAULT_TTL
FIND_LIMIT
STUB_QUERY_BATCH
cache_size
Functions
get_cached
get, following the Django dual sync/async pattern.
Parameters:
Returns:
get
Returns:
get_many
Returns:
find
Returns:
Raises:
Filter syntax
Django-style kwargs
find(Scene, title=“exact”) # EQUALS find(Scene, title__contains=“partial”) # INCLUDES find(Scene, title__regex=r”S\d+”) # MATCHES_REGEX find(Scene, rating100__gte=80) # GREATER_THAN find(Scene, rating100__between=(60,90)) # BETWEEN find(Scene, studio__null=True) # IS_NULLRaw dict for complex cases
find(Scene, title={“value”: “x”, “modifier”: “NOT_EQUALS”})Nested filters
find(Scene, performers_filter={“name”: {“value”: “Jane”, “modifier”: “EQUALS”}})find_one
Returns:
find_iter
Yields:
Exampleasync for scene in store.find_iter(Scene, path__contains=“/media/”): process(scene) if done: break # Won’t fetch remaining batches
filter
Returns:
all_cached
Returns:
filter_strict
'files__path': Validates that files relationship exists AND path is populated on each File - 'studio__parent__name': Validates full nested path is populated
Parameters:
Returns:
Raises:
Examples:
This will raise if any performer has rating100=UNSET
high_rated = store.filter_strict( Performer, required_fields=[‘rating100’, ‘favorite’], predicate=lambda p: p.rating100 >= 80 and p.favorite )Validate nested fields are populated
large_images = store.filter_strict( Image, required_fields=[‘files__path’, ‘files__size’], predicate=lambda i: any( f.size > 10_000_000 for f in i.files if f.size is not None ) )↑ Raises ValueError if any Image has files=UNSET or any File has path/size=UNSET
filter_and_populate
'files__path': Ensures files relationship is populated, then path on each File - 'studio__parent__name': Ensures studio, parent, and name are all populated
This is much faster than find() when most data is already cached, since it only fetches the specific missing fields rather than re-fetching entire entities.
Parameters:
Returns:
Examples:
Cache has 1000 performers, but only 500 have rating100 loaded
high_rated = await store.filter_and_populate( Performer, required_fields=[‘rating100’, ‘favorite’], predicate=lambda p: p.rating100 >= 80 and p.favorite )↓ Fetches rating100+favorite for the 500 that don’t have it
↓ Then filters all 1000 with complete data
↓ Network calls: Only for missing data (much faster than find())
Filter images by nested file properties
large_images = await store.filter_and_populate( Image, required_fields=[‘files__path’, ‘files__size’], predicate=lambda i: any( f.size > 10_000_000 for f in i.files if f.size is not None ) )↓ Fetches files relationship + path/size fields on each File object
filter_and_populate_with_stats
Returns:
| Type | Description |
|---|---|
list[T] | Tuple of (matching_entities, stats_dict) where stats contains: |
dict[str, Any] |
|
tuple[list[T], dict[str, Any]] |
|
tuple[list[T], dict[str, Any]] |
|
tuple[list[T], dict[str, Any]] |
|
tuple[list[T], dict[str, Any]] |
|
With nested fields
results, stats = await store.filter_and_populate_with_stats( Image, required_fields=[‘files__path’, ‘files__size’], predicate=lambda i: any(f.size > 10_000_000 for f in i.files) )populated_filter_iter
Yields:
Examples:
Process large dataset incrementally
async for performer in store.populated_filter_iter( Performer, required_fields=[‘rating100’, ‘scenes’], predicate=lambda p: p.rating100 >= 90 and len(p.scenes) > 100 ): # Start processing immediately as matches are found await expensive_operation(performer) if should_stop: break # Can stop early without processing allWith nested fields
async for image in store.populated_filter_iter( Image, required_fields=[‘files__path’, ‘files__size’], predicate=lambda i: any(f.size > 10_000_000 for f in i.files) ): await process_large_image(image)missing_fields_nested
Returns:
Example
Check if image has files loaded with path field
missing = store.missing_fields_nested( image, ‘title’, # Simple field ‘files__path’ # Nested field )Returns {‘files__path’} if files.path is not loaded
populate
_received_fields tracking to determine which fields are genuinely missing and need to be fetched. All entities are treated as potentially incomplete.
Supports nested field specifications using Django-style double-underscore syntax: - 'files__path': Populate files relationship, then path on each File - 'studio__parent__name': Populate studio, then parent, then name - Can be mixed with regular fields: ['rating100', 'files__path']
Parameters:
Returns:
Examples:
Populate specific fields on a scene
scene = await store.populate(scene, fields=[“studio”, “performers”])Populate nested fields using __ syntax
image = await store.populate(image, fields=[“files__path”, “files__size”])Mix regular and nested fields
scene = await store.populate( scene, fields=[“rating100”, “studio__parent__name”] )Populate nested object directly (identity map pattern)
scene.studio = await store.populate(scene.studio, fields=[“urls”, “details”])Force refresh from server (invalidates cache first)
scene = await store.populate(scene, fields=[“studio”], force_refetch=True)Populate performer from a list
performer = await store.populate( scene.performers[0], fields=[“scenes”, “images”] )Check what’s missing before populating
missing = store.missing_fields(scene.studio, “urls”, “details”, “aliases”) if missing: scene.studio = await store.populate(scene.studio, fields=list(missing))has_fields
_received_fields tracking when available.
Parameters:
Returns:
missing_fields
Returns:
invalidate
Examples:
invalidate_type
invalidate_all
set_ttl
Raises:
add
Example
save
Raises:
Example
save_batch
save).
Parameters:
Returns:
Raises:
save_all
Raises:
delete
obj.delete(client) then invalidates the cache entry.
Parameters:
Returns:
Raises:
Examples:
get_or_create
Returns:
Raises:
Example
is_cached
Returns: