stark-engine 4.5.2__tar.gz → 4.5.4__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.
Files changed (73) hide show
  1. {stark_engine-4.5.2 → stark_engine-4.5.4}/PKG-INFO +1 -1
  2. {stark_engine-4.5.2 → stark_engine-4.5.4}/pyproject.toml +1 -1
  3. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/parsing.py +55 -24
  4. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/__init__.py +1 -0
  5. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/object.py +40 -7
  6. stark_engine-4.5.4/stark/core/types/union.py +65 -0
  7. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/feature_flags.py +2 -0
  8. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/sliding_window_parser.py +1 -1
  9. {stark_engine-4.5.2 → stark_engine-4.5.4}/LICENSE.md +0 -0
  10. {stark_engine-4.5.2 → stark_engine-4.5.4}/README.md +0 -0
  11. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/__init__.py +0 -0
  12. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/__init__.py +0 -0
  13. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/command.py +0 -0
  14. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/commands_context.py +0 -0
  15. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/commands_context_processor.py +0 -0
  16. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/commands_manager.py +0 -0
  17. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/health_check.py +0 -0
  18. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/patterns/__init__.py +0 -0
  19. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/patterns/pattern.py +0 -0
  20. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/patterns/rules.py +0 -0
  21. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/processors/__init__.py +0 -0
  22. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/processors/corrections_processor.py +0 -0
  23. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/processors/search_processor.py +0 -0
  24. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/processors/spacy_ner_processor.py +0 -0
  25. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/location.py +0 -0
  26. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/slots.py +0 -0
  27. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/string.py +0 -0
  28. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/core/types/word.py +0 -0
  29. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/blockage_detector.py +0 -0
  30. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/cache.py +0 -0
  31. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/classproperty.py +0 -0
  32. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/dependencies.py +0 -0
  33. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/json_encoder.py +0 -0
  34. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/__init__.py +0 -0
  35. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/language_code.py +0 -0
  36. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/locale_string.py +0 -0
  37. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/localizable_string.py +0 -0
  38. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/localizer.py +0 -0
  39. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/general/localisation/strings.py +0 -0
  40. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/gcloud.py +0 -0
  41. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/microphone.py +0 -0
  42. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/protocols.py +0 -0
  43. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/recognizer_relay.py +0 -0
  44. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/silero.py +0 -0
  45. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/interfaces/vosk.py +0 -0
  46. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/models/__init__.py +0 -0
  47. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/models/transcription_string.py +0 -0
  48. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/models/voice_transcription.py +0 -0
  49. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/models/voice_transcription_string.py +0 -0
  50. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/common/span.py +0 -0
  51. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/!examples.py +0 -0
  52. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/__init__.py +0 -0
  53. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/dictionary.py +0 -0
  54. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/models.py +0 -0
  55. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/nl_dictionary_name.py +0 -0
  56. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/recognizable_dictionary.py +0 -0
  57. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/storage/__init__.py +0 -0
  58. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/storage/storage_memory.py +0 -0
  59. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/dictionary/storage/storage_sqlite.py +0 -0
  60. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/levenshtein/__init__.py +0 -0
  61. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/levenshtein/levenshtein.pyi +0 -0
  62. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/levenshtein/levenshtein.pyx +0 -0
  63. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/simplephone.py +0 -0
  64. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/__init__.py +0 -0
  65. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/epitran.py +0 -0
  66. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/espeak.py +0 -0
  67. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/ipa2lat.py +0 -0
  68. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/latin_passthrough.py +0 -0
  69. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/phonetic/transcription/protocol.py +0 -0
  70. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/tools/strtools.py +0 -0
  71. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/voice_assistant/__init__.py +0 -0
  72. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/voice_assistant/mode.py +0 -0
  73. {stark_engine-4.5.2 → stark_engine-4.5.4}/stark/voice_assistant/voice_assistant.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stark-engine
3
- Version: 4.5.2
3
+ Version: 4.5.4
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "stark-engine"
3
- version = "4.5.2"
3
+ version = "4.5.4"
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"
@@ -3,16 +3,16 @@ from __future__ import annotations
3
3
  import re
4
4
  from abc import ABC
5
5
  from dataclasses import dataclass, field
6
- from typing import AsyncGenerator
6
+ from typing import AsyncGenerator, NamedTuple
7
7
 
8
8
  from stark.core.patterns.pattern import Pattern
9
9
  from stark.core.patterns.rules import rules_list
10
10
  from stark.core.types import Object
11
11
  from stark.core.types.string import String
12
+ from stark.core.types.union import Union, _all_subclasses
12
13
  from stark.core.types.word import Word
13
14
  from stark.general.cache import alru_cache
14
- from typing import NamedTuple
15
-
15
+ from stark.general.feature_flags import FeatureFlag, get_flag
16
16
  from stark.general.localisation import LanguageCode, LocaleString, Localizer
17
17
  from stark.models.transcription_string import Correction
18
18
  from stark.tools.common.span import Span
@@ -22,6 +22,7 @@ class CorrectionMatch(NamedTuple):
22
22
  span: Span
23
23
  correction: Correction
24
24
 
25
+
25
26
  type ObjectType = type[Object]
26
27
 
27
28
 
@@ -104,6 +105,7 @@ class PatternParser:
104
105
 
105
106
  def __init__(self, localizer: Localizer | None = None):
106
107
  self.parameter_types_by_name = {}
108
+ self._registering: set[str] = set() # cycle guard for recursive register_parameter_type
107
109
  self.localizer = localizer
108
110
  self.register_parameter_type(Word)
109
111
  self.register_parameter_type(String)
@@ -114,19 +116,38 @@ class PatternParser:
114
116
  parser: ObjectParser | None = None,
115
117
  # TODO: consider adding lang parameter
116
118
  ):
117
- assert issubclass(object_type, Object), (
118
- f'Can`t add parameter type "{object_type.__name__}": it is not a subclass of Object'
119
- )
120
- exists_type = self.parameter_types_by_name.get(object_type.__name__)
121
- assert exists_type is None, (
122
- f"Duplicate parameter type: can`t add parameter type '{object_type.__name__}' because it already exists"
123
- )
124
- resolved_parser = parser or ObjectParser()
125
- resolved_parser.localizer = self.localizer
126
- self.parameter_types_by_name[object_type.__name__] = RegisteredParameterType(
127
- name=object_type.__name__, type=object_type, parser=resolved_parser
128
- )
129
- self._validate_localizer_keys(object_type)
119
+ name = object_type.__name__
120
+ # Idempotent: skip if already registered or currently being registered (cycle guard).
121
+ if name in self.parameter_types_by_name or name in self._registering:
122
+ return
123
+ self._registering.add(name)
124
+ try:
125
+ assert issubclass(object_type, Object), f'Can`t add parameter type "{name}": it is not a subclass of Object'
126
+ # Resolve deps. Union types declare members explicitly in _types;
127
+ # everything else declares deps implicitly via parameter annotations in the pattern.
128
+ if issubclass(object_type, Union) and hasattr(object_type, "_types"):
129
+ deps = object_type._types
130
+ else:
131
+ # Evaluate the pattern first — it may create new classes (e.g. any_subclass calls).
132
+ # Build known AFTER so those new classes are visible by name.
133
+ pattern = object_type.pattern
134
+ known = {cls.__name__: cls for cls in _all_subclasses(Object)}
135
+ deps = [known[p.type_name] for p in pattern.parameters.values() if p.type_name in known]
136
+ # Register each dep before the type itself (depth-first, handles transitive deps).
137
+ for dep in deps:
138
+ self.register_parameter_type(dep)
139
+ exists_type = self.parameter_types_by_name.get(name)
140
+ assert exists_type is None, (
141
+ f"Duplicate parameter type: can`t add parameter type '{name}' because it already exists"
142
+ )
143
+ resolved_parser = parser or ObjectParser()
144
+ resolved_parser.localizer = self.localizer
145
+ self.parameter_types_by_name[name] = RegisteredParameterType(
146
+ name=name, type=object_type, parser=resolved_parser
147
+ )
148
+ self._validate_localizer_keys(object_type)
149
+ finally:
150
+ self._registering.discard(name)
130
151
 
131
152
  def _validate_localizer_keys(self, object_type: ObjectType):
132
153
  for pattern in object_type.patterns.values():
@@ -172,7 +193,7 @@ class PatternParser:
172
193
  string = from_string._with(object_pattern_match.substring) # TODO: review _with usage
173
194
 
174
195
  parser: ObjectParser = self.parameter_types_by_name[object_type.__name__].parser
175
- obj = object_type(string) # temp/default value, may be overridden by did_parse
196
+ obj = object_type() # object_type(string) # temp/default value, may be overridden by did_parse
176
197
 
177
198
  matched_subparams = object_pattern_match.parameters
178
199
  assert matched_subparams.keys() <= {p.name for p in pattern.parameters.values() if not p.optional}
@@ -184,16 +205,19 @@ class PatternParser:
184
205
  try:
185
206
  substring = await self._did_parse(obj, parser, string)
186
207
  except ParseError:
187
- logger.debug(f"Failed to parse object {object_type} with parser {parser} from '{string}'")
208
+ logger.debug(f"Failed to parse object {object_type!r} with parser {parser!r} from '{string}'")
188
209
  continue # skip an try the next match candidate
189
210
 
190
- assert substring.strip(), ValueError(f"Parsed substring must not be empty. Object: {obj}, Parser: {parser}")
191
- assert substring in string, ValueError(
192
- f"Parsed substring must be a part of the original string. There is no {substring} in {string}. Object: {obj}, Parser: {parser}"
211
+ assert substring.strip(), ValueError(
212
+ f"Parsed substring must not be empty. Object: {obj!r}, Parser: {parser!r}"
193
213
  )
194
- assert obj.value is not None, ValueError(
195
- f"Parsed object {obj} must have a `value` property set in did_parse method. Object: {obj}, Parser: {parser}"
214
+ assert substring in string, ValueError(
215
+ f"Parsed substring must be a part of the original string. There is no '{substring}' in '{string}'. Object: {obj!r}, Parser: {parser!r}"
196
216
  )
217
+ if not get_flag(FeatureFlag.TYPE_NO_REQUIRED_VALUE):
218
+ assert obj.value is not None, ValueError(
219
+ f"Parsed object {obj!r} must have a `value` property set at the time did_parse method returns. Object: {obj!r}, Parser: {parser!r}"
220
+ )
197
221
 
198
222
  yield ParseResult(obj, substring)
199
223
 
@@ -441,10 +465,17 @@ class PatternParser:
441
465
 
442
466
  # parse the object
443
467
  try:
468
+ from stark.core.types.union import Union
469
+
444
470
  parse_result = await self.parse_object(parameter_reg_type.type, from_string=raw_param_substr)
471
+ parsed_obj = parse_result.obj
472
+ if isinstance(parsed_obj, Union) and getattr(type(parsed_obj), "_transparent", False):
473
+ # Transparent Unions (MakeUnion / | / any_subclass) are unwrapped to the branch.
474
+ # Named Union subclasses (class Foo(Union)) are kept as-is — caller uses .value.
475
+ parsed_obj = parsed_obj.value
445
476
  return ParameterMatch( # take the first match (the only in most cases)
446
477
  name=param.name,
447
- parsed_obj=parse_result.obj,
478
+ parsed_obj=parsed_obj,
448
479
  parsed_substr=parse_result.substring,
449
480
  )
450
481
  except ParseError as e:
@@ -1,4 +1,5 @@
1
1
  from .object import Object
2
+ from .union import MakeUnion, Union, any_subclass
2
3
  from .slots import SlotsParser
3
4
  from .string import String
4
5
  from .word import Word
@@ -1,18 +1,45 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
- from abc import ABC
5
- from typing import Any
4
+ from abc import ABCMeta
6
5
 
7
6
  from stark.core.patterns.pattern import Pattern
8
7
  from stark.general.classproperty import classproperty
9
8
  from stark.general.localisation import LocaleString
10
9
 
11
10
 
11
+ class UnionMeta(ABCMeta):
12
+ """Metaclass that makes | on Object subclasses produce a STARK Union subclass instead of types.UnionType."""
13
+
14
+ def __call__(cls, *args, **kwargs):
15
+ from stark.core.types.union import Union
16
+
17
+ if cls is Union:
18
+ raise TypeError("Union cannot be instantiated directly; subclass it or use | syntax or MakeUnion")
19
+ return super().__call__(*args, **kwargs)
20
+
21
+ def __or__(cls, other: type) -> type:
22
+ from stark.core.types.union import MakeUnion
23
+
24
+ if isinstance(other, type) and issubclass(other, Object):
25
+ return MakeUnion(cls, other)
26
+ return type.__or__(cls, other) # fall through to types.UnionType for X | None etc.
27
+
28
+ def __ror__(cls, other: type) -> type:
29
+ from stark.core.types.union import MakeUnion
30
+
31
+ if isinstance(other, type) and issubclass(other, Object):
32
+ return MakeUnion(other, cls)
33
+ return type.__ror__(cls, other)
34
+
35
+ def __format__(cls, spec) -> str:
36
+ return cls.__name__
37
+
38
+
12
39
  # TODO: review programmable init vs did_parse
13
40
  # TODO: consider storing parsing metadata here like substr and span
14
- class Object[T](ABC):
15
- value: T
41
+ class Object[T](metaclass=UnionMeta):
42
+ value: T = None
16
43
 
17
44
  @classproperty
18
45
  def pattern(cls) -> Pattern:
@@ -32,9 +59,13 @@ class Object[T](ABC):
32
59
  """
33
60
  return False # TODO: review default behavior
34
61
 
35
- def __init__(self, value: Any):
36
- """Just init with wrapped value."""
37
- self.value = value
62
+ def __init__(self, *args, **kwargs):
63
+ """Init with wrapped value, if provided. Otherwise leave `value` untouched so a static
64
+ value set at class declaration time (e.g. `value = "foo"`) survives instantiation."""
65
+ if "value" in kwargs:
66
+ self.value = kwargs["value"]
67
+ elif args:
68
+ self.value = args[0]
38
69
 
39
70
  async def did_parse(self, from_string: LocaleString) -> str:
40
71
  """
@@ -51,6 +82,8 @@ class Object[T](ABC):
51
82
  Raises:
52
83
  ParseError: if parsing failed.
53
84
  """
85
+ if not self.value:
86
+ self.value = from_string
54
87
  return from_string
55
88
 
56
89
  def copy(self) -> Object:
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from stark.core.patterns.pattern import Pattern
4
+ from stark.core.types.object import Object, UnionMeta
5
+ from stark.general.classproperty import classproperty
6
+
7
+
8
+ def MakeUnion(*types: type[Object]) -> type:
9
+ cls = UnionMeta("_".join(t.__name__ for t in types), (Union,), {"_types": list(types)})
10
+ cls._transparent = True # PatternParser unwraps to branch when used as a typed parameter
11
+ return cls
12
+
13
+
14
+ def any_subclass(cls: type[Object]) -> type[Union]:
15
+ """Returns a Union of all current concrete subclasses of cls.
16
+
17
+ Result is cached on the base class so the same class object is returned on every call —
18
+ required for stable type registration (the parser keys on class name and object identity).
19
+ Discovered at first call time, so subclasses defined before the pattern is first accessed
20
+ (i.e., at parse time) are included automatically.
21
+ """
22
+ cache_attr = f"_any_subclass_{cls.__name__}"
23
+ if not hasattr(cls, cache_attr):
24
+ u = MakeUnion(*_all_subclasses(cls))
25
+ u.__name__ = f"Any{cls.__name__}" # no | — Pattern syntax treats | as OR
26
+ setattr(cls, cache_attr, u)
27
+ return getattr(cls, cache_attr)
28
+
29
+
30
+ def _all_subclasses(cls):
31
+ for sub in cls.__subclasses__():
32
+ yield sub
33
+ yield from _all_subclasses(sub)
34
+
35
+
36
+ class Union(Object):
37
+ """Base for union types. Use as superclass or combine with | / MakeUnion.
38
+
39
+ self.value — the matched branch Object instance
40
+ self.value.value — the inner parsed value
41
+ type(self.value) — the matched branch type
42
+
43
+ After PatternParser unwraps Union parameters, callers receive the concrete branch
44
+ directly — isinstance, type(), and attribute access all work against the real class.
45
+ """
46
+
47
+ _types: list[type[Object]]
48
+ value: Object
49
+
50
+ def __init_subclass__(cls, **kwargs):
51
+ super().__init_subclass__(**kwargs)
52
+ if "pattern" in cls.__dict__ or "patterns" in cls.__dict__:
53
+ raise TypeError(f"{cls.__name__}: Union subclasses may not override 'pattern'")
54
+
55
+ @classproperty
56
+ def pattern(cls) -> Pattern:
57
+ return Pattern("(" + "|".join(f"$m{n}:{t.__name__}" for n, t in enumerate(cls._types, 1)) + ")")
58
+
59
+ async def did_parse(self, from_string) -> str:
60
+ from stark.core.parsing import ParseError
61
+ for k, v in self.__dict__.items():
62
+ if k.startswith("m") and v is not None:
63
+ self.value = v # branch Object, not v.value
64
+ return from_string
65
+ raise ParseError("Union: no branch matched")
@@ -5,11 +5,13 @@ from enum import StrEnum
5
5
  class FeatureFlag(StrEnum):
6
6
  ENABLE_VOICE_CLI = "STARK_ENABLE_VOICE_CLI"
7
7
  ENABLE_MULTILANG_MATRIX = "STARK_ENABLE_MULTILANG_MATRIX"
8
+ TYPE_NO_REQUIRED_VALUE = "STARK_TYPE_NO_REQUIRED_VALUE"
8
9
 
9
10
 
10
11
  _DEFAULTS: dict[FeatureFlag, str] = {
11
12
  FeatureFlag.ENABLE_VOICE_CLI: "0",
12
13
  FeatureFlag.ENABLE_MULTILANG_MATRIX: "1",
14
+ FeatureFlag.TYPE_NO_REQUIRED_VALUE: "0",
13
15
  }
14
16
 
15
17
 
@@ -131,4 +131,4 @@ async def sliding_window_parse[T](
131
131
  return results
132
132
 
133
133
  # If no valid window is found, raise an error.
134
- raise ParseError(f"No valid window found using parser={parser} in phrase={phrase}")
134
+ raise ParseError(f"No valid window found using parser={parser!r} in phrase='{phrase}'")
File without changes
File without changes