Skip to content

Commit 3e08c2e

Browse files
authored
Add handle_response to normalize @on_method return values (#15)
* Add handle_response to normalize @on_method return values Plugin methods can now yield/return Result, List[Result], or use generators without calling send_results() explicitly. Introduces types.py to break the result.py→plugin.py circular import, and response.py with handle_response() applied transparently in the on_method wrapper. Async generators are handled in EventHandler. * Fix missing Callable import in plugin.py after Method alias moved to types.py * Update docs and examples to use yield-based result pattern
1 parent c443fd6 commit 3e08c2e

12 files changed

Lines changed: 239 additions & 27 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,25 @@ python -m pip install pyflowlauncher[all]
2828
A basic plugin using a function as the query method.
2929

3030
```py
31-
from pyflowlauncher import Plugin, Result, send_results
32-
from .models.json_rpc import JsonRPCResponse
31+
from pyflowlauncher import Plugin, Result
3332

3433
plugin = Plugin()
3534

3635

3736
@plugin.on_method
38-
def query(query: str) -> JsonRPCResponse:
39-
r = Result(
37+
def query(query: str):
38+
yield Result(
4039
title="This is a title!",
4140
subtitle="This is the subtitle!",
4241
icon="icon.png"
4342
)
44-
return send_results([r])
4543

4644

4745
plugin.run()
4846
```
4947

48+
Methods decorated with `@plugin.on_method` can `yield` one or more `Result` objects, return a list of `Result` objects, or return a single `Result` — the framework normalizes all forms into the correct response automatically.
49+
5050
### Advanced plugin
5151

5252
A more advanced usage using a `Method` class as the query method.

docs/examples/guide/plugin_methods/example1.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
from pyflowlauncher import Plugin, Result, send_results
2-
from pyflowlauncher.models.json_rpc import JsonRPCResponse
1+
from pyflowlauncher import Plugin, Result
32

43
plugin = Plugin()
54

65

76
@plugin.on_method
8-
def query(query: str) -> JsonRPCResponse:
9-
r = Result(
7+
def query(query: str):
8+
yield Result(
109
title="This is a title!",
1110
subtitle="This is the subtitle!",
1211
json_rpc_action={"Method": "action", "Parameters": []}
1312
)
14-
return send_results([r])
1513

1614

1715
@plugin.on_method

docs/examples/guide/plugin_methods/example2.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
from pyflowlauncher import Plugin, Result, send_results
2-
from pyflowlauncher.models.json_rpc import JsonRPCResponse
1+
from pyflowlauncher import Plugin, Result
32

43
plugin = Plugin()
54

65

76
@plugin.on_method
8-
def query(query: str) -> JsonRPCResponse:
7+
def query(query: str):
98
r = Result(
109
title="This is a title!",
1110
subtitle="This is the subtitle!",
1211
)
1312
r.add_action(action, ["stuff"])
14-
return send_results([r])
13+
yield r
1514

1615

1716
def action(params: list[str]):
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
from pyflowlauncher import Plugin, Result, send_results
2-
from pyflowlauncher.models.json_rpc import JsonRPCResponse
1+
from pyflowlauncher import Plugin, Result
32
from pyflowlauncher.utils import score_results
43

54
plugin = Plugin()
65

76

87
@plugin.on_method
9-
def query(query: str) -> JsonRPCResponse:
10-
results = []
11-
for _ in range(100):
12-
r = Result(
8+
def query(query: str):
9+
results = [
10+
Result(
1311
title="This is a title!",
1412
subtitle="This is the subtitle!",
1513
)
16-
results.append(r)
17-
return send_results(score_results(query, results))
14+
for _ in range(100)
15+
]
16+
yield from score_results(query, results)
1817

1918

2019
plugin.run()

docs/guide/Plugin methods.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,42 @@
22

33
Flow Launcher can call custom methods created by your plugin as well. To do so simply register the method with your plugin.
44

5-
You can register any Function by using the `on_method` decorator or by using the `add_method` method from `Plugin`.
5+
You can register any function by using the `on_method` decorator or by using the `add_method` method from `Plugin`.
6+
7+
## Returning results
8+
9+
Methods decorated with `@plugin.on_method` support several return styles — the framework normalizes all of them automatically:
10+
11+
```py
12+
# yield a single result
13+
@plugin.on_method
14+
def query(query: str):
15+
yield Result(title="Hello!")
16+
17+
# yield multiple results
18+
@plugin.on_method
19+
def query(query: str):
20+
for item in data:
21+
yield Result(title=item)
22+
23+
# return a list of results
24+
@plugin.on_method
25+
def query(query: str):
26+
return [Result(title="a"), Result(title="b")]
27+
28+
# return a single result
29+
@plugin.on_method
30+
def query(query: str):
31+
return Result(title="Hello!")
32+
```
633

734
## Example 1
835

936
```py
1037
--8<-- "docs/examples/guide/plugin_methods/example1.py"
1138
```
1239

13-
Alternativley you can register and add the Method to a Result in one line by using `action` method.
40+
Alternatively you can register and add the Method to a Result in one line by using the `action` method.
1441

1542
## Example 2
1643

pyflowlauncher/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .plugin import Plugin
44
from .result import Result, send_results
55
from .method import Method
6+
from .response import handle_response
67

78

89
logger = logging.getLogger(__name__)
@@ -13,4 +14,5 @@
1314
"send_results",
1415
"Result",
1516
"Method",
17+
"handle_response",
1618
]

pyflowlauncher/event.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import asyncio
2+
import inspect
23
from typing import Any, Callable, Iterable, Type, Union
34

5+
from .result import Result, send_results
6+
47

58
class EventNotFound(Exception):
69

@@ -39,6 +42,14 @@ def get_event(self, event: str) -> Callable[..., Any]:
3942
async def _await_maybe(self, result: Any) -> Any:
4043
if asyncio.iscoroutine(result):
4144
return await result
45+
if inspect.isasyncgen(result):
46+
results = []
47+
async for item in result:
48+
if isinstance(item, Result):
49+
results.append(item)
50+
elif isinstance(item, list):
51+
results.extend(r for r in item if isinstance(r, Result))
52+
return send_results(results)
4253
return result
4354

4455
async def trigger_exception_handler(self, exception: Exception) -> Any:

pyflowlauncher/plugin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
from .base import pyFlowLauncherObject
1010

1111
from .event import EventHandler
12+
from .response import handle_response
1213
from .jsonrpc import JsonRPCClient, JsonRPCRequest
1314
from .models.plugin_manifest import FILE_NAME
1415
from .manifest import Manifest
1516

1617

17-
Method = Callable[..., Any]
18+
from .types import Method
1819

1920

2021
class Plugin(pyFlowLauncherObject):
@@ -38,7 +39,7 @@ def add_methods(self, methods: Iterable[Method]) -> None:
3839
def on_method(self, method: Method) -> Method:
3940
@wraps(method)
4041
def wrapper(*args, **kwargs):
41-
return method(*args, **kwargs)
42+
return handle_response(method(*args, **kwargs))
4243
self.add_method(wrapper)
4344
return wrapper
4445

pyflowlauncher/response.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import inspect
2+
from typing import Any, Generator, Union
3+
4+
from .result import Result, send_results
5+
from .models.json_rpc import JsonRPCRequest, JsonRPCResponse
6+
7+
8+
def handle_response(result: Any) -> Union[JsonRPCResponse, JsonRPCRequest, None]:
9+
"""Normalize a method's return value into a JSON-RPC response.
10+
11+
Accepts: Result, list of Result, generator of Result/list, JsonRPCRequest,
12+
JsonRPCResponse, coroutine, async generator, or None.
13+
Coroutines and async generators are returned as-is for the event loop to handle.
14+
"""
15+
if result is None:
16+
return None
17+
if isinstance(result, Result):
18+
return send_results([result])
19+
if isinstance(result, list):
20+
return send_results(result)
21+
if inspect.isgenerator(result):
22+
return _collect_generator(result)
23+
return result
24+
25+
26+
def _collect_generator(gen: Generator) -> JsonRPCResponse:
27+
results = []
28+
for item in gen:
29+
if isinstance(item, Result):
30+
results.append(item)
31+
elif isinstance(item, list):
32+
results.extend(r for r in item if isinstance(r, Result))
33+
return send_results(results)

pyflowlauncher/result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
66

7-
from .plugin import Method
7+
from .types import Method
88
from .models.result import Glyph, PreviewInfo
99
from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse
1010

0 commit comments

Comments
 (0)