nervous-notes 0.0.2__tar.gz → 0.0.3__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.
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/PKG-INFO +18 -5
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/README.md +17 -4
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/pyproject.toml +3 -2
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/dsl.py +5 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Next-Notes MOC.py +0 -1
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/nervous_note.py +30 -10
- nervous_notes-0.0.3/src/nervous_notes/nervous_note_router.py +50 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/note_companion_script.py +28 -21
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/note_embedded_script.py +3 -3
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/orchestration.py +1 -1
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/wrappers/external_messages.py +7 -2
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/wrappers/note_api.py +0 -2
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/wrappers/scripting.py +18 -6
- nervous_notes-0.0.2/src/nervous_notes/vault_router.py +0 -33
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/.gitignore +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/LICENSE +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/__init__.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/__main__.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Daily Responsibility.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Extrovert.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Introvert.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Recurring Note Generator.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Literate Programming - Hello World.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Mqtt Publisher.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Mqtt Subscriber.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Next Notes Manager.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Timer Demo.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/nervous_process.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/requirements.txt +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/util.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/wrappers/file_watcher.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/tests/experimental_test.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/tests/nextnotes_moc_test.py +0 -0
- {nervous_notes-0.0.2 → nervous_notes-0.0.3}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nervous-notes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Personal knowledge management meets the actor model
|
|
5
5
|
Project-URL: Homepage, https://github.com/micseydel/nervous_notes
|
|
6
6
|
Project-URL: Issues, https://github.com/micseydel/nervous_notes/issues
|
|
@@ -53,6 +53,16 @@ this_is: yaml
|
|
|
53
53
|
|
|
54
54
|
FIXME `--library`
|
|
55
55
|
|
|
56
|
+
|
|
57
|
+
### Companion scripts and the DSL
|
|
58
|
+
|
|
59
|
+
- on_start
|
|
60
|
+
- on_note_modified
|
|
61
|
+
- on_mqtt_message
|
|
62
|
+
- on_timer
|
|
63
|
+
- on_message (or is it on_other_message? different from on_receive)
|
|
64
|
+
- on_stop
|
|
65
|
+
|
|
56
66
|
## Glossary
|
|
57
67
|
|
|
58
68
|
- FIXME
|
|
@@ -73,10 +83,6 @@ FIXME `--library`
|
|
|
73
83
|
|
|
74
84
|
- FIXME (placeholder)
|
|
75
85
|
|
|
76
|
-
## Polyglot programming
|
|
77
|
-
|
|
78
|
-
This module currently includes mqtt support, which allows for sending messages between programs in different languages.
|
|
79
|
-
|
|
80
86
|
## Knowledge Based Programming
|
|
81
87
|
|
|
82
88
|
Again, from the actor model paper:
|
|
@@ -103,8 +109,10 @@ I originally implemented this in Scala, where I called it "[woke notes](https://
|
|
|
103
109
|
|
|
104
110
|
## Future work
|
|
105
111
|
|
|
112
|
+
- remove all FIXMEs from the code
|
|
106
113
|
- remove mqtt as a default dependency, and make it, kafka, and rabbitmq [optional dependencies](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#optional-dependencies)
|
|
107
114
|
- maybe a "whisper" optional dependency for AI transcription (since this was one of my primary use-cases)
|
|
115
|
+
- ntfy doesn't need to be core either, so `requests` could similarly be made an optional dependency
|
|
108
116
|
- similar modules for other languages/platforms (e.g. JVM, Javascript, Go, etc.) to improve polyglot support
|
|
109
117
|
- is Obsidian Canvas rich enough to wire actors together into flows?
|
|
110
118
|
- improved isolation between companion notes
|
|
@@ -112,3 +120,8 @@ I originally implemented this in Scala, where I called it "[woke notes](https://
|
|
|
112
120
|
- replace Pykka with something more Akka-like
|
|
113
121
|
- this would likely be an _enormous_ undertaking
|
|
114
122
|
- support actor-scoped `--library` imports, so that hot-reloading of imports can work
|
|
123
|
+
- Change .wake() class method design
|
|
124
|
+
- Don't bother trying for a uniform interface with pykka anymore
|
|
125
|
+
- ...because wake()ing is idempotent, has a different semantic
|
|
126
|
+
- Jumping to the wake() implementation is confusing
|
|
127
|
+
- Composition instead of inheritance?
|
|
@@ -32,6 +32,16 @@ this_is: yaml
|
|
|
32
32
|
|
|
33
33
|
FIXME `--library`
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
### Companion scripts and the DSL
|
|
37
|
+
|
|
38
|
+
- on_start
|
|
39
|
+
- on_note_modified
|
|
40
|
+
- on_mqtt_message
|
|
41
|
+
- on_timer
|
|
42
|
+
- on_message (or is it on_other_message? different from on_receive)
|
|
43
|
+
- on_stop
|
|
44
|
+
|
|
35
45
|
## Glossary
|
|
36
46
|
|
|
37
47
|
- FIXME
|
|
@@ -52,10 +62,6 @@ FIXME `--library`
|
|
|
52
62
|
|
|
53
63
|
- FIXME (placeholder)
|
|
54
64
|
|
|
55
|
-
## Polyglot programming
|
|
56
|
-
|
|
57
|
-
This module currently includes mqtt support, which allows for sending messages between programs in different languages.
|
|
58
|
-
|
|
59
65
|
## Knowledge Based Programming
|
|
60
66
|
|
|
61
67
|
Again, from the actor model paper:
|
|
@@ -82,8 +88,10 @@ I originally implemented this in Scala, where I called it "[woke notes](https://
|
|
|
82
88
|
|
|
83
89
|
## Future work
|
|
84
90
|
|
|
91
|
+
- remove all FIXMEs from the code
|
|
85
92
|
- remove mqtt as a default dependency, and make it, kafka, and rabbitmq [optional dependencies](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#optional-dependencies)
|
|
86
93
|
- maybe a "whisper" optional dependency for AI transcription (since this was one of my primary use-cases)
|
|
94
|
+
- ntfy doesn't need to be core either, so `requests` could similarly be made an optional dependency
|
|
87
95
|
- similar modules for other languages/platforms (e.g. JVM, Javascript, Go, etc.) to improve polyglot support
|
|
88
96
|
- is Obsidian Canvas rich enough to wire actors together into flows?
|
|
89
97
|
- improved isolation between companion notes
|
|
@@ -91,3 +99,8 @@ I originally implemented this in Scala, where I called it "[woke notes](https://
|
|
|
91
99
|
- replace Pykka with something more Akka-like
|
|
92
100
|
- this would likely be an _enormous_ undertaking
|
|
93
101
|
- support actor-scoped `--library` imports, so that hot-reloading of imports can work
|
|
102
|
+
- Change .wake() class method design
|
|
103
|
+
- Don't bother trying for a uniform interface with pykka anymore
|
|
104
|
+
- ...because wake()ing is idempotent, has a different semantic
|
|
105
|
+
- Jumping to the wake() implementation is confusing
|
|
106
|
+
- Composition instead of inheritance?
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nervous-notes"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Michael Seydel", email="contact@micseydel.me" },
|
|
10
10
|
]
|
|
@@ -20,8 +20,9 @@ license-files = ["LICEN[CS]E*"]
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"pykka==4.4.2",
|
|
22
22
|
"ruamel.yaml==0.19.1",
|
|
23
|
-
"paho-mqtt==2.1.0",
|
|
24
23
|
"watchdog==6.0.0",
|
|
24
|
+
# FIXME: make these optional
|
|
25
|
+
"paho-mqtt==2.1.0",
|
|
25
26
|
"requests==2.34.2",
|
|
26
27
|
]
|
|
27
28
|
|
|
@@ -9,6 +9,7 @@ import json
|
|
|
9
9
|
import datetime
|
|
10
10
|
import re
|
|
11
11
|
from time import ctime, sleep
|
|
12
|
+
from typing import Any
|
|
12
13
|
|
|
13
14
|
import requests
|
|
14
15
|
from pykka import ActorRef
|
|
@@ -62,3 +63,7 @@ def on_timer(key: str, payload: object) -> None: pass
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
def publish_to_ntfy(channel: str, message: str) -> None: pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def batch_publish_to_nervous_note(note_id: str, messages: list[Any]) -> None:
|
|
69
|
+
pass
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import os
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
3
4
|
from typing import TypeVar, Any, ClassVar
|
|
4
5
|
|
|
5
6
|
import pykka
|
|
6
7
|
from pykka import Actor, ActorRef
|
|
7
8
|
from watchdog.events import FileModifiedEvent, FileCreatedEvent
|
|
8
9
|
|
|
9
|
-
from .
|
|
10
|
+
from .nervous_note_router import NervousNoteRouter, MessageBatch, NervousNoteSubscription
|
|
10
11
|
from .wrappers.note_api import NoteAPI, PrimitiveNoteAPI
|
|
11
12
|
from .wrappers.external_messages import MqttPublish, MqttSubscription, MqttConfig, ExternalMessages
|
|
12
13
|
from .wrappers.file_watcher import VaultWatcher, VaultNoteSubscription
|
|
@@ -32,7 +33,10 @@ class NervousNoteSupport:
|
|
|
32
33
|
vault_name: str = None
|
|
33
34
|
file_path = None
|
|
34
35
|
external_messages_actor_ref = None
|
|
35
|
-
|
|
36
|
+
nervous_note_router = None
|
|
37
|
+
|
|
38
|
+
actor_cache: dict[str, ActorRef] = {}
|
|
39
|
+
lock = threading.Lock()
|
|
36
40
|
|
|
37
41
|
def __init__(self, vault_path: str, mqtt_config: MqttConfig):
|
|
38
42
|
self.vault_path = vault_path
|
|
@@ -41,12 +45,24 @@ class NervousNoteSupport:
|
|
|
41
45
|
self.vault_watcher = VaultWatcher.start(self.vault_path)
|
|
42
46
|
self.file_path = os.path.join(self.vault_path, "Nervous Notes Mqtt Orchestrator.md")
|
|
43
47
|
self.external_messages_actor_ref = ExternalMessages.start(mqtt_config, self.file_path)
|
|
44
|
-
self.
|
|
48
|
+
self.nervous_note_router = NervousNoteRouter.start()
|
|
49
|
+
|
|
50
|
+
def wake(self, cls, note_name: str, *args: Any, **kwargs: Any):
|
|
51
|
+
with self.lock:
|
|
52
|
+
if note_name in self.actor_cache:
|
|
53
|
+
logging.warning(f"[nervous_note.wake] Fetching {note_name} from cache, ignoring *args and **kwargs: {args} {kwargs}")
|
|
54
|
+
# FIXME: send *args and **kwargs to the actor to let it know about the double-wake?
|
|
55
|
+
# should also document/rename wake being different from start() (which also answers the uniform interface question)
|
|
56
|
+
return self.actor_cache[note_name]
|
|
57
|
+
else:
|
|
58
|
+
actor_ref = cls.start(note_name, *args, **kwargs)
|
|
59
|
+
self.actor_cache[note_name] = actor_ref
|
|
60
|
+
return actor_ref
|
|
45
61
|
|
|
46
62
|
def stop(self):
|
|
47
63
|
self.vault_watcher.stop()
|
|
48
64
|
self.external_messages_actor_ref.stop()
|
|
49
|
-
self.
|
|
65
|
+
self.nervous_note_router.stop()
|
|
50
66
|
|
|
51
67
|
|
|
52
68
|
class NervousNote(pykka.ThreadingActor):
|
|
@@ -58,7 +74,8 @@ class NervousNote(pykka.ThreadingActor):
|
|
|
58
74
|
vault_path: str,
|
|
59
75
|
mqtt_config: MqttConfig,
|
|
60
76
|
):
|
|
61
|
-
|
|
77
|
+
# FIXME: make this idempotent? probably not, but document in a [[Design Decisions]] note
|
|
78
|
+
assert cls.support is None, "Background actors already started"
|
|
62
79
|
cls.support = NervousNoteSupport(vault_path, mqtt_config)
|
|
63
80
|
|
|
64
81
|
@classmethod
|
|
@@ -77,15 +94,18 @@ class NervousNote(pykka.ThreadingActor):
|
|
|
77
94
|
) -> ActorRef[Actor]:
|
|
78
95
|
assert cls.support is not None, "Did you run NervousNote.start_background_actors(vault_path, mqtt_config)?"
|
|
79
96
|
|
|
80
|
-
#
|
|
81
|
-
actor_ref = cls.
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
# idempotency - grab from cache if existing
|
|
98
|
+
actor_ref = cls.support.wake(cls, note_name,
|
|
99
|
+
# e.g. this is where e.g. ScriptedNote.scripts_dir gets passed through
|
|
100
|
+
*args, **kwargs)
|
|
84
101
|
|
|
85
102
|
# a NervousNote adds events from mqtt and note updates
|
|
86
103
|
# FIXME: ...and timers??!
|
|
104
|
+
# FIXME: this is more convenient for a batch publisher anyway since the default_topic is more complicated than the note name
|
|
87
105
|
cls.support.vault_watcher.tell(VaultNoteSubscription(note_name, actor_ref))
|
|
88
106
|
|
|
107
|
+
cls.support.nervous_note_router.tell(NervousNoteSubscription(note_name, actor_ref))
|
|
108
|
+
|
|
89
109
|
default_topic = _default_topic(cls.support.vault_name, note_name)
|
|
90
110
|
cls.support.external_messages_actor_ref.tell(MqttSubscription(default_topic, actor_ref))
|
|
91
111
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pykka import ActorRef, ThreadingActor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class NervousNoteSubscription:
|
|
11
|
+
note_id: str
|
|
12
|
+
subscriber: ActorRef
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class PublishToNervousNote:
|
|
17
|
+
note_id: str # important for snoopers
|
|
18
|
+
payload: Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MessageBatch:
|
|
23
|
+
messages: list[Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class BatchPublishToNervousNote:
|
|
28
|
+
note_id: str # important for snoopers
|
|
29
|
+
messages: MessageBatch
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def of(note_id: str, messages: list[Any]):
|
|
33
|
+
return BatchPublishToNervousNote(note_id, MessageBatch(messages))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NervousNoteRouter(ThreadingActor):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__()
|
|
39
|
+
# FIXME since I'm changing things up anyway, a NervousNoteRef (or whatever) should probably include note_id
|
|
40
|
+
self.subscribers: dict[str, set[ActorRef]] = defaultdict(set)
|
|
41
|
+
|
|
42
|
+
def on_receive(self, message):
|
|
43
|
+
if isinstance(message, NervousNoteSubscription):
|
|
44
|
+
# a NervousNote subscribes to its own [[Note Name]] by default, but we allow for snoopers (for now)
|
|
45
|
+
self.subscribers[message.note_id].add(message.subscriber)
|
|
46
|
+
elif isinstance(message, (PublishToNervousNote, BatchPublishToNervousNote)):
|
|
47
|
+
for subscriber in self.subscribers[message.note_id]:
|
|
48
|
+
subscriber.tell(message)
|
|
49
|
+
else:
|
|
50
|
+
logging.warning(f"Unknown message type: {type(message)}")
|
|
@@ -8,6 +8,7 @@ from pykka import ActorRef, ActorDeadError
|
|
|
8
8
|
from watchdog.events import FileModifiedEvent, FileCreatedEvent
|
|
9
9
|
|
|
10
10
|
from .nervous_note import NervousNote, MqttWrapper
|
|
11
|
+
from .nervous_note_router import BatchPublishToNervousNote, PublishToNervousNote
|
|
11
12
|
from .wrappers.note_api import NoteAPI
|
|
12
13
|
from .wrappers.scripting import ScriptHarness, ScriptPath
|
|
13
14
|
from .wrappers.external_messages import MqttPublish
|
|
@@ -33,7 +34,7 @@ class NoteCompanionScript(NervousNote):
|
|
|
33
34
|
self.script_wrapper = ScriptedNoteHarness(
|
|
34
35
|
self.actor_ref, self.my_note,
|
|
35
36
|
self.topic, self.mqtt,
|
|
36
|
-
self.support.
|
|
37
|
+
self.support.nervous_note_router,
|
|
37
38
|
self.script_path,
|
|
38
39
|
)
|
|
39
40
|
self.script_wrapper.on_start()
|
|
@@ -53,6 +54,9 @@ class NoteCompanionScript(NervousNote):
|
|
|
53
54
|
elif message == "SCRIPT_MODIFIED":
|
|
54
55
|
logging.info(f"Hot reloading [[{self.note_name}]]")
|
|
55
56
|
self.script_wrapper.compile_script(this_is_a_recompile=True)
|
|
57
|
+
elif isinstance(message, (PublishToNervousNote, BatchPublishToNervousNote)):
|
|
58
|
+
# FIXME: worth renaming script_wrapper.on_other_message if it's really this specific?
|
|
59
|
+
self.script_wrapper.on_other_message(message)
|
|
56
60
|
else:
|
|
57
61
|
try:
|
|
58
62
|
message_type, key, payload = message
|
|
@@ -117,6 +121,7 @@ class NoteCompanionScriptsManager(NervousNote):
|
|
|
117
121
|
logging.warning(f"{maybe_note_name} was not in {list(self.nervous_notes.keys())}")
|
|
118
122
|
raise
|
|
119
123
|
except ActorDeadError:
|
|
124
|
+
# FIXME: do I need to log the exception here, or just an error?
|
|
120
125
|
logging.exception(f"{maybe_note_name} is dead, could not send a FileModifiedEvent to it")
|
|
121
126
|
logging.warning(f"NOT HOT RELOADING {message.src_path}") # FIXME - it DOES get hotreloaded, doesn't it?
|
|
122
127
|
else:
|
|
@@ -130,21 +135,23 @@ class NoteCompanionScriptsManager(NervousNote):
|
|
|
130
135
|
if message.src_path.startswith(self.support.vault_path):
|
|
131
136
|
pass # FIXME - on_note_created event?
|
|
132
137
|
elif message.src_path.startswith(self.scripts_dir):
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
# FIXME: gate starting new .py scripts behind [[Note Companion Scripts Manager]]
|
|
139
|
+
logging.warning(f"Ignoring {message.src_path}, restart the app to load the new script")
|
|
140
|
+
# script_path_to_wake = message.src_path
|
|
141
|
+
# note_name: str = os.path.splitext(os.path.split(script_path_to_wake)[1])[0]
|
|
142
|
+
# if note_name in self.nervous_notes:
|
|
143
|
+
# logging.warning(f"Got a file created event for script {script_path_to_wake} but it was already created (ignoring)")
|
|
144
|
+
# else:
|
|
145
|
+
# logging.info(f"Detected new script {script_path_to_wake}, loading it and adding it for hotreloading...")
|
|
146
|
+
# actor_ref = NoteCompanionScript.wake(
|
|
147
|
+
# note_name,
|
|
148
|
+
# ScriptPath(self.scripts_dir, note_name)
|
|
149
|
+
# )
|
|
150
|
+
# self.nervous_notes[note_name] = actor_ref
|
|
151
|
+
# self.__update_note()
|
|
145
152
|
else:
|
|
146
|
-
logging.warning(
|
|
147
|
-
|
|
153
|
+
logging.warning(f"unexpected new path `{message.src_path}` (did not start with scripts dir `{self.scripts_dir}` or "
|
|
154
|
+
f"vault dir `{self.support.vault_path}`)")
|
|
148
155
|
elif isinstance(message, MqttPublish):
|
|
149
156
|
self.on_mqtt_message(message.topic, message.payload)
|
|
150
157
|
else:
|
|
@@ -172,22 +179,22 @@ class NoteCompanionScriptsManager(NervousNote):
|
|
|
172
179
|
|
|
173
180
|
|
|
174
181
|
class ScriptedNoteHarness(ScriptHarness):
|
|
175
|
-
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper,
|
|
176
|
-
super().__init__(actor_ref, my_note, topic, mqtt,
|
|
182
|
+
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper, nervous_note_router: ActorRef, script_path: ScriptPath):
|
|
183
|
+
super().__init__(actor_ref, my_note, topic, mqtt, nervous_note_router, script_path)
|
|
177
184
|
self._scripts = []
|
|
178
|
-
self.script_scope["wake"] = self.
|
|
185
|
+
self.script_scope["wake"] = self.__wake
|
|
179
186
|
self.script_scope["TemplateScript"] = TemplateScript
|
|
180
187
|
|
|
181
|
-
def
|
|
188
|
+
def __wake(self, filename: str, template_script: TemplateScript) -> ActorRef:
|
|
182
189
|
if isinstance(template_script, TemplateScript):
|
|
183
|
-
script = NoteCompanionScript.wake(
|
|
190
|
+
script = NoteCompanionScript.wake(
|
|
184
191
|
filename,
|
|
185
192
|
ScriptPath(os.path.join(self.script_path.scripts_dir, "templates"), template_script.script_name)
|
|
186
193
|
)
|
|
187
194
|
self._scripts.append(script)
|
|
188
195
|
return script
|
|
189
196
|
else:
|
|
190
|
-
raise NotImplementedError
|
|
197
|
+
raise NotImplementedError(f"Only TemplateScript templates are supported currently")
|
|
191
198
|
|
|
192
199
|
def get_script(self) -> str:
|
|
193
200
|
with open(self.script_path.script_path) as f:
|
|
@@ -53,7 +53,7 @@ class NoteEmbeddedScript(NervousNote):
|
|
|
53
53
|
|
|
54
54
|
self.script_wrapper = NoteEmbeddedScriptHarness(self.actor_ref, self.my_note,
|
|
55
55
|
self.topic, self.mqtt,
|
|
56
|
-
self.support.
|
|
56
|
+
self.support.nervous_note_router)
|
|
57
57
|
self.script_wrapper.on_start()
|
|
58
58
|
|
|
59
59
|
def on_receive(self, message):
|
|
@@ -85,8 +85,8 @@ class NoteEmbeddedScript(NervousNote):
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class NoteEmbeddedScriptHarness(ScriptHarness):
|
|
88
|
-
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper,
|
|
89
|
-
super().__init__(actor_ref, my_note, topic, mqtt,
|
|
88
|
+
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper, nervous_note_router: ActorRef):
|
|
89
|
+
super().__init__(actor_ref, my_note, topic, mqtt, nervous_note_router, f"{my_note.note_path}#Code")
|
|
90
90
|
|
|
91
91
|
def get_script(self) -> str:
|
|
92
92
|
with open(self.my_note.note_path) as f:
|
|
@@ -20,7 +20,7 @@ class Orchestrator(NervousNote):
|
|
|
20
20
|
# this runs in the spawner's context, whereas on_start runs in the actor thread
|
|
21
21
|
super().__init__(note_name, *args, **kwargs)
|
|
22
22
|
|
|
23
|
-
self.scripts_dir = scripts_dir
|
|
23
|
+
self.scripts_dir = os.path.abspath(scripts_dir)
|
|
24
24
|
self.libraries_dir = libraries_dir
|
|
25
25
|
self.started = []
|
|
26
26
|
self.deploy_debug_notes = deploy_debug_notes
|
|
@@ -86,6 +86,7 @@ class ExternalMessages(pykka.ThreadingActor):
|
|
|
86
86
|
if isinstance(msg, MqttSubscription):
|
|
87
87
|
if self.mqtt_client is not None:
|
|
88
88
|
logging.debug(f"Subscribing to {msg.topic}: {msg.subscriber}")
|
|
89
|
+
# FIXME: how best to handle a user accidentally providing a list typed topic?
|
|
89
90
|
self.mqtt_client.subscribe(msg.topic)
|
|
90
91
|
self.subscribers[msg.topic].add(msg.subscriber)
|
|
91
92
|
self.my_note.append(f"- \\[{ctime()}] subscribed to `{msg.topic}`: {msg.subscriber}\n")
|
|
@@ -93,8 +94,12 @@ class ExternalMessages(pykka.ThreadingActor):
|
|
|
93
94
|
logging.warning(f"Ignoring subscription to {msg.topic}, no mqtt config")
|
|
94
95
|
elif isinstance(msg, MqttPublish):
|
|
95
96
|
if self.mqtt_client is not None:
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
try:
|
|
98
|
+
self.mqtt_client.publish(msg.topic, msg.payload)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logging.exception(f"Failed to publish {msg.topic} ({type(msg.topic)}): {e}")
|
|
101
|
+
else:
|
|
102
|
+
logging.debug(f"published {len(msg.payload)} bytes to `{msg.topic}`")
|
|
98
103
|
else:
|
|
99
104
|
logging.warning(f"Ignoring publish to {msg.topic}, no mqtt config")
|
|
100
105
|
else:
|
|
@@ -246,8 +246,6 @@ def _raw_note_to_frontmatter_and_markdown(raw_note_lines) -> Tuple[str | None, s
|
|
|
246
246
|
frontmatter_lines.append(line)
|
|
247
247
|
|
|
248
248
|
frontmatter = ''.join(frontmatter_lines)
|
|
249
|
-
else:
|
|
250
|
-
raise Exception("no end to frontmatter")
|
|
251
249
|
|
|
252
250
|
markdown = ''.join(raw_note_lines) # remaining
|
|
253
251
|
return frontmatter, markdown
|
|
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING # FIXME: remove
|
|
|
12
12
|
from pykka import ActorRef
|
|
13
13
|
|
|
14
14
|
from ..util import Clock, TimeUtil
|
|
15
|
-
from ..
|
|
15
|
+
from ..nervous_note_router import NervousNoteSubscription, PublishToNervousNote, BatchPublishToNervousNote
|
|
16
16
|
from ..nervous_note import MqttWrapper
|
|
17
17
|
from ..wrappers.note_api import NoteAPI, datetimestamped_markdown_list_line, timestamped_markdown_list_line
|
|
18
18
|
|
|
@@ -34,7 +34,7 @@ class ScriptPath:
|
|
|
34
34
|
class ScriptHarness:
|
|
35
35
|
my_note: NoteAPI
|
|
36
36
|
|
|
37
|
-
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper,
|
|
37
|
+
def __init__(self, actor_ref: ActorRef, my_note: NoteAPI, topic: str, mqtt: MqttWrapper, nervous_note_router: ActorRef, script_path: ScriptPath):
|
|
38
38
|
self.my_note = my_note
|
|
39
39
|
self.script_path = script_path
|
|
40
40
|
self.actor_ref = actor_ref
|
|
@@ -64,8 +64,9 @@ class ScriptHarness:
|
|
|
64
64
|
"cancel_timer": self.cancel_timer,
|
|
65
65
|
"datetimestamped_markdown_list_line": datetimestamped_markdown_list_line,
|
|
66
66
|
"timestamped_markdown_list_line": timestamped_markdown_list_line,
|
|
67
|
-
"
|
|
68
|
-
"
|
|
67
|
+
# "snoop_on_nervous_note": lambda nid, s: nervous_note_router.tell(NervousNoteSubscription(nid, s)),
|
|
68
|
+
"publish_to_nervous_note": lambda nid, p: nervous_note_router.tell(PublishToNervousNote(nid, p)),
|
|
69
|
+
"batch_publish_to_nervous_note": lambda nid, batch: nervous_note_router.tell(BatchPublishToNervousNote.of(nid, batch)),
|
|
69
70
|
"publish_to_ntfy": publish_to_ntfy,
|
|
70
71
|
"next_occurrence": timeutil.next_occurrence,
|
|
71
72
|
"seconds_until": timeutil.seconds_until,
|
|
@@ -78,6 +79,7 @@ class ScriptHarness:
|
|
|
78
79
|
"on_start": lambda: None,
|
|
79
80
|
"on_timer": lambda key, payload: None,
|
|
80
81
|
"on_stop": lambda: None,
|
|
82
|
+
"on_other_message": lambda: None,
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
self.prior_script = None
|
|
@@ -120,8 +122,12 @@ class ScriptHarness:
|
|
|
120
122
|
else:
|
|
121
123
|
logging.info(f"{self.script_path} compilation complete, executing to update the scope now...")
|
|
122
124
|
# this may update the scope with potentially new on* event functions
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
try:
|
|
126
|
+
exec(self.compiled_script, self.script_scope)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logging.exception(f"Failed to compile {self.script_path} ({e})")
|
|
129
|
+
else:
|
|
130
|
+
self.prior_script = script
|
|
125
131
|
|
|
126
132
|
def on_start(self) -> None:
|
|
127
133
|
try:
|
|
@@ -151,6 +157,12 @@ class ScriptHarness:
|
|
|
151
157
|
def on_stop(self) -> None:
|
|
152
158
|
logging.info(f"stopping ScriptHarness")
|
|
153
159
|
|
|
160
|
+
def on_other_message(self, message) -> None:
|
|
161
|
+
try:
|
|
162
|
+
self.script_scope["on_other_message"](message)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logging.exception(f"on_other_message call failed {e}")
|
|
165
|
+
|
|
154
166
|
|
|
155
167
|
# FIXME: move to "services" or something, with weather, PurpleAir, etc
|
|
156
168
|
def publish_to_ntfy(channel: str, message: str) -> None:
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Set, Dict
|
|
5
|
-
|
|
6
|
-
from pykka import ActorRef, ThreadingActor
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class Subscribe:
|
|
11
|
-
topic: str
|
|
12
|
-
subscriber: ActorRef
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class Publish:
|
|
17
|
-
topic: str
|
|
18
|
-
payload: object
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class VaultRouter(ThreadingActor):
|
|
22
|
-
def __init__(self):
|
|
23
|
-
super().__init__()
|
|
24
|
-
self.subscribers: Dict[str, Set[ActorRef]] = defaultdict(set)
|
|
25
|
-
|
|
26
|
-
def on_message(self, message):
|
|
27
|
-
if isinstance(message, Subscribe):
|
|
28
|
-
self.subscribers[message.topic].add(message.subscriber)
|
|
29
|
-
elif isinstance(message, Publish):
|
|
30
|
-
for subscriber in self.subscribers[message.topic]:
|
|
31
|
-
subscriber.tell(message.payload)
|
|
32
|
-
else:
|
|
33
|
-
logging.warning(f"Unknown message type: {type(message)}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Extrovert.py
RENAMED
|
File without changes
|
{nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Example - Introvert.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Mqtt Publisher.py
RENAMED
|
File without changes
|
{nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Mqtt Subscriber.py
RENAMED
|
File without changes
|
{nervous_notes-0.0.2 → nervous_notes-0.0.3}/src/nervous_notes/example_scripts/Next Notes Manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|