alfred-async 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alfred_async-0.1.0/PKG-INFO +6 -0
- alfred_async-0.1.0/README.md +0 -0
- alfred_async-0.1.0/pyproject.toml +32 -0
- alfred_async-0.1.0/setup.cfg +4 -0
- alfred_async-0.1.0/src/alfred_async/__init__.py +132 -0
- alfred_async-0.1.0/src/alfred_async.egg-info/PKG-INFO +6 -0
- alfred_async-0.1.0/src/alfred_async.egg-info/SOURCES.txt +7 -0
- alfred_async-0.1.0/src/alfred_async.egg-info/dependency_links.txt +1 -0
- alfred_async-0.1.0/src/alfred_async.egg-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "alfred-async"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["setuptools"]
|
|
11
|
+
build-backend = "setuptools.build_meta"
|
|
12
|
+
|
|
13
|
+
[tool.ruff]
|
|
14
|
+
line-length = 100
|
|
15
|
+
|
|
16
|
+
[tool.ruff.lint] # https://beta.ruff.rs/docs/rules/
|
|
17
|
+
select = [
|
|
18
|
+
"C", # Complexity
|
|
19
|
+
"E", # pycodestyle
|
|
20
|
+
"F", # Unused imports
|
|
21
|
+
"I", # isort
|
|
22
|
+
"PGH004", # Use specific rule codes when using noqa
|
|
23
|
+
"PLC0414", # Useless import alias. Import alias does not rename original package.
|
|
24
|
+
"S103", # bad-file-permissions
|
|
25
|
+
"TRY004", # Prefer TypeError exception for invalid type
|
|
26
|
+
"UP", # pyupgrade
|
|
27
|
+
"W", # pycodestyle
|
|
28
|
+
]
|
|
29
|
+
ignore = [
|
|
30
|
+
"E501", # Don't enforce line length for now
|
|
31
|
+
"E741", # Ambiguous variable name
|
|
32
|
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import asyncio
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
import plistlib
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
9
|
+
from functools import wraps
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def hash(string):
|
|
15
|
+
h = hashlib.new("sha256")
|
|
16
|
+
h.update(string.encode())
|
|
17
|
+
return h.hexdigest()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Path(pathlib.Path):
|
|
21
|
+
def mtime(self) -> datetime:
|
|
22
|
+
return datetime.fromtimestamp(self.stat().st_mtime, tz=UTC)
|
|
23
|
+
|
|
24
|
+
def age(self) -> timedelta:
|
|
25
|
+
return datetime.now(UTC) - self.mtime()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cache_json(f):
|
|
29
|
+
@wraps(f)
|
|
30
|
+
async def wrapper(client: "WorkflowClient", *args, **kwds):
|
|
31
|
+
key = hash(str(kwds))
|
|
32
|
+
path = Path(client.cache_directory / key).with_suffix(".json")
|
|
33
|
+
if client.is_cached(path):
|
|
34
|
+
logger.debug("Reading data for %s", path)
|
|
35
|
+
with path.open() as fp:
|
|
36
|
+
return json.load(fp)
|
|
37
|
+
data = await f(client, *args, **kwds)
|
|
38
|
+
logger.info("Writing data for %s", path)
|
|
39
|
+
if not client.cache_directory.exists():
|
|
40
|
+
client.cache_directory.mkdir(parents=True)
|
|
41
|
+
with path.open("w") as fp:
|
|
42
|
+
json.dump(data, fp, indent=True)
|
|
43
|
+
return data
|
|
44
|
+
|
|
45
|
+
return wrapper
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cache_path(f):
|
|
49
|
+
@wraps(f)
|
|
50
|
+
async def wrapper(client: "WorkflowClient", *args, **kwds):
|
|
51
|
+
key = hash(str(kwds))
|
|
52
|
+
path = Path(client.cache_directory / key)
|
|
53
|
+
if client.is_cached(path):
|
|
54
|
+
logger.debug("Reading data for %s", path)
|
|
55
|
+
return path
|
|
56
|
+
data = await f(client, *args, **kwds)
|
|
57
|
+
logger.info("Writing data for %s", path)
|
|
58
|
+
if not client.cache_directory.exists():
|
|
59
|
+
client.cache_directory.mkdir(parents=True)
|
|
60
|
+
with path.open("wb+") as fp:
|
|
61
|
+
fp.write(data)
|
|
62
|
+
return path
|
|
63
|
+
|
|
64
|
+
return wrapper
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class JsonPathEncoder(json.JSONEncoder):
|
|
68
|
+
def default(self, o):
|
|
69
|
+
if isinstance(o, Path):
|
|
70
|
+
return str(o)
|
|
71
|
+
return super().default(o)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class WorkflowClient:
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.plist_path = Path.cwd() / "info.plist"
|
|
77
|
+
if self.plist_path.exists():
|
|
78
|
+
with self.plist_path.open("rb") as fp:
|
|
79
|
+
self.plist = plistlib.load(fp)
|
|
80
|
+
self.bundleid = self.plist["bundleid"]
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def alfred_preferences(self) -> Path:
|
|
84
|
+
return (
|
|
85
|
+
Path.home()
|
|
86
|
+
/ "Library"
|
|
87
|
+
/ "Application Support"
|
|
88
|
+
/ "Alfred"
|
|
89
|
+
/ "Alfred.preferences"
|
|
90
|
+
/ self.bundleid
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def cache_directory(self) -> Path:
|
|
95
|
+
return (
|
|
96
|
+
Path.home()
|
|
97
|
+
/ "Caches"
|
|
98
|
+
/ "com.runningwithcrayons.Alfred"
|
|
99
|
+
/ "Workflow Data"
|
|
100
|
+
/ self.bundleid
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def is_cached(self, path: Path, duration=timedelta(minutes=5)):
|
|
104
|
+
if path.exists():
|
|
105
|
+
if path.age() < duration:
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
results = []
|
|
110
|
+
|
|
111
|
+
async def add_result(self, icon_path: str = None, **kwargs):
|
|
112
|
+
if icon_path and icon_path.startswith("http"):
|
|
113
|
+
logger.debug("need to download %s", icon_path)
|
|
114
|
+
kwargs["icon_path"] = await self.icon(icon_path)
|
|
115
|
+
self.results.append(kwargs)
|
|
116
|
+
|
|
117
|
+
async def loop(self):
|
|
118
|
+
try:
|
|
119
|
+
await self.run()
|
|
120
|
+
except Exception:
|
|
121
|
+
logger.exception("Error with workflow")
|
|
122
|
+
else:
|
|
123
|
+
print(json.dumps({"items": self.results}, cls=JsonPathEncoder))
|
|
124
|
+
|
|
125
|
+
@abc.abstractmethod
|
|
126
|
+
async def run(self):
|
|
127
|
+
raise NotImplementedError()
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def main(cls):
|
|
131
|
+
client = cls()
|
|
132
|
+
return asyncio.run(client.loop())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alfred_async
|