Search Handlers

The easy and intuative way of handling query/search requests.

Search Handler Callback

async def search_handler_callback(query)

This is an scheme of a basic search handler callback that returns items.

See the registering search handlers section for information on how to register your search handler.

flogin will attemp to convert whatever the callback returns into a list of results. If a dictionary is given, flogin will try and convert it into an Result via from_dict()

The callback can also be an async iterator, and yield the results.

Parameters:

query (Query) – The query data

Return type:

Result | list[Result] | dict | str | int | Any

Yields:

Result | dict | str | int | Any

Returns:

flogin will take the output in whatever form it is in, and try its best to convert it into a list of results. Worst case, it casts the item to a string and handles it accordingly.

# Return a string, which gets turned into a result
async def search_handler_callback_example(query):
    return "This is a string"
# flogin will return a single result to flow launcher, which will look something like this:
# Result(title="This is a string")
# Return a list of strings, which gets turned into a list of strings
async def search_handler_callback_example(query):
    return ["Foo", "Bar", "Apple", "Pear"]
# flogin will return a list of results to flow launcher, which will look something like this:
# [
#   Result(title="Foo"),
#   Result(title="Bar"),
#   Result(title="Apple"),
#   Result(title="Pear"),
# ]
# Return an int, which gets casted to a string and turned into a result.
async def search_handler_callback_example(query):
    return 25
# flogin will return a single result to flow launcher, which will look something like this:
# Result(title=str(25))
# yield a couple of numbers
async def search_handler_callback_example(query):
    yield 2
    yield 3
    yield 25
    yield 30
# flogin will return a list of results to flow launcher, which will look something like this:
# [
#   Result(title=str(2)),
#   Result(title=str(3)),
#   Result(title=str(25)),
#   Result(title=str(30)),
# ]

Conditions

flogin uses condition functions to determine which handler should be used on a certain query. A condition function should take a single parameter (Query), and return a bool. True means the search handler that this condition is associated with should be used on this query, and False means that the search handler shouldn’t be used on this query. See the builtin conditions section of this page’s api reference for a list of builtin conditions.

Condition Example

def condition(query)

This is called when flogin is determining if a certain query handler should be used for a certain query or not.

Parameters:

query (Query) – The query that will be give to the search handler

Return type:

bool

Returns:

A bool. True means the search handler that this condition is associated with should be used on this query, and False means that the search handler shouldn’t be used on this query.

Registering Handlers

There are 3 main ways to register handlers:

  1. Using the plugin.search decorator

  2. Using plugin.search as a classmethod

  3. Subclassing and registering your search handler

Plugin.search decorator

If you want to create a handler outside of your Plugin class using a decorator, you can use the search() decorator.

@plugin.search()
async def my_handler(query: Query):
    return f"Your query was: {query.text}"

Plugin.search decorator as classmethod

The search() decorator can also be used as a classmethod to define search handlers inside of a plugin class. Like so:

class MyPlugin(Plugin):
    @Plugin.search()
    async def my_handler(self, query: Query):
        return f"Your query was: {query.text}"

Subclassing and registering a search handler

Using the decorator isn’t the only weay to create search handlers, you can also subclass the SearchHandler object and register the handler.

class MyHandler(SearchHandler):
    def __init__(self) -> None:
        super().__init__(condition=PlainTextCondition("egg"))

    async def callback(self, query: Query):
        return "You found the easter egg!"

Error Handling

flogin is callback focused, so callbacks are used to handle errors in search handlers. If you are using the search() decorator to make your handler, you can use the error() decorator to register an error handler.

@plugin.search()
async def my_handler(query: Query):
    ...

@my_handler.error
async def my_error_handler(error: Exception):
    return f"An error occured! {error!r}"

Alternatively, if you are subclassing your handler, you can override the on_error() method to handle your error.

class MyHandler(SearchHandler):
    async def callback(self, query: Query):
        ...

    async def on_error(error: Exception):
        return f"An error occured! {error!r}"

Caching

If your results are predicable and don’t change much, a great option is to cache the results to make response times just that much faster. Additionally, if web requests are involved, the ignore_cancellation_requests parameter in Plugin combined with cached search handlers can help reduce the amount of web requests that are made. For this, a caching module is provided. Here is a quick walkthrough:

There are 3 provided decorators:

  1. cached_coro()

This decorator can be used to cache the result of coroutines.

  1. cached_gen()

This decorator can be used for caching async generators.

  1. cached_property()

This decorator ca be used for caching properties, almost exacly like functools.cached_property

Useage

The useage of each decorator is the same. You can use them as-is, for example:

@cached_coro
async def my_coro():
    ...

or you can call it:

@cached_coro()
async def my_coro():
    ...

If you call the decorator, you can pass an argument which acts as a name parameter. This parameter is used in combination with clear_cache() to fully customize which cached items you want to clear. The default name value is None, but you can pass a custom one, like so:

@cached_coro("my_coro")
async def my_coro():
    ...

Clearing the cache

To clear the cache, you can use clear_cache(). See the above section about the name parameter for more information about where the name comes from.

To clear the cache of every cached object, don’t pass any parameters. Example:

clear_cache()

To clear the cache of every cached object that doesn’t have a set name, pass None. Example:

clear_cache(None)

Lastly, to clear the cache of a specific item, pass it’s name. Example:

@cached_coro("my_coro")
async def my_coro():
    ...

# just clear the cache of 'my_coro' and nothing else
clear_cache('my_coro')

API Reference

You can see the API reference for search handlers & conditions here