def event_env_updated(app, env):
    """Called by Sphinx during phase 3 (resolving).

    * Find Imgur IDs that need to be queried.
    * Query the Imgur API for new/outdated albums/images.

    :param sphinx.application.Sphinx app: Sphinx application object.
    :param sphinx.environment.BuildEnvironment env: Sphinx build environment.
    """
    client_id = app.config['imgur_client_id']
    ttl = app.config['imgur_api_cache_ttl']
    album_cache = app.builder.env.imgur_album_cache
    image_cache = app.builder.env.imgur_image_cache
    album_whitelist = {v.imgur_id for v in album_cache.values() if v.mod_time == 0}
    image_whitelist = {v.imgur_id for v in image_cache.values() if v.mod_time == 0}

    # Build whitelist of Imgur IDs in just new/updated docs.
    for doctree in (env.get_doctree(n) for n in app.builder.get_outdated_docs()):
        for node in (n for c in (ImgurTextNode, ImgurImageNode) for n in doctree.traverse(c)):
            if node.album:
                album_whitelist.add(node.imgur_id)
            else:
                image_whitelist.add(node.imgur_id)

    # Update the cache only if an added/changed doc has an Imgur album/image.
    if album_whitelist or image_whitelist:
        update_cache(album_cache, image_cache, app, client_id, ttl, album_whitelist, image_whitelist)
        prune_cache(album_cache, image_cache, app)
def test_prune_cache_prune_nothing(app):
    """Test with nothing to prune.

    :param app: conftest fixture.
    """
    album_cache, image_cache = initialize(dict(), dict(), ['album1'], ['image1', 'image2', 'image3'])
    album_cache['album1'].image_ids = ['image3']
    doctree_album_ids = ['album1']
    doctree_image_ids = ['image1', 'image2']
    prune_cache(album_cache, image_cache, app, doctree_album_ids, doctree_image_ids)
    assert sorted(album_cache) == ['album1']
    assert sorted(image_cache) == ['image1', 'image2', 'image3']
def event_before_read_docs(app, env, _):
    """Called by Sphinx before phase 1 (reading).

    * Verify config.
    * Initialize the cache dict before reading any docs with an empty dictionary if not exists.
    * Prune old/invalid data from cache.

    :param sphinx.application.Sphinx app: Sphinx application object.
    :param sphinx.environment.BuildEnvironment env: Sphinx build environment.
    :param _: Not used.
    """
    client_id = app.config['imgur_client_id']
    if not client_id:
        raise ExtensionError('imgur_client_id config value must be set for Imgur API calls.')
    if not RE_CLIENT_ID.match(client_id):
        raise ExtensionError('imgur_client_id config value must be 5-30 lower case hexadecimal characters only.')

    imgur_album_cache = getattr(env, 'imgur_album_cache', None)
    imgur_image_cache = getattr(env, 'imgur_image_cache', None)
    env.imgur_album_cache, env.imgur_image_cache = initialize(imgur_album_cache, imgur_image_cache, (), ())
    prune_cache(env.imgur_album_cache, env.imgur_image_cache, app)
def test_prune_cache_bad_types(app, doctree_none):
    """Test pruning v1.0.0/invalid items from cache.

    :param app: conftest fixture.
    :param bool doctree_none: Test with None value for last argument.
    """
    album_cache, image_cache = initialize(dict(), dict(), ['album1', 'willBeBad1'], ['image1', 'willBeBad2'])
    doctree_album_ids = None if doctree_none else list(album_cache)  # Same effect.
    doctree_image_ids = None if doctree_none else list(image_cache)  # Same effect.
    for key in ('album2', 0, False, None, ''):
        album_cache[key] = album_cache['album1']
        if not doctree_none:
            doctree_album_ids.append(key)
    for key in ('image2', 0, False, None, ''):
        image_cache[key] = image_cache['image1']
        if not doctree_none:
            doctree_image_ids.append(key)
    album_cache['willBeBad1'] = None
    image_cache['willBeBad2'] = str()

    prune_cache(album_cache, image_cache, app, doctree_album_ids, doctree_image_ids)
    assert sorted(album_cache) == ['album1']
    assert sorted(image_cache) == ['image1']