Resolvers FAQ

Basic Questions

When is the resolver called?

When you access the value for the first time (or when cache expires):

bag['data'] = UrlResolver('https://...')  # Nothing happens yet
result = bag['data']  # NOW the HTTP request is made

How do I force a refresh?

Reset the resolver’s cache:

node = bag.get_node('data')
node.resolver.reset()

# Next access will reload
fresh_data = bag['data']

Can I check if a value is resolved without triggering resolution?

Yes, use static=True:

# This won't trigger the resolver
cached = bag.get_item('data', static=True)

if cached is None:
    # Not yet resolved or no cached value
    pass

What happens if the resolver fails?

The exception propagates to the caller:

bag['api'] = UrlResolver('https://invalid-url')

try:
    data = bag['api']
except Exception as e:
    print(f"Failed to resolve: {e}")

Caching

How does caching work?

cache_time

Behavior

0

No caching, compute every time

> 0

Passive cache for N seconds (reload on next access after expiry)

< 0

Active cache — background refresh every abs(N) seconds (async only)

False

Cache forever (until manual reset)

Why is my value not updating?

Check your cache_time:

# This caches forever
bag['data'] = UrlResolver('...', cache_time=False)

# Force refresh
bag.get_node('data').resolver.reset()

Can I have different cache times for different values?

Yes, each resolver has its own cache:

bag['static'] = UrlResolver('...', cache_time=False)  # Forever
bag['live'] = UrlResolver('...', cache_time=-20)    # Background refresh every 20s (async only)
bag['dynamic'] = UrlResolver('...', cache_time=30)  # 30 seconds
bag['realtime'] = UrlResolver('...', cache_time=0)  # Never cache

Async

How do I use resolvers in async code?

Use smartawait:

from genro_toolbox import smartawait

async def get_data():
    return await smartawait(bag.get_item('api'))

Why do I get a coroutine instead of the value?

In async context, get_item() may return a coroutine:

# This might return a coroutine
result = bag.get_item('api')

# Always safe:
result = await smartawait(bag.get_item('api'))

Can I use sync resolvers in async code?

Yes, they work automatically. The @smartasync decorator handles it.

Serialization

Are resolvers preserved when serializing?

With TYTX, yes:

tytx = bag.to_tytx()
restored = Bag.from_tytx(tytx)
# Resolver is preserved!

With XML/JSON, no — only the cached value is preserved.

Can I serialize a Bag with unresolved resolvers?

Yes, but the resolver definition is stored, not the value:

bag['api'] = UrlResolver('https://...')
# Never accessed, so no cached value

tytx = bag.to_tytx()
restored = Bag.from_tytx(tytx)

# The resolver is there, will fetch when accessed
data = restored['api']  # HTTP request happens now

Modifying Nodes with Resolvers

Why can’t I overwrite a resolver node?

To prevent accidental data loss:

bag['data'] = UrlResolver('...')
bag['data'] = 'new_value'  # ERROR!

How do I replace a resolver with a value?

Use resolver=False:

bag.set_item('data', 'new_value', resolver=False)

How do I replace one resolver with another?

new_resolver = UrlResolver('https://new-url')
bag.set_item('data', None, resolver=new_resolver)

Performance

Are resolvers thread-safe?

Basic thread safety is provided, but for high-concurrency use cases, consider external synchronization.

How do I avoid thundering herd with cached resolvers?

Use appropriate cache times and consider staggering:

# Don't: All caches expire at the same time
for i in range(100):
    bag[f'item_{i}'] = UrlResolver('...', cache_time=300)

# Better: Stagger cache times
import random
for i in range(100):
    jitter = random.randint(0, 60)
    bag[f'item_{i}'] = UrlResolver('...', cache_time=300 + jitter)

Common Mistakes

Using bag['key'] inside the resolver for the same key

This causes infinite recursion:

# WRONG - infinite loop!
def bad_resolver():
    current = bag['data']  # Calls this resolver again!
    return current + 1

bag['data'] = BagCbResolver(bad_resolver)

Forgetting async context

# WRONG in async code
result = bag['api']  # Might be a coroutine!

# RIGHT
result = await smartawait(bag.get_item('api'))