From b88dc885394bd78eba7f58f827ed4f4a3244f8da Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:01:51 -0400 Subject: [PATCH 01/37] Fix: plugin_root should be a property --- pyflowlauncher/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 9f26ee6..35fc199 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -86,6 +86,7 @@ def run_dir(self) -> Path: """Return the run directory of the plugin.""" return Path(sys.argv[0]).parent + @property def root_dir(self) -> Path: """Return the root directory of the plugin.""" current_dir = self.run_dir @@ -95,8 +96,9 @@ def root_dir(self) -> Path: current_dir = current_dir.parent raise FileNotFoundError(f"Could not find {MANIFEST_FILE} in {self.run_dir} or any parent directory.") + @property def manifest(self) -> PluginManifestSchema: """Return the plugin manifest.""" - with open(self.root_dir() / MANIFEST_FILE, 'r', encoding='utf-8') as f: + with open(self.root_dir / MANIFEST_FILE, 'r', encoding='utf-8') as f: manifest = json.load(f) return manifest From 4c4b0b9ca14455a85536d0f4e5dfa6e421df8094 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:03:41 -0400 Subject: [PATCH 02/37] Refactor: use cached_property for root_dir and manifest_path in Plugin class --- pyflowlauncher/plugin.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 35fc199..4dabaac 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from functools import wraps +from functools import cached_property, wraps from typing import Any, Callable, Iterable, Optional, Type, Union from pathlib import Path import json @@ -86,7 +86,7 @@ def run_dir(self) -> Path: """Return the run directory of the plugin.""" return Path(sys.argv[0]).parent - @property + @cached_property def root_dir(self) -> Path: """Return the root directory of the plugin.""" current_dir = self.run_dir @@ -95,10 +95,15 @@ def root_dir(self) -> Path: return current_dir current_dir = current_dir.parent raise FileNotFoundError(f"Could not find {MANIFEST_FILE} in {self.run_dir} or any parent directory.") + + @cached_property + def manifest_path(self) -> Path: + """Return the path to the plugin manifest.""" + return self.root_dir / MANIFEST_FILE - @property + @cached_property def manifest(self) -> PluginManifestSchema: """Return the plugin manifest.""" - with open(self.root_dir / MANIFEST_FILE, 'r', encoding='utf-8') as f: + with open(self.manifest_path, 'r', encoding='utf-8') as f: manifest = json.load(f) return manifest From b362db34dbcacc0b1431dac486ec7fa10418e193 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:30:55 -0400 Subject: [PATCH 03/37] Refactor: replace PluginManifestSchema with PluginMetadata and add properties for plugin details --- pyflowlauncher/manifest.py | 25 ----------------------- pyflowlauncher/models/plugin_manifest.py | 17 ++++++++++++++++ pyflowlauncher/plugin.py | 26 ++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 27 deletions(-) delete mode 100644 pyflowlauncher/manifest.py create mode 100644 pyflowlauncher/models/plugin_manifest.py diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py deleted file mode 100644 index a7bb457..0000000 --- a/pyflowlauncher/manifest.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import TypedDict, Literal - -MANIFEST_FILE = 'plugin.json' - -Languages = Literal[ - 'Python', - 'CSharp', - 'FSharp', - 'Executable', - 'TypeScript', - 'JavaScript', -] - - -class PluginManifestSchema(TypedDict): - ID: str - ActionKeyword: str - Name: str - Description: str - Author: str - Version: str - Language: Languages - Website: str - IcoPath: str - ExecuteFileName: str diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py new file mode 100644 index 0000000..8921ff9 --- /dev/null +++ b/pyflowlauncher/models/plugin_manifest.py @@ -0,0 +1,17 @@ +from typing import TypedDict, List + + +class PluginMetadata(TypedDict): + ID: str + Name: str + Author: str + Version: str + Language: str + Description: str + Website: str + ExecuteFileName: str + IcoPath: str + ActionKeyword: str + ActionKeywords: List[str] + + \ No newline at end of file diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 4dabaac..8762c8b 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -12,11 +12,13 @@ from .event import EventHandler from .jsonrpc import JsonRPCClient from .result import JsonRPCAction, ResultResponse -from .manifest import PluginManifestSchema, MANIFEST_FILE +from .models.plugin_manifest import PluginMetadata Method = Callable[..., Union[ResultResponse, JsonRPCAction, None]] +MANIFEST_FILE = 'plugin.json' + class Plugin: def __init__(self, methods: list[Method] | None = None) -> None: @@ -102,8 +104,28 @@ def manifest_path(self) -> Path: return self.root_dir / MANIFEST_FILE @cached_property - def manifest(self) -> PluginManifestSchema: + def manifest(self) -> PluginMetadata: """Return the plugin manifest.""" with open(self.manifest_path, 'r', encoding='utf-8') as f: manifest = json.load(f) return manifest + + @property + def name(self) -> str: + """Return the name of the plugin.""" + return self.manifest['Name'] + + @property + def author(self) -> str: + """Return the author of the plugin.""" + return self.manifest['Author'] + + @property + def version(self) -> str: + """Return the version of the plugin.""" + return self.manifest['Version'] + + @property + def id(self) -> str: + """Return the ID of the plugin.""" + return self.manifest['ID'] From a0a877167ba8b5e2b6931b64d4270f91bd0027d1 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:31:14 -0400 Subject: [PATCH 04/37] Add models --- pyflowlauncher/models/json_rpc.py | 19 +++++++++++++++++++ pyflowlauncher/models/result.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 pyflowlauncher/models/json_rpc.py create mode 100644 pyflowlauncher/models/result.py diff --git a/pyflowlauncher/models/json_rpc.py b/pyflowlauncher/models/json_rpc.py new file mode 100644 index 0000000..d626bf9 --- /dev/null +++ b/pyflowlauncher/models/json_rpc.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, NotRequired, Optional, TypedDict + +from .result import Result + + +class JsonRPCRequest(TypedDict): + Method: str + Parameters: list + DontHideAfterAction: NotRequired[bool] + + +class JsonRPCResponse(TypedDict): + Result: List[Result] + SettingsChange: NotRequired[Optional[Dict[str, Any]]] + + +class JsonRPCResult(Result): + JsonRPCAction: NotRequired[JsonRPCRequest] + SettingsChange: NotRequired[Dict[str, Any]] \ No newline at end of file diff --git a/pyflowlauncher/models/result.py b/pyflowlauncher/models/result.py new file mode 100644 index 0000000..1661334 --- /dev/null +++ b/pyflowlauncher/models/result.py @@ -0,0 +1,29 @@ +from typing import TypedDict, Optional, Iterable, Required, NotRequired + + +class Glyph(TypedDict): + """Flow Launcher Glyph""" + Glyph: str + FontFamily: str + + +class PreviewInfo(TypedDict): + """Flow Launcher Preview section""" + PreviewImagePath: Optional[str] + Description: Optional[str] + IsMedia: bool + PreviewDeligate: Optional[str] + + +class Result(TypedDict, total=False): + Title: Required[str] + SubTitle: NotRequired[str] + IcoPath: NotRequired[str] + Score: NotRequired[int] + ContextData: NotRequired[Iterable] + Glyph: NotRequired[Glyph] + CopyText: NotRequired[str] + AutoCompleteText: NotRequired[str] + RoundedIcon: NotRequired[bool] + Preview: NotRequired[PreviewInfo] + TitleHighlightData: NotRequired[Iterable[int]] \ No newline at end of file From 86aef60e215011088cb53f7781f85847a90445dc Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:35:56 -0400 Subject: [PATCH 05/37] Fix: use correct typing --- pyflowlauncher/models/json_rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyflowlauncher/models/json_rpc.py b/pyflowlauncher/models/json_rpc.py index d626bf9..f4764c9 100644 --- a/pyflowlauncher/models/json_rpc.py +++ b/pyflowlauncher/models/json_rpc.py @@ -5,7 +5,7 @@ class JsonRPCRequest(TypedDict): Method: str - Parameters: list + Parameters: List[str] DontHideAfterAction: NotRequired[bool] From e800c40a22f9507721d46408383138995db59314 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 9 May 2026 23:36:06 -0400 Subject: [PATCH 06/37] Fix: update type hints for action method and method signature --- pyflowlauncher/plugin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 8762c8b..fddde1b 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -2,7 +2,7 @@ import sys from functools import cached_property, wraps -from typing import Any, Callable, Iterable, Optional, Type, Union +from typing import Any, Callable, Iterable, Optional, Type, Union, List from pathlib import Path import json import asyncio @@ -10,11 +10,11 @@ from pyflowlauncher.shared import logger from .event import EventHandler -from .jsonrpc import JsonRPCClient -from .result import JsonRPCAction, ResultResponse +from .jsonrpc import JsonRPCClient, JsonRPCRequest +from .result import ResultResponse from .models.plugin_manifest import PluginMetadata -Method = Callable[..., Union[ResultResponse, JsonRPCAction, None]] +Method = Callable[..., Union[ResultResponse, JsonRPCRequest, None]] MANIFEST_FILE = 'plugin.json' @@ -58,8 +58,8 @@ def wrapper(handler: Callable[..., Any]) -> Callable[..., Any]: return handler return wrapper - def action(self, method: Method, parameters: Optional[Iterable] = None) -> JsonRPCAction: - """Register a method and return a JsonRPCAction that calls it.""" + def action(self, method: Method, parameters: Optional[List] = None) -> JsonRPCRequest: + """Register a method and return a JsonRPCRequest that calls it.""" method_name = self.add_method(method) return {"method": method_name, "parameters": parameters or []} From ba896d30f57825df68c9263a7b5b56f65770204a Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Mon, 11 May 2026 16:55:33 -0400 Subject: [PATCH 07/37] Refactor: remove unused JsonRPCAction and ResultResponse, update Result class and send_results function for JsonRPC compatibility --- pyflowlauncher/__init__.py | 4 +- pyflowlauncher/method.py | 7 +-- pyflowlauncher/plugin.py | 4 +- pyflowlauncher/result.py | 90 +++++++++++++++----------------------- 4 files changed, 42 insertions(+), 63 deletions(-) diff --git a/pyflowlauncher/__init__.py b/pyflowlauncher/__init__.py index 22bbdfa..346b7ca 100644 --- a/pyflowlauncher/__init__.py +++ b/pyflowlauncher/__init__.py @@ -1,7 +1,7 @@ import logging from .plugin import Plugin -from .result import JsonRPCAction, Result, send_results, ResultResponse +from .result import Result, send_results from .method import Method @@ -10,9 +10,7 @@ __all__ = [ "Plugin", - "ResultResponse", "send_results", "Result", - "JsonRPCAction", "Method", ] diff --git a/pyflowlauncher/method.py b/pyflowlauncher/method.py index 50d3e0a..a89eb2e 100644 --- a/pyflowlauncher/method.py +++ b/pyflowlauncher/method.py @@ -3,7 +3,8 @@ from abc import ABC, abstractmethod from typing import Any, Dict, Optional -from .result import JsonRPCAction, Result, ResultResponse, send_results +from .models.json_rpc import JsonRPCRequest, JsonRPCResponse +from .result import Result, send_results from .shared import logger @@ -16,9 +17,9 @@ def __init__(self) -> None: def add_result(self, result: Result) -> None: self._results.append(result) - def return_results(self, settings: Optional[Dict[str, Any]] = None) -> ResultResponse: + def return_results(self, settings: Optional[Dict[str, Any]] = None) -> JsonRPCResponse: return send_results(self._results, settings) @abstractmethod - def __call__(self, *args, **kwargs) -> ResultResponse | JsonRPCAction: + def __call__(self, *args, **kwargs) -> JsonRPCResponse | JsonRPCRequest: pass diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index fddde1b..3448871 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -7,14 +7,14 @@ import json import asyncio +from pyflowlauncher.models.json_rpc import JsonRPCResponse from pyflowlauncher.shared import logger from .event import EventHandler from .jsonrpc import JsonRPCClient, JsonRPCRequest -from .result import ResultResponse from .models.plugin_manifest import PluginMetadata -Method = Callable[..., Union[ResultResponse, JsonRPCRequest, None]] +Method = Callable[..., Union[JsonRPCResponse, JsonRPCRequest, None]] MANIFEST_FILE = 'plugin.json' diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 626933e..45c281a 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -3,73 +3,53 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union +from typing import Any, Dict, Iterable, List, Optional, Union, cast if sys.version_info < (3, 11): from typing_extensions import NotRequired, TypedDict else: from typing import NotRequired, TypedDict - -if TYPE_CHECKING: - from .plugin import Method - - -class JsonRPCAction(TypedDict): - """Flow Launcher JsonRPCAction""" - method: str - parameters: Iterable - dontHideAfterAction: NotRequired[bool] - - -class Glyph(TypedDict): - """Flow Launcher Glyph""" - Glyph: str - FontFamily: str - - -class PreviewInfo(TypedDict): - """Flow Launcher Preview section""" - PreviewImagePath: Optional[str] - Description: Optional[str] - IsMedia: bool - PreviewDeligate: Optional[str] +from .models.result import Glyph, PreviewInfo +from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse @dataclass class Result: - Title: str - SubTitle: Optional[str] = None - IcoPath: Optional[Union[str, Path]] = None - Score: int = 0 - JsonRPCAction: Optional[JsonRPCAction] = None - ContextData: Optional[Iterable] = None - Glyph: Optional[Glyph] = None - CopyText: Optional[str] = None - AutoCompleteText: Optional[str] = None - RoundedIcon: bool = False - Preview: Optional[PreviewInfo] = None - TitleHighlightData: Optional[List[int]] = None + title: str + subtitle: Optional[str] = None + ico_path: Optional[Union[str, Path]] = None + score: int = 0 + json_rpc_action: Optional[JsonRPCRequest] = None + context_data: Optional[Iterable] = None + glyph: Optional[Glyph] = None + copy_text: Optional[str] = None + auto_complete_text: Optional[str] = None + rounded_icon: bool = False + preview: Optional[PreviewInfo] = None + title_highlight_data: Optional[List[int]] = None def as_dict(self) -> Dict[str, Any]: return self.__dict__ - def add_action(self, method: Method, - parameters: Optional[Iterable[Any]] = None, - *, - dont_hide_after_action: bool = False) -> None: - self.JsonRPCAction = { - "method": method.__name__, - "parameters": parameters or [], - "dontHideAfterAction": dont_hide_after_action - } - - -class ResultResponse(TypedDict): - result: List[Dict[str, Any]] - SettingsChange: NotRequired[Optional[Dict[str, Any]]] - - -def send_results(results: Iterable[Result], settings: Optional[Dict[str, Any]] = None) -> ResultResponse: + def to_json(self) -> JsonRPCResult: + """Converts the Result instance to a JsonRPCResult dictionary""" + return cast(JsonRPCResult, { + 'Title': self.title, + 'SubTitle': self.subtitle, + 'IcoPath': str(self.ico_path) if self.ico_path else None, + 'Score': self.score, + 'JsonRPCAction': self.json_rpc_action, + 'ContextData': self.context_data, + 'Glyph': self.glyph, + 'CopyText': self.copy_text, + 'AutoCompleteText': self.auto_complete_text, + 'RoundedIcon': self.rounded_icon, + 'Preview': self.preview, + 'TitleHighlightData': self.title_highlight_data, + }) + + +def send_results(results: Iterable[Result], settings: Optional[Dict[str, Any]] = None) -> JsonRPCResponse: """Formats and returns results as a JsonRPCResponse""" - return {'result': [result.as_dict() for result in results], 'SettingsChange': settings} + return {'Result': [result.to_json() for result in results], 'SettingsChange': settings} From 5872fd40bd978ce1b712c8b336783488d93f22f6 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Mon, 11 May 2026 16:59:39 -0400 Subject: [PATCH 08/37] Refactor: Normalize fields for Result --- pyflowlauncher/result.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 45c281a..e7e1afe 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -18,9 +18,9 @@ class Result: title: str subtitle: Optional[str] = None - ico_path: Optional[Union[str, Path]] = None + icon: Optional[Union[str, Path]] = None score: int = 0 - json_rpc_action: Optional[JsonRPCRequest] = None + action: Optional[JsonRPCRequest] = None context_data: Optional[Iterable] = None glyph: Optional[Glyph] = None copy_text: Optional[str] = None @@ -37,9 +37,9 @@ def to_json(self) -> JsonRPCResult: return cast(JsonRPCResult, { 'Title': self.title, 'SubTitle': self.subtitle, - 'IcoPath': str(self.ico_path) if self.ico_path else None, + 'IcoPath': str(self.icon) if self.icon else None, 'Score': self.score, - 'JsonRPCAction': self.json_rpc_action, + 'JsonRPCAction': self.action, 'ContextData': self.context_data, 'Glyph': self.glyph, 'CopyText': self.copy_text, From d9cc61725aa24cca8a8d07e1f00ab5903ebc8f34 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Mon, 11 May 2026 17:13:28 -0400 Subject: [PATCH 09/37] Refactor: Update Result class to use json_rpc_action and add action method for JsonRPCRequest --- pyflowlauncher/result.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index e7e1afe..3284c54 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -3,7 +3,7 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, Iterable, List, Optional, Union, cast +from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast if sys.version_info < (3, 11): from typing_extensions import NotRequired, TypedDict @@ -14,13 +14,23 @@ from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse + +def action(method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> JsonRPCRequest: + """Creates a JsonRPCRequest for the given method and parameters.""" + return { + 'Method': method.__name__, + 'Parameters': list(parameters) if parameters else [], + 'DontHideAfterAction': dont_hide_after_action, + } + + @dataclass class Result: title: str subtitle: Optional[str] = None icon: Optional[Union[str, Path]] = None score: int = 0 - action: Optional[JsonRPCRequest] = None + json_rpc_action: Optional[JsonRPCRequest] = None context_data: Optional[Iterable] = None glyph: Optional[Glyph] = None copy_text: Optional[str] = None @@ -29,6 +39,14 @@ class Result: preview: Optional[PreviewInfo] = None title_highlight_data: Optional[List[int]] = None + def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: + """Adds a JsonRPC action to the result.""" + self.json_rpc_action = { + 'Method': method.__name__, + 'Parameters': list(parameters) if parameters else [], + 'DontHideAfterAction': dont_hide_after_action, + } + def as_dict(self) -> Dict[str, Any]: return self.__dict__ @@ -39,7 +57,7 @@ def to_json(self) -> JsonRPCResult: 'SubTitle': self.subtitle, 'IcoPath': str(self.icon) if self.icon else None, 'Score': self.score, - 'JsonRPCAction': self.action, + 'JsonRPCAction': self.json_rpc_action, 'ContextData': self.context_data, 'Glyph': self.glyph, 'CopyText': self.copy_text, From e1ed0a2587f57e3723c2557be9ec49ddee6a875c Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Mon, 11 May 2026 17:20:33 -0400 Subject: [PATCH 10/37] Add from_json method to Result class for JsonRPCResult conversion --- pyflowlauncher/result.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 3284c54..909b59f 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -49,6 +49,26 @@ def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[A def as_dict(self) -> Dict[str, Any]: return self.__dict__ + + @staticmethod + def from_json(json_result: JsonRPCResult) -> Result: + """Creates a Result instance from a JsonRPCResult dictionary.""" + if 'Title' not in json_result: + raise ValueError("JsonRPCResult must have a 'Title' field") + return Result( + title=json_result['Title'], + subtitle=json_result.get('SubTitle'), + icon=json_result.get('IcoPath'), + score=json_result.get('Score', 0), + json_rpc_action=json_result.get('JsonRPCAction'), + context_data=json_result.get('ContextData'), + glyph=json_result.get('Glyph'), + copy_text=json_result.get('CopyText'), + auto_complete_text=json_result.get('AutoCompleteText'), + rounded_icon=json_result.get('RoundedIcon', False), + preview=json_result.get('Preview'), + title_highlight_data=list(json_result.get('TitleHighlightData', [])) + ) def to_json(self) -> JsonRPCResult: """Converts the Result instance to a JsonRPCResult dictionary""" From 46d4df3dbb6150d98ffa804330af194a0bd0d507 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Mon, 11 May 2026 17:30:58 -0400 Subject: [PATCH 11/37] Refactor: Enhance method registration in Plugin class and validate actions in Result class --- pyflowlauncher/plugin.py | 3 ++- pyflowlauncher/result.py | 12 ++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 3448871..b2534e4 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -31,6 +31,7 @@ def __init__(self, methods: list[Method] | None = None) -> None: def add_method(self, method: Method) -> str: """Add a method to the event handler.""" + setattr(method, '_is_registered_method', True) return self._event_handler.add_event(method) def add_methods(self, methods: Iterable[Method]) -> None: @@ -40,7 +41,7 @@ def on_method(self, method: Method) -> Method: @wraps(method) def wrapper(*args, **kwargs): return method(*args, **kwargs) - self._event_handler.add_event(wrapper) + self.add_method(wrapper) return wrapper def method(self, method: Method) -> Method: diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 909b59f..60b245f 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -14,16 +14,6 @@ from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse - -def action(method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> JsonRPCRequest: - """Creates a JsonRPCRequest for the given method and parameters.""" - return { - 'Method': method.__name__, - 'Parameters': list(parameters) if parameters else [], - 'DontHideAfterAction': dont_hide_after_action, - } - - @dataclass class Result: title: str @@ -41,6 +31,8 @@ class Result: def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: """Adds a JsonRPC action to the result.""" + if not getattr(method, '_is_registered_method', False): + raise ValueError(f"Method {method.__name__} is not registered as a plugin method. Please use the @plugin.on_method decorator to register it.") self.json_rpc_action = { 'Method': method.__name__, 'Parameters': list(parameters) if parameters else [], From c0f07e984adb75ed976c19d880973a310eb4d6bd Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:25:17 -0400 Subject: [PATCH 12/37] Refactor: Remove unnecessary sys.version_info check and clean up imports in result.py --- pyflowlauncher/result.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 60b245f..0a62f44 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -1,15 +1,9 @@ from __future__ import annotations -import sys from dataclasses import dataclass from pathlib import Path from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast -if sys.version_info < (3, 11): - from typing_extensions import NotRequired, TypedDict -else: - from typing import NotRequired, TypedDict - from .models.result import Glyph, PreviewInfo from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse @@ -41,7 +35,7 @@ def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[A def as_dict(self) -> Dict[str, Any]: return self.__dict__ - + @staticmethod def from_json(json_result: JsonRPCResult) -> Result: """Creates a Result instance from a JsonRPCResult dictionary.""" From 42f35caceed147c501eeef68cc36c00af2c7cf53 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:25:35 -0400 Subject: [PATCH 13/37] Fix: Ensure newline at end of file in result.py --- pyflowlauncher/models/result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyflowlauncher/models/result.py b/pyflowlauncher/models/result.py index 1661334..9ac9f0a 100644 --- a/pyflowlauncher/models/result.py +++ b/pyflowlauncher/models/result.py @@ -26,4 +26,4 @@ class Result(TypedDict, total=False): AutoCompleteText: NotRequired[str] RoundedIcon: NotRequired[bool] Preview: NotRequired[PreviewInfo] - TitleHighlightData: NotRequired[Iterable[int]] \ No newline at end of file + TitleHighlightData: NotRequired[Iterable[int]] From 4c8f6b488df4bb3ab3c6b74d4a3217ac071fed69 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:29:10 -0400 Subject: [PATCH 14/37] Refactor: Introduce MethodNotRegisteredError for better error handling in add_action method --- pyflowlauncher/result.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 0a62f44..ea56352 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -8,6 +8,14 @@ from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse +class MethodNotRegisteredError(Exception): + """Exception raised when trying to add an action with a method that is not registered as a plugin method.""" + + def __init__(self, method: Callable[..., Any]): + self.method = method + super().__init__(f"Method {method.__name__} is not registered as a plugin method. Please use the @plugin.on_method decorator to register it.") + + @dataclass class Result: title: str @@ -26,7 +34,7 @@ class Result: def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: """Adds a JsonRPC action to the result.""" if not getattr(method, '_is_registered_method', False): - raise ValueError(f"Method {method.__name__} is not registered as a plugin method. Please use the @plugin.on_method decorator to register it.") + raise MethodNotRegisteredError(method) self.json_rpc_action = { 'Method': method.__name__, 'Parameters': list(parameters) if parameters else [], From 0c09fe6090e71faa0c7116b5027a8488e48c4ebc Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:29:30 -0400 Subject: [PATCH 15/37] Fix: Remove unnecessary blank lines in plugin.py for cleaner code --- pyflowlauncher/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index b2534e4..a933cf3 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -19,6 +19,7 @@ MANIFEST_FILE = 'plugin.json' + class Plugin: def __init__(self, methods: list[Method] | None = None) -> None: @@ -98,7 +99,7 @@ def root_dir(self) -> Path: return current_dir current_dir = current_dir.parent raise FileNotFoundError(f"Could not find {MANIFEST_FILE} in {self.run_dir} or any parent directory.") - + @cached_property def manifest_path(self) -> Path: """Return the path to the plugin manifest.""" From 8f0de99e362d3d0ed22205772b905d7273fbfbbe Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:38:01 -0400 Subject: [PATCH 16/37] Refactor: Move logger setup to base.py and update Plugin class to inherit from pyFlowLauncherObject --- pyflowlauncher/base.py | 23 +++++++++++++++++++++++ pyflowlauncher/plugin.py | 6 +++--- pyflowlauncher/shared.py | 14 -------------- 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 pyflowlauncher/base.py delete mode 100644 pyflowlauncher/shared.py diff --git a/pyflowlauncher/base.py b/pyflowlauncher/base.py new file mode 100644 index 0000000..add2163 --- /dev/null +++ b/pyflowlauncher/base.py @@ -0,0 +1,23 @@ +import logging + + +def _setup_file_logger() -> logging.Logger: + log = logging.getLogger('pyflowlauncher') + if not log.handlers: + handler = logging.FileHandler('plugin.log') + handler.setLevel(logging.WARNING) + handler.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')) + log.addHandler(handler) + log.setLevel(logging.WARNING) + log.propagate = False + return log + + +_setup_file_logger() + + +class pyFlowLauncherObject: + """Base class for all pyFlowLauncher objects.""" + + def __init__(self): + self._logger = logging.getLogger(f'pyflowlauncher.{self.__class__.__name__}') diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index a933cf3..4bdd535 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -8,7 +8,7 @@ import asyncio from pyflowlauncher.models.json_rpc import JsonRPCResponse -from pyflowlauncher.shared import logger +from .base import pyFlowLauncherObject from .event import EventHandler from .jsonrpc import JsonRPCClient, JsonRPCRequest @@ -20,10 +20,10 @@ MANIFEST_FILE = 'plugin.json' -class Plugin: +class Plugin(pyFlowLauncherObject): def __init__(self, methods: list[Method] | None = None) -> None: - self._logger = logger(self) + super().__init__() self._client = JsonRPCClient() self._event_handler = EventHandler() self._settings: dict[str, Any] = {} diff --git a/pyflowlauncher/shared.py b/pyflowlauncher/shared.py deleted file mode 100644 index 767d6b1..0000000 --- a/pyflowlauncher/shared.py +++ /dev/null @@ -1,14 +0,0 @@ -import logging -import sys -from pathlib import Path -from typing import Any - -_logger = logging.getLogger(__name__) - - -def logger(obj: Any) -> logging.Logger: - module_file = sys.modules[obj.__module__].__file__ - if module_file is not None: - module_name = Path(module_file).stem - return logging.getLogger(f"{module_name}.{obj.__class__.__name__}") - return logging.getLogger(f"{obj.__module__}.{obj.__name__}") From f304fb81cc30d8c9b014654d4c3b961b72720c82 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:40:06 -0400 Subject: [PATCH 17/37] Fix: Remove unnecessary blank lines and ensure newline at end of file in plugin_manifest.py --- pyflowlauncher/models/plugin_manifest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py index 8921ff9..9816f55 100644 --- a/pyflowlauncher/models/plugin_manifest.py +++ b/pyflowlauncher/models/plugin_manifest.py @@ -13,5 +13,3 @@ class PluginMetadata(TypedDict): IcoPath: str ActionKeyword: str ActionKeywords: List[str] - - \ No newline at end of file From da855f8a372041dbecfe84193e810b5a37776916 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:40:51 -0400 Subject: [PATCH 18/37] Add: Create __init__.py file for models package initialization --- pyflowlauncher/models/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyflowlauncher/models/__init__.py diff --git a/pyflowlauncher/models/__init__.py b/pyflowlauncher/models/__init__.py new file mode 100644 index 0000000..e69de29 From 4c37fc57751600d42642f91913245faf0e955c74 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:53:56 -0400 Subject: [PATCH 19/37] Refactor: Update plugin manifest handling and introduce Manifest class for better structure --- pyflowlauncher/manifest.py | 54 ++++++++++++++++++++++++ pyflowlauncher/models/plugin_manifest.py | 3 ++ pyflowlauncher/plugin.py | 39 ++++------------- 3 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 pyflowlauncher/manifest.py diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py new file mode 100644 index 0000000..8eb324e --- /dev/null +++ b/pyflowlauncher/manifest.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass, field +import json +from typing import Self +from pathlib import Path + +from .models.plugin_manifest import FILE_NAME, PluginMetadata + + +@dataclass +class Manifest: + id: str + name: str + author: str + version: str + language: str + description: str + website: str + execute_file_name: str + ico_path: str + action_keyword: str + action_keywords: list[str] = field(default_factory=list) + + @classmethod + def from_file(cls, path: Path) -> Self: + """Load the manifest from a JSON file.""" + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + return cls(**data) + + @classmethod + def from_dir(cls, dir_path: Path) -> Self: + """Load the manifest from a directory containing a plugin.json file.""" + return cls.from_file(Path(dir_path) / FILE_NAME) + + def to_json(self) -> PluginMetadata: + """Convert the manifest to a JSON-serializable dictionary.""" + return PluginMetadata( + ID=self.id, + Name=self.name, + Author=self.author, + Version=self.version, + Language=self.language, + Description=self.description, + Website=self.website, + ExecuteFileName=self.execute_file_name, + IcoPath=self.ico_path, + ActionKeyword=self.action_keyword, + ActionKeywords=self.action_keywords, + ) + + def save(self, path: str) -> None: + """Save the manifest to a JSON file.""" + with open(path, 'w', encoding='utf-8') as f: + json.dump(self.to_json(), f, ensure_ascii=False, indent=4) diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py index 9816f55..cbbb69e 100644 --- a/pyflowlauncher/models/plugin_manifest.py +++ b/pyflowlauncher/models/plugin_manifest.py @@ -1,6 +1,9 @@ from typing import TypedDict, List +FILE_NAME = 'plugin.json' + + class PluginMetadata(TypedDict): ID: str Name: str diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 4bdd535..4076b65 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -12,12 +12,11 @@ from .event import EventHandler from .jsonrpc import JsonRPCClient, JsonRPCRequest -from .models.plugin_manifest import PluginMetadata - -Method = Callable[..., Union[JsonRPCResponse, JsonRPCRequest, None]] +from .models.plugin_manifest import FILE_NAME +from .manifest import Manifest -MANIFEST_FILE = 'plugin.json' +Method = Callable[..., Union[JsonRPCResponse, JsonRPCRequest, None]] class Plugin(pyFlowLauncherObject): @@ -95,39 +94,17 @@ def root_dir(self) -> Path: """Return the root directory of the plugin.""" current_dir = self.run_dir for part in current_dir.parts: - if current_dir.joinpath(MANIFEST_FILE).exists(): + if current_dir.joinpath(FILE_NAME).exists(): return current_dir current_dir = current_dir.parent - raise FileNotFoundError(f"Could not find {MANIFEST_FILE} in {self.run_dir} or any parent directory.") + raise FileNotFoundError(f"Could not find {FILE_NAME} in {self.run_dir} or any parent directory.") @cached_property def manifest_path(self) -> Path: """Return the path to the plugin manifest.""" - return self.root_dir / MANIFEST_FILE + return self.root_dir / FILE_NAME @cached_property - def manifest(self) -> PluginMetadata: + def manifest(self) -> Manifest: """Return the plugin manifest.""" - with open(self.manifest_path, 'r', encoding='utf-8') as f: - manifest = json.load(f) - return manifest - - @property - def name(self) -> str: - """Return the name of the plugin.""" - return self.manifest['Name'] - - @property - def author(self) -> str: - """Return the author of the plugin.""" - return self.manifest['Author'] - - @property - def version(self) -> str: - """Return the version of the plugin.""" - return self.manifest['Version'] - - @property - def id(self) -> str: - """Return the ID of the plugin.""" - return self.manifest['ID'] + return Manifest.from_file(self.manifest_path) From cd88b950657965a2720bc06b124a178c1d177818 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:54:08 -0400 Subject: [PATCH 20/37] Fix: Remove unused import of json in plugin.py for cleaner code --- pyflowlauncher/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 4076b65..6524b87 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -4,7 +4,6 @@ from functools import cached_property, wraps from typing import Any, Callable, Iterable, Optional, Type, Union, List from pathlib import Path -import json import asyncio from pyflowlauncher.models.json_rpc import JsonRPCResponse From 86cd0fcaeea671d90ed29066ee358583013b2203 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:57:01 -0400 Subject: [PATCH 21/37] Refactor: Update Method class to inherit from pyFlowLauncherObject and remove logger initialization --- pyflowlauncher/method.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyflowlauncher/method.py b/pyflowlauncher/method.py index a89eb2e..5ecff59 100644 --- a/pyflowlauncher/method.py +++ b/pyflowlauncher/method.py @@ -5,13 +5,13 @@ from .models.json_rpc import JsonRPCRequest, JsonRPCResponse from .result import Result, send_results -from .shared import logger +from .base import pyFlowLauncherObject -class Method(ABC): +class Method(pyFlowLauncherObject, ABC): def __init__(self) -> None: - self._logger = logger(self) + super().__init__() self._results: list[Result] = [] def add_result(self, result: Result) -> None: From f257b64629674586508c2c33e1c77bda43937dc0 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 10:58:40 -0400 Subject: [PATCH 22/37] Refactor: Update add_action method to use Method type for better type safety --- pyflowlauncher/method.py | 3 +++ pyflowlauncher/result.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/method.py b/pyflowlauncher/method.py index 5ecff59..7c3b84a 100644 --- a/pyflowlauncher/method.py +++ b/pyflowlauncher/method.py @@ -8,6 +8,9 @@ from .base import pyFlowLauncherObject + + + class Method(pyFlowLauncherObject, ABC): def __init__(self) -> None: diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index ea56352..18bd1f4 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast +from .plugin import Method from .models.result import Glyph, PreviewInfo from .models.json_rpc import JsonRPCResult, JsonRPCRequest, JsonRPCResponse @@ -31,7 +32,7 @@ class Result: preview: Optional[PreviewInfo] = None title_highlight_data: Optional[List[int]] = None - def add_action(self, method: Callable[..., Any], parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: + def add_action(self, method: Method, parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: """Adds a JsonRPC action to the result.""" if not getattr(method, '_is_registered_method', False): raise MethodNotRegisteredError(method) From 94761ef4122b03857285d712ac283490006acdde Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:02:18 -0400 Subject: [PATCH 23/37] Refactor: Remove unnecessary blank lines in method.py for cleaner code --- pyflowlauncher/method.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyflowlauncher/method.py b/pyflowlauncher/method.py index 7c3b84a..5ecff59 100644 --- a/pyflowlauncher/method.py +++ b/pyflowlauncher/method.py @@ -8,9 +8,6 @@ from .base import pyFlowLauncherObject - - - class Method(pyFlowLauncherObject, ABC): def __init__(self) -> None: From e7307b321cde2e7585629a7a10b10ef52c51e648 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:02:33 -0400 Subject: [PATCH 24/37] Refactor: make logger public --- pyflowlauncher/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyflowlauncher/base.py b/pyflowlauncher/base.py index add2163..1af97a8 100644 --- a/pyflowlauncher/base.py +++ b/pyflowlauncher/base.py @@ -20,4 +20,4 @@ class pyFlowLauncherObject: """Base class for all pyFlowLauncher objects.""" def __init__(self): - self._logger = logging.getLogger(f'pyflowlauncher.{self.__class__.__name__}') + self.logger = logging.getLogger(f'pyflowlauncher.{self.__class__.__name__}') From 8f1c41a583d6ae55c2bf9f28abefed97fd225c50 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:11:30 -0400 Subject: [PATCH 25/37] Fix: Update run_dir method to resolve the path for accurate directory retrieval --- pyflowlauncher/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index 6524b87..a9e4ae1 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -86,7 +86,7 @@ def run(self) -> None: @property def run_dir(self) -> Path: """Return the run directory of the plugin.""" - return Path(sys.argv[0]).parent + return Path(sys.argv[0]).resolve().parent @cached_property def root_dir(self) -> Path: From a2065be6966b95117426ae8a532928fd667fcf37 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:11:40 -0400 Subject: [PATCH 26/37] Refactor: Enhance from_json method to create Manifest instance from JSON data --- pyflowlauncher/manifest.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py index 8eb324e..f8b7367 100644 --- a/pyflowlauncher/manifest.py +++ b/pyflowlauncher/manifest.py @@ -20,12 +20,29 @@ class Manifest: action_keyword: str action_keywords: list[str] = field(default_factory=list) + @classmethod + def from_json(cls, json_data: PluginMetadata) -> Self: + """Create a Manifest instance from a JSON dictionary.""" + return cls( + id=json_data['ID'], + name=json_data['Name'], + author=json_data['Author'], + version=json_data['Version'], + language=json_data['Language'], + description=json_data['Description'], + website=json_data['Website'], + execute_file_name=json_data['ExecuteFileName'], + ico_path=json_data['IcoPath'], + action_keyword=json_data['ActionKeyword'], + action_keywords=json_data.get('ActionKeywords', []), + ) + @classmethod def from_file(cls, path: Path) -> Self: """Load the manifest from a JSON file.""" with open(path, 'r', encoding='utf-8') as f: data = json.load(f) - return cls(**data) + return cls.from_json(data) @classmethod def from_dir(cls, dir_path: Path) -> Self: From d7c7173a4cd48202d857c05f479d58a4391e5b31 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:13:34 -0400 Subject: [PATCH 27/37] Refactor: Update to_json method to return a dictionary instead of PluginMetadata --- pyflowlauncher/manifest.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py index f8b7367..513abed 100644 --- a/pyflowlauncher/manifest.py +++ b/pyflowlauncher/manifest.py @@ -51,21 +51,21 @@ def from_dir(cls, dir_path: Path) -> Self: def to_json(self) -> PluginMetadata: """Convert the manifest to a JSON-serializable dictionary.""" - return PluginMetadata( - ID=self.id, - Name=self.name, - Author=self.author, - Version=self.version, - Language=self.language, - Description=self.description, - Website=self.website, - ExecuteFileName=self.execute_file_name, - IcoPath=self.ico_path, - ActionKeyword=self.action_keyword, - ActionKeywords=self.action_keywords, - ) + return { + "ID": self.id, + "Name": self.name, + "Author": self.author, + "Version": self.version, + "Language": self.language, + "Description": self.description, + "Website": self.website, + "ExecuteFileName": self.execute_file_name, + "IcoPath": self.ico_path, + "ActionKeyword": self.action_keyword, + "ActionKeywords": self.action_keywords, + } - def save(self, path: str) -> None: + def save(self, path: Path) -> None: """Save the manifest to a JSON file.""" with open(path, 'w', encoding='utf-8') as f: json.dump(self.to_json(), f, ensure_ascii=False, indent=4) From 119fc644fbbe5c9342b22a6d858cb4efe8a27082 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:14:52 -0400 Subject: [PATCH 28/37] Refactor: Update PluginMetadata to make ActionKeywords field optional --- pyflowlauncher/models/plugin_manifest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py index cbbb69e..1f9c06a 100644 --- a/pyflowlauncher/models/plugin_manifest.py +++ b/pyflowlauncher/models/plugin_manifest.py @@ -1,4 +1,5 @@ -from typing import TypedDict, List +from typing import TypedDict, List, NotRequired + FILE_NAME = 'plugin.json' @@ -15,4 +16,4 @@ class PluginMetadata(TypedDict): ExecuteFileName: str IcoPath: str ActionKeyword: str - ActionKeywords: List[str] + ActionKeywords: NotRequired[List[str]] From e30851d5736e697036ba5a2bbb1427482a6126cd Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Wed, 27 May 2026 11:25:52 -0400 Subject: [PATCH 29/37] Refactor: Remove unnecessary blank line in plugin_manifest.py --- pyflowlauncher/models/plugin_manifest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py index 1f9c06a..39f6782 100644 --- a/pyflowlauncher/models/plugin_manifest.py +++ b/pyflowlauncher/models/plugin_manifest.py @@ -1,7 +1,6 @@ from typing import TypedDict, List, NotRequired - FILE_NAME = 'plugin.json' From d020ff34ea0b3bef1349e71de848b54bc8079f5d Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sat, 30 May 2026 11:40:30 -0400 Subject: [PATCH 30/37] Refactor: Update examples with new Result --- docs/examples/guide/api_requests/example1.py | 12 ++++++------ docs/examples/guide/api_requests/example2.py | 12 ++++++------ docs/examples/guide/launcher_icons/example1.py | 10 +++++----- docs/examples/guide/plugin_methods/example1.py | 10 +++++----- docs/examples/guide/plugin_methods/example2.py | 10 +++++----- docs/examples/guide/scoring_results/example1.py | 8 ++++---- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/examples/guide/api_requests/example1.py b/docs/examples/guide/api_requests/example1.py index 49b6c63..d0162af 100644 --- a/docs/examples/guide/api_requests/example1.py +++ b/docs/examples/guide/api_requests/example1.py @@ -1,16 +1,16 @@ from pyflowlauncher import Plugin, Result, send_results, api -from pyflowlauncher.result import ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse plugin = Plugin() @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", - IcoPath="icon.png", - JsonRPCAction=api.change_query("This is a new query!"), + title="This is a title!", + subtitle="This is the subtitle!", + icon="icon.png", + json_rpc_action=api.change_query("This is a new query!"), ) return send_results([r]) diff --git a/docs/examples/guide/api_requests/example2.py b/docs/examples/guide/api_requests/example2.py index b7e5374..2f2529f 100644 --- a/docs/examples/guide/api_requests/example2.py +++ b/docs/examples/guide/api_requests/example2.py @@ -1,21 +1,21 @@ from pyflowlauncher import Plugin, Result, send_results, api -from pyflowlauncher.result import JsonRPCAction, ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse plugin = Plugin() @plugin.on_method -def example_method() -> JsonRPCAction: +def example_method() -> JsonRPCResponse: # Do stuff here return api.change_query("This is also a new query!") @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", - IcoPath="icon.png", + title="This is a title!", + subtitle="This is the subtitle!", + icon="icon.png", ) r.add_action(example_method) return send_results([r]) diff --git a/docs/examples/guide/launcher_icons/example1.py b/docs/examples/guide/launcher_icons/example1.py index 9a23808..39d9c88 100644 --- a/docs/examples/guide/launcher_icons/example1.py +++ b/docs/examples/guide/launcher_icons/example1.py @@ -1,16 +1,16 @@ from pyflowlauncher import Plugin, Result, send_results -from pyflowlauncher.result import ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse from pyflowlauncher.icons import ADMIN plugin = Plugin() @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", - IcoPath=ADMIN + title="This is a title!", + subtitle="This is the subtitle!", + icon=ADMIN ) return send_results([r]) diff --git a/docs/examples/guide/plugin_methods/example1.py b/docs/examples/guide/plugin_methods/example1.py index 190f8f1..5328afc 100644 --- a/docs/examples/guide/plugin_methods/example1.py +++ b/docs/examples/guide/plugin_methods/example1.py @@ -1,15 +1,15 @@ from pyflowlauncher import Plugin, Result, send_results -from pyflowlauncher.result import ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse plugin = Plugin() @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", - JsonRPCAction={"method": "action", "parameters": []} + title="This is a title!", + subtitle="This is the subtitle!", + json_rpc_action={"Method": "action", "Parameters": []} ) return send_results([r]) diff --git a/docs/examples/guide/plugin_methods/example2.py b/docs/examples/guide/plugin_methods/example2.py index 023cd65..6e7dcb1 100644 --- a/docs/examples/guide/plugin_methods/example2.py +++ b/docs/examples/guide/plugin_methods/example2.py @@ -1,16 +1,16 @@ from pyflowlauncher import Plugin, Result, send_results -from pyflowlauncher.result import ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse plugin = Plugin() @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", - JsonRPCAction=plugin.action(action, ["stuff"]) + title="This is a title!", + subtitle="This is the subtitle!", ) + r.add_action(action, ["stuff"]) return send_results([r]) diff --git a/docs/examples/guide/scoring_results/example1.py b/docs/examples/guide/scoring_results/example1.py index 38bf7be..2b1255c 100644 --- a/docs/examples/guide/scoring_results/example1.py +++ b/docs/examples/guide/scoring_results/example1.py @@ -1,17 +1,17 @@ from pyflowlauncher import Plugin, Result, send_results -from pyflowlauncher.result import ResultResponse +from pyflowlauncher.models.json_rpc import JsonRPCResponse from pyflowlauncher.utils import score_results plugin = Plugin() @plugin.on_method -def query(query: str) -> ResultResponse: +def query(query: str) -> JsonRPCResponse: results = [] for _ in range(100): r = Result( - Title="This is a title!", - SubTitle="This is the subtitle!", + title="This is a title!", + subtitle="This is the subtitle!", ) results.append(r) return send_results(score_results(query, results)) From 8b83fe62b17ecf3b9ea61780346e9b5d9087c264 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 16:14:45 -0400 Subject: [PATCH 31/37] Refactor: Update return type of action functions to JsonRPCRequest --- pyflowlauncher/api.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pyflowlauncher/api.py b/pyflowlauncher/api.py index f0b36cb..b5c4321 100644 --- a/pyflowlauncher/api.py +++ b/pyflowlauncher/api.py @@ -1,79 +1,79 @@ from typing import Optional -from .result import JsonRPCAction +from .models.json_rpc import JsonRPCRequest NAME_SPACE = 'Flow.Launcher' -def _send_action(method: str, *parameters) -> JsonRPCAction: +def _send_action(method: str, *parameters) -> JsonRPCRequest: return {"method": f"{NAME_SPACE}.{method}", "parameters": parameters} -def change_query(query: str, requery: bool = False) -> JsonRPCAction: +def change_query(query: str, requery: bool = False) -> JsonRPCRequest: """Change the query in Flow Launcher.""" return _send_action("ChangeQuery", query, requery) -def shell_run(command: str, filename: str = 'cmd.exe') -> JsonRPCAction: +def shell_run(command: str, filename: str = 'cmd.exe') -> JsonRPCRequest: """Run a shell command.""" return _send_action("ShellRun", command, filename) -def close_app() -> JsonRPCAction: +def close_app() -> JsonRPCRequest: """Close Flow Launcher.""" return _send_action("CloseApp") -def hide_app() -> JsonRPCAction: +def hide_app() -> JsonRPCRequest: """Hide Flow Launcher.""" return _send_action("HideApp") -def show_app() -> JsonRPCAction: +def show_app() -> JsonRPCRequest: """Show Flow Launcher.""" return _send_action("ShowApp") -def show_msg(title: str, sub_title: str, ico_path: str = "") -> JsonRPCAction: +def show_msg(title: str, sub_title: str, ico_path: str = "") -> JsonRPCRequest: """Show a message in Flow Launcher.""" return _send_action("ShowMsg", title, sub_title, ico_path) -def open_setting_dialog() -> JsonRPCAction: +def open_setting_dialog() -> JsonRPCRequest: """Open the settings window in Flow Launcher.""" return _send_action("OpenSettingDialog") -def start_loading_bar() -> JsonRPCAction: +def start_loading_bar() -> JsonRPCRequest: """Start the loading bar in Flow Launcher.""" return _send_action("StartLoadingBar") -def stop_loading_bar() -> JsonRPCAction: +def stop_loading_bar() -> JsonRPCRequest: """Stop the loading bar in Flow Launcher.""" return _send_action("StopLoadingBar") -def reload_plugins() -> JsonRPCAction: +def reload_plugins() -> JsonRPCRequest: """Reload the plugins in Flow Launcher.""" return _send_action("ReloadPlugins") -def copy_to_clipboard(text: str, direct_copy: bool = False, show_default_notification=True) -> JsonRPCAction: +def copy_to_clipboard(text: str, direct_copy: bool = False, show_default_notification=True) -> JsonRPCRequest: """Copy text to the clipboard.""" return _send_action("CopyToClipboard", text, direct_copy, show_default_notification) -def open_directory(directory_path: str, filename_or_filepath: Optional[str] = None) -> JsonRPCAction: +def open_directory(directory_path: str, filename_or_filepath: Optional[str] = None) -> JsonRPCRequest: """Open a directory.""" return _send_action("OpenDirectory", directory_path, filename_or_filepath) -def open_url(url: str, in_private: bool = False) -> JsonRPCAction: +def open_url(url: str, in_private: bool = False) -> JsonRPCRequest: """Open a URL.""" return _send_action("OpenUrl", url, in_private) -def open_uri(uri: str) -> JsonRPCAction: +def open_uri(uri: str) -> JsonRPCRequest: """Open a URI.""" return _send_action("OpenAppUri", uri) From e4bc6123bdabdda3b8d283d3ddbea1d21ed77d82 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 16:51:23 -0400 Subject: [PATCH 32/37] Refactor: Include "pyflowlauncher.models" in setuptools packages --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ac1764f..19c0eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ all = ['typing_extensions>=4.8.0'] [tool.setuptools] -packages = ["pyflowlauncher"] +packages = ["pyflowlauncher", "pyflowlauncher.models"] [tool.setuptools.package-data] "pyflowlauncher" = ["py.typed"] From ce535ee7bb18975ec9f1c7df173c016c68b017c1 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 16:51:40 -0400 Subject: [PATCH 33/37] Refactor: Update JsonRPCRequest structure and improve method signatures for consistency --- pyflowlauncher/api.py | 2 +- pyflowlauncher/models/json_rpc.py | 2 +- pyflowlauncher/plugin.py | 5 ++--- pyflowlauncher/result.py | 9 +++++++-- pyflowlauncher/utils.py | 6 +++--- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pyflowlauncher/api.py b/pyflowlauncher/api.py index b5c4321..37359e3 100644 --- a/pyflowlauncher/api.py +++ b/pyflowlauncher/api.py @@ -6,7 +6,7 @@ def _send_action(method: str, *parameters) -> JsonRPCRequest: - return {"method": f"{NAME_SPACE}.{method}", "parameters": parameters} + return {"Method": f"{NAME_SPACE}.{method}", "Parameters": list(parameters)} def change_query(query: str, requery: bool = False) -> JsonRPCRequest: diff --git a/pyflowlauncher/models/json_rpc.py b/pyflowlauncher/models/json_rpc.py index f4764c9..a57eef3 100644 --- a/pyflowlauncher/models/json_rpc.py +++ b/pyflowlauncher/models/json_rpc.py @@ -16,4 +16,4 @@ class JsonRPCResponse(TypedDict): class JsonRPCResult(Result): JsonRPCAction: NotRequired[JsonRPCRequest] - SettingsChange: NotRequired[Dict[str, Any]] \ No newline at end of file + SettingsChange: NotRequired[Dict[str, Any]] diff --git a/pyflowlauncher/plugin.py b/pyflowlauncher/plugin.py index a9e4ae1..09458e5 100644 --- a/pyflowlauncher/plugin.py +++ b/pyflowlauncher/plugin.py @@ -2,11 +2,10 @@ import sys from functools import cached_property, wraps -from typing import Any, Callable, Iterable, Optional, Type, Union, List +from typing import Any, Callable, Iterable, Optional, Type, List from pathlib import Path import asyncio -from pyflowlauncher.models.json_rpc import JsonRPCResponse from .base import pyFlowLauncherObject from .event import EventHandler @@ -15,7 +14,7 @@ from .manifest import Manifest -Method = Callable[..., Union[JsonRPCResponse, JsonRPCRequest, None]] +Method = Callable[..., Any] class Plugin(pyFlowLauncherObject): diff --git a/pyflowlauncher/result.py b/pyflowlauncher/result.py index 18bd1f4..cd34800 100644 --- a/pyflowlauncher/result.py +++ b/pyflowlauncher/result.py @@ -14,7 +14,10 @@ class MethodNotRegisteredError(Exception): def __init__(self, method: Callable[..., Any]): self.method = method - super().__init__(f"Method {method.__name__} is not registered as a plugin method. Please use the @plugin.on_method decorator to register it.") + super().__init__( + f"Method {method.__name__} is not registered as a plugin method. " + "Please use the @plugin.on_method decorator to register it." + ) @dataclass @@ -32,7 +35,9 @@ class Result: preview: Optional[PreviewInfo] = None title_highlight_data: Optional[List[int]] = None - def add_action(self, method: Method, parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False) -> None: + def add_action( + self, method: Method, parameters: Optional[Iterable[Any]] = None, dont_hide_after_action: bool = False + ) -> None: """Adds a JsonRPC action to the result.""" if not getattr(method, '_is_registered_method', False): raise MethodNotRegisteredError(method) diff --git a/pyflowlauncher/utils.py b/pyflowlauncher/utils.py index 754b9fe..0ba2441 100644 --- a/pyflowlauncher/utils.py +++ b/pyflowlauncher/utils.py @@ -12,10 +12,10 @@ def score_results( for result in results: match = string_matcher( query, - result.Title, + result.title, query_search_precision=score_cutoff ) if match.matched or (match_on_empty_query and not query): - result.TitleHighlightData = match.index_list - result.Score = match.score + result.title_highlight_data = match.index_list + result.score = match.score yield result From daa8431188aa1907c2c7fa44905362c39fe5dd9c Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 16:51:48 -0400 Subject: [PATCH 34/37] Refactor: Update API response keys to follow consistent casing and improve Result handling --- tests/test_api.py | 30 ++++++++++----------- tests/test_method.py | 2 +- tests/test_plugin.py | 4 +-- tests/test_result.py | 62 ++++++++++++++++++++++---------------------- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 3e2afe9..812a4ef 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,60 +2,60 @@ def test_send_action(): - assert api._send_action("Test", "Test") == {"method": "Flow.Launcher.Test", "parameters": ("Test",)} + assert api._send_action("Test", "Test") == {"Method": "Flow.Launcher.Test", "Parameters": ["Test"]} def test_change_query(): - assert api.change_query("Test", False) == {"method": "Flow.Launcher.ChangeQuery", "parameters": ("Test", False)} + assert api.change_query("Test", False) == {"Method": "Flow.Launcher.ChangeQuery", "Parameters": ["Test", False]} def test_shell_run(): - assert api.shell_run("Test", "Test") == {"method": "Flow.Launcher.ShellRun", "parameters": ("Test", "Test")} + assert api.shell_run("Test", "Test") == {"Method": "Flow.Launcher.ShellRun", "Parameters": ["Test", "Test"]} def test_close_app(): - assert api.close_app() == {"method": "Flow.Launcher.CloseApp", "parameters": ()} + assert api.close_app() == {"Method": "Flow.Launcher.CloseApp", "Parameters": []} def test_hide_app(): - assert api.hide_app() == {"method": "Flow.Launcher.HideApp", "parameters": ()} + assert api.hide_app() == {"Method": "Flow.Launcher.HideApp", "Parameters": []} def test_show_app(): - assert api.show_app() == {"method": "Flow.Launcher.ShowApp", "parameters": ()} + assert api.show_app() == {"Method": "Flow.Launcher.ShowApp", "Parameters": []} def test_show_msg(): - assert api.show_msg("Test", "Test", "Test") == {"method": "Flow.Launcher.ShowMsg", "parameters": ("Test", "Test", "Test")} + assert api.show_msg("Test", "Test", "Test") == {"Method": "Flow.Launcher.ShowMsg", "Parameters": ["Test", "Test", "Test"]} def test_open_setting_dialog(): - assert api.open_setting_dialog() == {"method": "Flow.Launcher.OpenSettingDialog", "parameters": ()} + assert api.open_setting_dialog() == {"Method": "Flow.Launcher.OpenSettingDialog", "Parameters": []} def test_start_loading_bar(): - assert api.start_loading_bar() == {"method": "Flow.Launcher.StartLoadingBar", "parameters": ()} + assert api.start_loading_bar() == {"Method": "Flow.Launcher.StartLoadingBar", "Parameters": []} def test_stop_loading_bar(): - assert api.stop_loading_bar() == {"method": "Flow.Launcher.StopLoadingBar", "parameters": ()} + assert api.stop_loading_bar() == {"Method": "Flow.Launcher.StopLoadingBar", "Parameters": []} def test_reload_plugins(): - assert api.reload_plugins() == {"method": "Flow.Launcher.ReloadPlugins", "parameters": ()} + assert api.reload_plugins() == {"Method": "Flow.Launcher.ReloadPlugins", "Parameters": []} def test_copy_to_clipboard(): - assert api.copy_to_clipboard("Test", False, True) == {"method": "Flow.Launcher.CopyToClipboard", "parameters": ("Test", False, True)} + assert api.copy_to_clipboard("Test", False, True) == {"Method": "Flow.Launcher.CopyToClipboard", "Parameters": ["Test", False, True]} def test_open_directory(): - assert api.open_directory("Test", "Test") == {"method": "Flow.Launcher.OpenDirectory", "parameters": ("Test", "Test")} + assert api.open_directory("Test", "Test") == {"Method": "Flow.Launcher.OpenDirectory", "Parameters": ["Test", "Test"]} def test_open_url(): - assert api.open_url("Test", False) == {"method": "Flow.Launcher.OpenUrl", "parameters": ("Test", False)} + assert api.open_url("Test", False) == {"Method": "Flow.Launcher.OpenUrl", "Parameters": ["Test", False]} def test_open_uri(): - assert api.open_uri("Test") == {"method": "Flow.Launcher.OpenAppUri", "parameters": ("Test",)} + assert api.open_uri("Test") == {"Method": "Flow.Launcher.OpenAppUri", "Parameters": ["Test"]} diff --git a/tests/test_method.py b/tests/test_method.py index 5aee754..9e088cf 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -22,4 +22,4 @@ def __call__(self, *args, **kwargs): return self.return_results() method = TestMethod() - assert method() == {"result": [result.as_dict()], 'SettingsChange': None} + assert method() == {"Result": [result.to_json()], 'SettingsChange': None} diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 6dba757..7f0ec1b 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -42,7 +42,7 @@ def test_root_dir(tmp_path, monkeypatch): monkeypatch.setattr('sys.argv', [tmp_path / 'plugin.py']) monkeypatch.setattr('pyflowlauncher.plugin.Path.exists', lambda _: True) plugin = Plugin() - assert plugin.root_dir() == tmp_path + assert plugin.root_dir == tmp_path def test_root_dir_not_found(tmp_path, monkeypatch): @@ -50,7 +50,7 @@ def test_root_dir_not_found(tmp_path, monkeypatch): monkeypatch.setattr('pyflowlauncher.plugin.Path.exists', lambda _: False) plugin = Plugin() with pytest.raises(FileNotFoundError): - assert plugin.root_dir() == tmp_path + assert plugin.root_dir == tmp_path def test_action(): diff --git a/tests/test_result.py b/tests/test_result.py index 557c897..3073f30 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -3,21 +3,21 @@ def test_asdict(): r = Result( - Title="Test", - SubTitle="Test", - IcoPath="Test.png", - TitleHighlightData=[0], - ContextData=["Test"], - Glyph={"Glyph": "Test", "FontFamily": "Test"}, - CopyText="Test", - AutoCompleteText="Test", - RoundedIcon=True, - JsonRPCAction={ + title="Test", + subtitle="Test", + icon="Test.png", + title_highlight_data=[0], + context_data=["Test"], + glyph={"Glyph": "Test", "FontFamily": "Test"}, + copy_text="Test", + auto_complete_text="Test", + rounded_icon=True, + json_rpc_action={ "Method": "Test", "Parameters": ["Test"], "DontHideAfterAction": True }, - Preview={ + preview={ "PreviewImagePath": "Test.png", "Description": "Test", "IsMedia": True, @@ -25,25 +25,25 @@ def test_asdict(): } ) assert r.as_dict() == { - "Title": "Test", - "SubTitle": "Test", - "IcoPath": "Test.png", - "TitleHighlightData": [0], - "Score": 0, - "JsonRPCAction": { + "title": "Test", + "subtitle": "Test", + "icon": "Test.png", + "title_highlight_data": [0], + "score": 0, + "json_rpc_action": { "Method": "Test", "Parameters": ["Test"], "DontHideAfterAction": True }, - "ContextData": ["Test"], - "Glyph": { + "context_data": ["Test"], + "glyph": { "Glyph": "Test", "FontFamily": "Test" }, - "CopyText": "Test", - "AutoCompleteText": "Test", - "RoundedIcon": True, - "Preview": { + "copy_text": "Test", + "auto_complete_text": "Test", + "rounded_icon": True, + "preview": { "PreviewImagePath": "Test.png", "Description": "Test", "IsMedia": True, @@ -53,12 +53,12 @@ def test_asdict(): def test_add_action(): - r = Result( - Title="Test" - ) - r.add_action(lambda: None, ["Test"], dont_hide_after_action=True) - assert r.JsonRPCAction == { - "method": "", - "parameters": ["Test"], - "dontHideAfterAction": True + r = Result(title="Test") + method = lambda: None + method._is_registered_method = True + r.add_action(method, ["Test"], dont_hide_after_action=True) + assert r.json_rpc_action == { + "Method": "", + "Parameters": ["Test"], + "DontHideAfterAction": True } From 25097ae8070c72a0e97d1fe0b9dfd48d0875c8e9 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 17:10:50 -0400 Subject: [PATCH 35/37] Fix: Update imports for compat --- pyflowlauncher/models/json_rpc.py | 8 +++++++- pyflowlauncher/models/plugin_manifest.py | 8 +++++++- pyflowlauncher/models/result.py | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pyflowlauncher/models/json_rpc.py b/pyflowlauncher/models/json_rpc.py index a57eef3..830cc34 100644 --- a/pyflowlauncher/models/json_rpc.py +++ b/pyflowlauncher/models/json_rpc.py @@ -1,4 +1,10 @@ -from typing import Any, Dict, List, NotRequired, Optional, TypedDict +import sys +from typing import Any, Dict, List, Optional + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, TypedDict +else: + from typing import NotRequired, TypedDict from .result import Result diff --git a/pyflowlauncher/models/plugin_manifest.py b/pyflowlauncher/models/plugin_manifest.py index 39f6782..c4a792e 100644 --- a/pyflowlauncher/models/plugin_manifest.py +++ b/pyflowlauncher/models/plugin_manifest.py @@ -1,4 +1,10 @@ -from typing import TypedDict, List, NotRequired +import sys +from typing import List + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, TypedDict +else: + from typing import NotRequired, TypedDict FILE_NAME = 'plugin.json' diff --git a/pyflowlauncher/models/result.py b/pyflowlauncher/models/result.py index 9ac9f0a..3d01ef4 100644 --- a/pyflowlauncher/models/result.py +++ b/pyflowlauncher/models/result.py @@ -1,4 +1,10 @@ -from typing import TypedDict, Optional, Iterable, Required, NotRequired +import sys +from typing import Optional, Required, Iterable + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, TypedDict +else: + from typing import NotRequired, TypedDict class Glyph(TypedDict): From 9588a583d21b4094b7b9c28499d7da1990b047a5 Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 17:12:34 -0400 Subject: [PATCH 36/37] Refactor: Import sys and conditionally import Self based on Python version --- pyflowlauncher/manifest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py index 513abed..a577300 100644 --- a/pyflowlauncher/manifest.py +++ b/pyflowlauncher/manifest.py @@ -1,7 +1,12 @@ from dataclasses import dataclass, field import json -from typing import Self from pathlib import Path +import sys + +if sys.version_info < (3, 11): + from typing_extensions import Self +else: + from typing import Self from .models.plugin_manifest import FILE_NAME, PluginMetadata From dc3b5ec6e3c0492a7c16e1ecc9d97050cfdbaa3f Mon Sep 17 00:00:00 2001 From: Garulf <535299+Garulf@users.noreply.github.com> Date: Sun, 31 May 2026 17:21:34 -0400 Subject: [PATCH 37/37] Refactor: Add future annotations import and adjust typing imports for consistency --- pyflowlauncher/manifest.py | 2 ++ pyflowlauncher/models/result.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyflowlauncher/manifest.py b/pyflowlauncher/manifest.py index a577300..3635a2c 100644 --- a/pyflowlauncher/manifest.py +++ b/pyflowlauncher/manifest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass, field import json from pathlib import Path diff --git a/pyflowlauncher/models/result.py b/pyflowlauncher/models/result.py index 3d01ef4..12b443d 100644 --- a/pyflowlauncher/models/result.py +++ b/pyflowlauncher/models/result.py @@ -1,10 +1,10 @@ import sys -from typing import Optional, Required, Iterable +from typing import Optional, Iterable if sys.version_info < (3, 11): - from typing_extensions import NotRequired, TypedDict + from typing_extensions import NotRequired, TypedDict, Required else: - from typing import NotRequired, TypedDict + from typing import NotRequired, TypedDict, Required class Glyph(TypedDict):