stark-engine 4.3.1__tar.gz → 4.4.1__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.
- {stark_engine-4.3.1 → stark_engine-4.4.1}/PKG-INFO +3 -2
- {stark_engine-4.3.1 → stark_engine-4.4.1}/pyproject.toml +4 -2
- stark_engine-4.4.1/stark/__init__.py +79 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/command.py +16 -23
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/commands_context.py +16 -3
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/commands_context_processor.py +6 -2
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/commands_manager.py +8 -3
- stark_engine-4.4.1/stark/core/health_check.py +74 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/parsing.py +198 -26
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/patterns/pattern.py +0 -1
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/processors/__init__.py +1 -0
- stark_engine-4.4.1/stark/core/processors/corrections_processor.py +81 -0
- stark_engine-4.4.1/stark/core/processors/search_processor.py +176 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/processors/spacy_ner_processor.py +16 -5
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/object.py +7 -2
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/slots.py +2 -1
- stark_engine-4.4.1/stark/general/feature_flags.py +17 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/general/json_encoder.py +1 -1
- stark_engine-4.4.1/stark/general/localisation/__init__.py +6 -0
- stark_engine-4.4.1/stark/general/localisation/language_code.py +13 -0
- stark_engine-4.4.1/stark/general/localisation/locale_string.py +188 -0
- stark_engine-4.4.1/stark/general/localisation/localizable_string.py +40 -0
- stark_engine-4.4.1/stark/general/localisation/localizer.py +154 -0
- stark_engine-4.4.1/stark/general/localisation/strings.py +47 -0
- stark_engine-4.4.1/stark/interfaces/microphone.py +61 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/interfaces/protocols.py +5 -2
- stark_engine-4.4.1/stark/interfaces/recognizer_relay.py +207 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/interfaces/vosk.py +99 -57
- stark_engine-4.4.1/stark/models/__init__.py +0 -0
- stark_engine-4.4.1/stark/models/transcription_string.py +201 -0
- stark_engine-4.4.1/stark/models/voice_transcription.py +291 -0
- stark_engine-4.4.1/stark/models/voice_transcription_string.py +133 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/__init__.py +1 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/dictionary.py +9 -8
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/models.py +4 -3
- stark_engine-4.4.1/stark/tools/dictionary/recognizable_dictionary.py +28 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/storage/storage_memory.py +4 -2
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/storage/storage_sqlite.py +4 -2
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/phonetic/transcription/__init__.py +7 -3
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/phonetic/transcription/epitran.py +5 -3
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/phonetic/transcription/espeak.py +3 -1
- stark_engine-4.4.1/stark/tools/phonetic/transcription/latin_passthrough.py +25 -0
- stark_engine-4.4.1/stark/tools/phonetic/transcription/protocol.py +7 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/voice_assistant/voice_assistant.py +21 -10
- stark_engine-4.3.1/stark/__init__.py +0 -50
- stark_engine-4.3.1/stark/core/health_check.py +0 -51
- stark_engine-4.3.1/stark/core/processors/search_processor.py +0 -82
- stark_engine-4.3.1/stark/tools/phonetic/transcription/protocol.py +0 -5
- {stark_engine-4.3.1 → stark_engine-4.4.1}/LICENSE.md +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/README.md +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/patterns/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/patterns/rules.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/location.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/string.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/core/types/word.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/general/blockage_detector.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/general/cache.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/general/classproperty.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/general/dependencies.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/interfaces/gcloud.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/interfaces/silero.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/common/span.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/!examples.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/nl_dictionary_name.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/dictionary/storage/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/levenshtein/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/levenshtein/levenshtein.pyi +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/levenshtein/levenshtein.pyx +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/phonetic/simplephone.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/phonetic/transcription/ipa2lat.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/sliding_window_parser.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/tools/strtools.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/voice_assistant/__init__.py +0 -0
- {stark_engine-4.3.1 → stark_engine-4.4.1}/stark/voice_assistant/mode.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stark-engine
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.1
|
|
4
4
|
Summary: S.T.A.R.K - Speech and Text Algorithmic Recognition Kit. Modern framework for creating powerfull voice assistants.
|
|
5
5
|
License: CC BY-NC-SA 4.0
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -19,7 +19,8 @@ Provides-Extra: silero
|
|
|
19
19
|
Provides-Extra: sound
|
|
20
20
|
Provides-Extra: spacy
|
|
21
21
|
Provides-Extra: vosk
|
|
22
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: anyio (>=4.0.0,<5.0.0)
|
|
23
|
+
Requires-Dist: asyncer (>=0.0.8,<1.0.0)
|
|
23
24
|
Requires-Dist: google-cloud-texttospeech (>=2.14.1,<3.0.0) ; extra == "gcloud" or extra == "all"
|
|
24
25
|
Requires-Dist: numpy (>=2.3.3,<3.0.0) ; extra == "silero" or extra == "all"
|
|
25
26
|
Requires-Dist: pydantic (>=2.0.0,<3.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "stark-engine"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.4.1"
|
|
4
4
|
description = "S.T.A.R.K - Speech and Text Algorithmic Recognition Kit. Modern framework for creating powerfull voice assistants."
|
|
5
5
|
authors = ["MarkParker5 <mark@parker-programs.com>"]
|
|
6
6
|
license = "CC BY-NC-SA 4.0"
|
|
@@ -39,7 +39,8 @@ keywords = [
|
|
|
39
39
|
[tool.poetry.dependencies]
|
|
40
40
|
python = ">=3.12,<3.15"
|
|
41
41
|
pydantic = "^2.0.0"
|
|
42
|
-
asyncer = "
|
|
42
|
+
asyncer = ">=0.0.8,<1.0.0"
|
|
43
|
+
anyio = ">=4.0.0,<5.0.0"
|
|
43
44
|
numpy = "^2.3.3"
|
|
44
45
|
|
|
45
46
|
# Optional dependencies
|
|
@@ -85,6 +86,7 @@ coverage = "^7.11.0"
|
|
|
85
86
|
faker = "^37.11.0"
|
|
86
87
|
pympler = "^1.1"
|
|
87
88
|
psutil = "^7.1.1"
|
|
89
|
+
trio = "^0.33.0"
|
|
88
90
|
|
|
89
91
|
[build-system]
|
|
90
92
|
requires = ["poetry-core"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncer
|
|
2
|
+
|
|
3
|
+
from stark.core import (
|
|
4
|
+
Command,
|
|
5
|
+
CommandsContext,
|
|
6
|
+
CommandsManager,
|
|
7
|
+
Pattern,
|
|
8
|
+
Response,
|
|
9
|
+
ResponseStatus,
|
|
10
|
+
)
|
|
11
|
+
from stark.core.commands_context_processor import CommandsContextProcessor
|
|
12
|
+
from stark.core.health_check import health_check
|
|
13
|
+
from stark.core.processors.search_processor import SearchProcessor
|
|
14
|
+
from stark.general.blockage_detector import BlockageDetector
|
|
15
|
+
from stark.general.localisation import Localizer
|
|
16
|
+
from stark.interfaces.protocols import (
|
|
17
|
+
SpeechRecognizer,
|
|
18
|
+
SpeechRecognizerDelegate,
|
|
19
|
+
SpeechSynthesizer,
|
|
20
|
+
SpeechSynthesizerResult,
|
|
21
|
+
)
|
|
22
|
+
from stark.voice_assistant import Mode, VoiceAssistant
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def run(
|
|
26
|
+
manager: CommandsManager,
|
|
27
|
+
speech_recognizer: SpeechRecognizer | list[SpeechRecognizer],
|
|
28
|
+
speech_synthesizer: SpeechSynthesizer,
|
|
29
|
+
processors: list[CommandsContextProcessor] | None = None,
|
|
30
|
+
localizer: Localizer | None = None,
|
|
31
|
+
):
|
|
32
|
+
if processors is None:
|
|
33
|
+
if localizer:
|
|
34
|
+
from stark.core.processors.corrections_processor import CorrectionsProcessor
|
|
35
|
+
processors = [CorrectionsProcessor(), SearchProcessor()]
|
|
36
|
+
else:
|
|
37
|
+
processors = [SearchProcessor()]
|
|
38
|
+
|
|
39
|
+
async with asyncer.create_task_group() as main_task_group:
|
|
40
|
+
context = CommandsContext(
|
|
41
|
+
task_group=main_task_group,
|
|
42
|
+
commands_manager=manager,
|
|
43
|
+
processors=processors,
|
|
44
|
+
localizer=localizer,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from stark.interfaces.microphone import Microphone
|
|
48
|
+
|
|
49
|
+
recognizers = speech_recognizer if isinstance(speech_recognizer, list) else [speech_recognizer]
|
|
50
|
+
use_relay = len(recognizers) > 1
|
|
51
|
+
|
|
52
|
+
if use_relay:
|
|
53
|
+
from stark.interfaces.recognizer_relay import SpeechRecognizerRelay
|
|
54
|
+
effective_recognizer = SpeechRecognizerRelay(recognizers)
|
|
55
|
+
else:
|
|
56
|
+
effective_recognizer = recognizers[0]
|
|
57
|
+
|
|
58
|
+
voice_assistant = VoiceAssistant(
|
|
59
|
+
speech_recognizer=effective_recognizer,
|
|
60
|
+
speech_synthesizer=speech_synthesizer,
|
|
61
|
+
commands_context=context,
|
|
62
|
+
)
|
|
63
|
+
effective_recognizer.delegate = voice_assistant
|
|
64
|
+
context.delegate = voice_assistant
|
|
65
|
+
|
|
66
|
+
health_check(context.pattern_parser, manager.commands)
|
|
67
|
+
|
|
68
|
+
if use_relay:
|
|
69
|
+
effective_recognizer.start_speech_recognizers(main_task_group)
|
|
70
|
+
else:
|
|
71
|
+
main_task_group.soonify(effective_recognizer.start_listening)()
|
|
72
|
+
|
|
73
|
+
microphone = Microphone(effective_recognizer.microphone_did_receive_sample)
|
|
74
|
+
main_task_group.soonify(microphone.start_listening)()
|
|
75
|
+
|
|
76
|
+
main_task_group.soonify(context.handle_responses)()
|
|
77
|
+
|
|
78
|
+
detector = BlockageDetector()
|
|
79
|
+
main_task_group.soonify(detector.monitor)()
|
|
@@ -29,12 +29,11 @@ import asyncer
|
|
|
29
29
|
from pydantic import BaseModel, Field
|
|
30
30
|
|
|
31
31
|
from ..general.classproperty import classproperty
|
|
32
|
+
from ..general.localisation import LanguageCode, LocalizableString
|
|
32
33
|
from .patterns import Pattern
|
|
33
34
|
|
|
34
35
|
ResponseOptions = (
|
|
35
|
-
Optional["Response"]
|
|
36
|
-
| Generator[Optional["Response"], None, None]
|
|
37
|
-
| AsyncGenerator[Optional["Response"], None]
|
|
36
|
+
Optional["Response"] | Generator[Optional["Response"], None, None] | AsyncGenerator[Optional["Response"], None]
|
|
38
37
|
)
|
|
39
38
|
AwaitResponse = Awaitable[ResponseOptions]
|
|
40
39
|
AsyncCommandRunner = Callable[..., AwaitResponse]
|
|
@@ -44,16 +43,21 @@ CommandRunner = TypeVar("CommandRunner", bound=SyncCommandRunner | AsyncCommandR
|
|
|
44
43
|
|
|
45
44
|
class Command(Generic[CommandRunner]):
|
|
46
45
|
name: str
|
|
47
|
-
|
|
46
|
+
patterns: dict[LanguageCode, Pattern]
|
|
48
47
|
_runner: CommandRunner
|
|
49
48
|
|
|
50
|
-
def __init__(self, name: str,
|
|
51
|
-
assert isinstance(
|
|
49
|
+
def __init__(self, name: str, patterns: dict[LanguageCode, Pattern], runner: CommandRunner):
|
|
50
|
+
assert patterns and all(isinstance(p, Pattern) for p in patterns.values())
|
|
52
51
|
self.name = name
|
|
53
|
-
self.
|
|
52
|
+
self.patterns = patterns
|
|
54
53
|
self._runner = runner
|
|
55
54
|
update_wrapper(self, runner)
|
|
56
55
|
|
|
56
|
+
def get_pattern(self, language_code: LanguageCode) -> Pattern:
|
|
57
|
+
if language_code in self.patterns:
|
|
58
|
+
return self.patterns[language_code]
|
|
59
|
+
return self.patterns["base"]
|
|
60
|
+
|
|
57
61
|
def run(
|
|
58
62
|
self,
|
|
59
63
|
parameters_dict: dict[str, Any] | None = None,
|
|
@@ -74,9 +78,7 @@ class Command(Generic[CommandRunner]):
|
|
|
74
78
|
|
|
75
79
|
runner: AsyncCommandRunner
|
|
76
80
|
|
|
77
|
-
if inspect.iscoroutinefunction(self._runner) or inspect.isasyncgen(
|
|
78
|
-
self._runner
|
|
79
|
-
):
|
|
81
|
+
if inspect.iscoroutinefunction(self._runner) or inspect.isasyncgen(self._runner):
|
|
80
82
|
# async functions (coroutines) and async generators are remain as is
|
|
81
83
|
runner = cast(AsyncCommandRunner, self._runner)
|
|
82
84
|
else:
|
|
@@ -84,21 +86,12 @@ class Command(Generic[CommandRunner]):
|
|
|
84
86
|
# async generators are not supported yet by asyncer.asyncify (https://github.com/tiangolo/asyncer/discussions/86)
|
|
85
87
|
runner = asyncer.asyncify(cast(SyncCommandRunner, self._runner))
|
|
86
88
|
|
|
87
|
-
if any(
|
|
88
|
-
p.kind == p.VAR_KEYWORD
|
|
89
|
-
for p in inspect.signature(self._runner).parameters.values()
|
|
90
|
-
):
|
|
89
|
+
if any(p.kind == p.VAR_KEYWORD for p in inspect.signature(self._runner).parameters.values()):
|
|
91
90
|
# if command runner accepts **kwargs, pass all parameters
|
|
92
91
|
coroutine = runner(**parameters)
|
|
93
92
|
else:
|
|
94
93
|
# otherwise pass only parameters that are in command runner signature to prevent TypeError: got an unexpected keyword argument
|
|
95
|
-
coroutine = runner(
|
|
96
|
-
**{
|
|
97
|
-
k: v
|
|
98
|
-
for k, v in parameters.items()
|
|
99
|
-
if k in self._runner.__code__.co_varnames
|
|
100
|
-
}
|
|
101
|
-
)
|
|
94
|
+
coroutine = runner(**{k: v for k, v in parameters.items() if k in self._runner.__code__.co_varnames})
|
|
102
95
|
|
|
103
96
|
@wraps(runner)
|
|
104
97
|
async def coroutine_wrapper() -> ResponseOptions:
|
|
@@ -139,8 +132,8 @@ class ResponseStatus(Enum):
|
|
|
139
132
|
|
|
140
133
|
|
|
141
134
|
class Response(BaseModel):
|
|
142
|
-
voice: str = ""
|
|
143
|
-
text: str = ""
|
|
135
|
+
voice: str | LocalizableString = ""
|
|
136
|
+
text: str | LocalizableString = ""
|
|
144
137
|
status: ResponseStatus = ResponseStatus.success
|
|
145
138
|
needs_user_input: bool = False
|
|
146
139
|
commands: list[Command] = []
|
|
@@ -15,8 +15,10 @@ from stark.core.commands_context_processor import (
|
|
|
15
15
|
)
|
|
16
16
|
from stark.core.parsing import PatternParser
|
|
17
17
|
from stark.core.types.object import Object
|
|
18
|
+
from stark.general.localisation import LocaleString, Localizer
|
|
19
|
+
from stark.general.localisation.language_code import LanguageCode
|
|
18
20
|
|
|
19
|
-
from ..general.dependencies import DependencyManager, default_dependency_manager
|
|
21
|
+
from ..general.dependencies import Dependency, DependencyManager, default_dependency_manager
|
|
20
22
|
from .command import (
|
|
21
23
|
AsyncResponseHandler,
|
|
22
24
|
Command,
|
|
@@ -57,11 +59,12 @@ class CommandsContext:
|
|
|
57
59
|
commands_manager: CommandsManager,
|
|
58
60
|
dependency_manager: DependencyManager = default_dependency_manager,
|
|
59
61
|
processors: list[Any] | None = None,
|
|
62
|
+
localizer: Localizer | None = None,
|
|
60
63
|
):
|
|
61
64
|
assert isinstance(task_group, TaskGroup), task_group
|
|
62
65
|
assert isinstance(commands_manager, CommandsManager)
|
|
63
66
|
assert isinstance(dependency_manager, DependencyManager)
|
|
64
|
-
self.pattern_parser = PatternParser()
|
|
67
|
+
self.pattern_parser = PatternParser(localizer=localizer)
|
|
65
68
|
self.commands_manager = commands_manager
|
|
66
69
|
self.context_queue = [self.root_context]
|
|
67
70
|
self._response_queue = []
|
|
@@ -88,7 +91,9 @@ class CommandsContext:
|
|
|
88
91
|
def root_context(self):
|
|
89
92
|
return CommandsContextLayer(self.commands_manager.commands, {})
|
|
90
93
|
|
|
91
|
-
async def process_string(self, string: str):
|
|
94
|
+
async def process_string(self, string: str | LocaleString):
|
|
95
|
+
string = LocaleString(string) if not isinstance(string, LocaleString) else string
|
|
96
|
+
|
|
92
97
|
if not self.context_queue:
|
|
93
98
|
self.context_queue.append(self.root_context)
|
|
94
99
|
|
|
@@ -117,7 +122,15 @@ class CommandsContext:
|
|
|
117
122
|
parameters: dict[str, Object] = {}
|
|
118
123
|
parameters.update(current_context.parameters)
|
|
119
124
|
parameters.update(search_result.match_result.parameters)
|
|
125
|
+
|
|
126
|
+
substring = search_result.match_result.substring
|
|
127
|
+
lang = substring.language_code if isinstance(substring, LocaleString) else string.language_code
|
|
128
|
+
lang_dep = Dependency(None, LanguageCode, lang)
|
|
129
|
+
self.dependency_manager.dependencies.add(lang_dep)
|
|
120
130
|
parameters.update(self.dependency_manager.resolve(search_result.command._runner))
|
|
131
|
+
# language is a command-specific temporary dependency
|
|
132
|
+
self.dependency_manager.dependencies.discard(lang_dep)
|
|
133
|
+
|
|
121
134
|
self.run_command(search_result.command, parameters)
|
|
122
135
|
|
|
123
136
|
return search_results or []
|
|
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
|
|
8
8
|
from stark.core.command import Command
|
|
9
9
|
from stark.core.parsing import RecognizedEntity
|
|
10
10
|
from stark.core.types.object import Object
|
|
11
|
+
from stark.general.localisation import LocaleString
|
|
11
12
|
|
|
12
13
|
from .commands_manager import SearchResult
|
|
13
14
|
|
|
@@ -42,7 +43,10 @@ class CommandsContextProcessor(ABC):
|
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
45
|
async def process_string(
|
|
45
|
-
self,
|
|
46
|
+
self,
|
|
47
|
+
string: LocaleString,
|
|
48
|
+
context: CommandsContext,
|
|
49
|
+
recognized_entities: list[RecognizedEntity],
|
|
46
50
|
) -> tuple[list[SearchResult], int]:
|
|
47
51
|
"""
|
|
48
52
|
Processes the entire context queue.
|
|
@@ -72,7 +76,7 @@ class CommandsContextProcessor(ABC):
|
|
|
72
76
|
|
|
73
77
|
async def process_context_layer(
|
|
74
78
|
self,
|
|
75
|
-
string:
|
|
79
|
+
string: LocaleString,
|
|
76
80
|
context: CommandsContext,
|
|
77
81
|
context_layer: CommandsContextLayer,
|
|
78
82
|
recognized_entities: list[RecognizedEntity],
|
|
@@ -31,9 +31,14 @@ class CommandsManager:
|
|
|
31
31
|
return command
|
|
32
32
|
return None
|
|
33
33
|
|
|
34
|
-
def new(self, pattern_str: str, hidden: bool = False):
|
|
34
|
+
def new(self, pattern_str: str | dict[str, str], hidden: bool = False):
|
|
35
35
|
def creator(runner: CommandRunner) -> Command:
|
|
36
|
-
|
|
36
|
+
if isinstance(pattern_str, dict):
|
|
37
|
+
patterns = {lang: Pattern(p) for lang, p in pattern_str.items()}
|
|
38
|
+
if 'base' not in patterns:
|
|
39
|
+
patterns['base'] = next(iter(patterns.values()))
|
|
40
|
+
else:
|
|
41
|
+
patterns = {'base': Pattern(pattern_str)}
|
|
37
42
|
|
|
38
43
|
# take the main type from Optionals
|
|
39
44
|
annotations = dict()
|
|
@@ -60,7 +65,7 @@ class CommandsManager:
|
|
|
60
65
|
|
|
61
66
|
# create command
|
|
62
67
|
|
|
63
|
-
cmd = Command(f"{self.name}.{runner.__name__}",
|
|
68
|
+
cmd = Command(f"{self.name}.{runner.__name__}", patterns, runner)
|
|
64
69
|
|
|
65
70
|
if not hidden:
|
|
66
71
|
self.commands.append(cmd)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from stark.core.command import Command
|
|
4
|
+
from stark.core.parsing import _LOCALIZER_KEY_REGEX, PatternParser
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def health_check(pattern_parser: PatternParser, commands: list[Command]) -> None:
|
|
8
|
+
# TODO: update schema and validation; handle optional parameters; handle short form where type is defined in object
|
|
9
|
+
|
|
10
|
+
# 1. Registered types: pattern parameters and annotations match; all used types are registered
|
|
11
|
+
for reg_type in pattern_parser.parameter_types_by_name.values():
|
|
12
|
+
for lang, type_pattern in reg_type.type.patterns.items():
|
|
13
|
+
pattern_params = set(type_pattern.parameters.keys())
|
|
14
|
+
class_params = set(reg_type.type.__annotations__.keys())
|
|
15
|
+
assert pattern_params <= class_params, (
|
|
16
|
+
f"Pattern parameters {pattern_params} do not match class annotations {class_params} for {reg_type.type.__name__} (language '{lang}')"
|
|
17
|
+
)
|
|
18
|
+
# 2. Registered types: All referenced parameter types are registered
|
|
19
|
+
for param_name in pattern_params:
|
|
20
|
+
assert type_pattern.parameters[param_name].type_name in pattern_parser.parameter_types_by_name, (
|
|
21
|
+
f"Unknown parameter type '{param_name}' in pattern '{type_pattern}' of type '{reg_type.type.__name__}' (language '{lang}'). Known types: {sorted(pattern_parser.parameter_types_by_name.keys())}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 3. No duplicate parameter types registered
|
|
25
|
+
type_names = list(pattern_parser.parameter_types_by_name.keys())
|
|
26
|
+
if len(type_names) != len(set(type_names)):
|
|
27
|
+
duplicates = {name for name in type_names if type_names.count(name) > 1}
|
|
28
|
+
assert not duplicates, f"Duplicate parameter type names found: {sorted(duplicates)}"
|
|
29
|
+
|
|
30
|
+
# 4. Commands: validate @key references and parameter types
|
|
31
|
+
for command in commands:
|
|
32
|
+
for lang, cmd_pattern in command.patterns.items():
|
|
33
|
+
if pattern_parser.localizer and pattern_parser._has_localizer_keys(cmd_pattern):
|
|
34
|
+
for key in _LOCALIZER_KEY_REGEX.findall(cmd_pattern._origin):
|
|
35
|
+
pattern_parser.localizer.verify_recognizable(key)
|
|
36
|
+
for param in command.get_pattern("base").parameters.values():
|
|
37
|
+
assert param.type_name in pattern_parser.parameter_types_by_name, (
|
|
38
|
+
f"Unknown parameter type '{param.type_name}' in pattern '{command.get_pattern('base')}' of command '{command.name}'. Known types: {sorted(pattern_parser.parameter_types_by_name.keys())}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# 5. Commands: all pattern params are expected by the runner func
|
|
42
|
+
for command in commands:
|
|
43
|
+
pattern_params = set(command.get_pattern("base").parameters.keys())
|
|
44
|
+
runner_params = set(command._runner.__annotations__.keys())
|
|
45
|
+
assert pattern_params <= runner_params, (
|
|
46
|
+
f"Command '{command.name}' function missing parameters: {pattern_params - runner_params}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# 6. Pattern syntax validity (TODO)
|
|
50
|
+
# for command in commands:
|
|
51
|
+
# assert command.get_pattern("base").is_valid(), f"Invalid pattern syntax: {command.get_pattern("base").origin}"
|
|
52
|
+
|
|
53
|
+
# 7. Try compiling all patterns to catch syntax errors early
|
|
54
|
+
for reg_type in pattern_parser.parameter_types_by_name.values():
|
|
55
|
+
for lang, p in reg_type.type.patterns.items():
|
|
56
|
+
try:
|
|
57
|
+
pattern_parser._compile_pattern(p, language_code=lang)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
warnings.warn(
|
|
60
|
+
f"Failed to compile pattern '{p}' of type '{reg_type.type.__name__}' (language '{lang}'): {e}"
|
|
61
|
+
)
|
|
62
|
+
for command in commands:
|
|
63
|
+
for lang, p in command.patterns.items():
|
|
64
|
+
try:
|
|
65
|
+
pattern_parser._compile_pattern(p, language_code=lang)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
warnings.warn(f"Failed to compile pattern '{p}' of command '{command.name}' (language '{lang}'): {e}")
|
|
68
|
+
|
|
69
|
+
# 8. No unused types
|
|
70
|
+
used_types = {param.type_name for command in commands for param in command.get_pattern("base").parameters.values()}
|
|
71
|
+
for type_name in pattern_parser.parameter_types_by_name:
|
|
72
|
+
# assert type_name in used_types, f"Registered type {type_name} is not used in any pattern"
|
|
73
|
+
if type_name not in used_types:
|
|
74
|
+
warnings.warn(f"Registered type {type_name} is not used in any pattern")
|