stark-engine 4.5.2__tar.gz → 4.5.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.
Files changed (73) hide show
  1. {stark_engine-4.5.2 → stark_engine-4.5.3}/PKG-INFO +1 -1
  2. {stark_engine-4.5.2 → stark_engine-4.5.3}/pyproject.toml +1 -1
  3. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/parsing.py +50 -21
  4. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/__init__.py +1 -0
  5. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/object.py +30 -2
  6. stark_engine-4.5.3/stark/core/types/union.py +65 -0
  7. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/sliding_window_parser.py +1 -1
  8. {stark_engine-4.5.2 → stark_engine-4.5.3}/LICENSE.md +0 -0
  9. {stark_engine-4.5.2 → stark_engine-4.5.3}/README.md +0 -0
  10. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/__init__.py +0 -0
  11. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/__init__.py +0 -0
  12. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/command.py +0 -0
  13. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/commands_context.py +0 -0
  14. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/commands_context_processor.py +0 -0
  15. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/commands_manager.py +0 -0
  16. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/health_check.py +0 -0
  17. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/patterns/__init__.py +0 -0
  18. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/patterns/pattern.py +0 -0
  19. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/patterns/rules.py +0 -0
  20. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/processors/__init__.py +0 -0
  21. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/processors/corrections_processor.py +0 -0
  22. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/processors/search_processor.py +0 -0
  23. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/processors/spacy_ner_processor.py +0 -0
  24. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/location.py +0 -0
  25. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/slots.py +0 -0
  26. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/string.py +0 -0
  27. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/core/types/word.py +0 -0
  28. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/blockage_detector.py +0 -0
  29. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/cache.py +0 -0
  30. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/classproperty.py +0 -0
  31. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/dependencies.py +0 -0
  32. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/feature_flags.py +0 -0
  33. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/json_encoder.py +0 -0
  34. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/__init__.py +0 -0
  35. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/language_code.py +0 -0
  36. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/locale_string.py +0 -0
  37. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/localizable_string.py +0 -0
  38. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/localizer.py +0 -0
  39. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/general/localisation/strings.py +0 -0
  40. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/gcloud.py +0 -0
  41. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/microphone.py +0 -0
  42. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/protocols.py +0 -0
  43. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/recognizer_relay.py +0 -0
  44. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/silero.py +0 -0
  45. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/interfaces/vosk.py +0 -0
  46. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/models/__init__.py +0 -0
  47. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/models/transcription_string.py +0 -0
  48. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/models/voice_transcription.py +0 -0
  49. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/models/voice_transcription_string.py +0 -0
  50. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/common/span.py +0 -0
  51. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/!examples.py +0 -0
  52. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/__init__.py +0 -0
  53. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/dictionary.py +0 -0
  54. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/models.py +0 -0
  55. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/nl_dictionary_name.py +0 -0
  56. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/recognizable_dictionary.py +0 -0
  57. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/storage/__init__.py +0 -0
  58. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/storage/storage_memory.py +0 -0
  59. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/dictionary/storage/storage_sqlite.py +0 -0
  60. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/levenshtein/__init__.py +0 -0
  61. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/levenshtein/levenshtein.pyi +0 -0
  62. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/levenshtein/levenshtein.pyx +0 -0
  63. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/simplephone.py +0 -0
  64. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/__init__.py +0 -0
  65. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/epitran.py +0 -0
  66. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/espeak.py +0 -0
  67. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/ipa2lat.py +0 -0
  68. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/latin_passthrough.py +0 -0
  69. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/phonetic/transcription/protocol.py +0 -0
  70. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/tools/strtools.py +0 -0
  71. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/voice_assistant/__init__.py +0 -0
  72. {stark_engine-4.5.2 → stark_engine-4.5.3}/stark/voice_assistant/mode.py +0 -0
  73. {stark_engine-4.5.2 → stark_engine-4.5.3}/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.3
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.3"
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,15 @@ 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
-
16
15
  from stark.general.localisation import LanguageCode, LocaleString, Localizer
17
16
  from stark.models.transcription_string import Correction
18
17
  from stark.tools.common.span import Span
@@ -22,6 +21,7 @@ class CorrectionMatch(NamedTuple):
22
21
  span: Span
23
22
  correction: Correction
24
23
 
24
+
25
25
  type ObjectType = type[Object]
26
26
 
27
27
 
@@ -104,6 +104,7 @@ class PatternParser:
104
104
 
105
105
  def __init__(self, localizer: Localizer | None = None):
106
106
  self.parameter_types_by_name = {}
107
+ self._registering: set[str] = set() # cycle guard for recursive register_parameter_type
107
108
  self.localizer = localizer
108
109
  self.register_parameter_type(Word)
109
110
  self.register_parameter_type(String)
@@ -114,19 +115,38 @@ class PatternParser:
114
115
  parser: ObjectParser | None = None,
115
116
  # TODO: consider adding lang parameter
116
117
  ):
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)
118
+ name = object_type.__name__
119
+ # Idempotent: skip if already registered or currently being registered (cycle guard).
120
+ if name in self.parameter_types_by_name or name in self._registering:
121
+ return
122
+ self._registering.add(name)
123
+ try:
124
+ assert issubclass(object_type, Object), f'Can`t add parameter type "{name}": it is not a subclass of Object'
125
+ # Resolve deps. Union types declare members explicitly in _types;
126
+ # everything else declares deps implicitly via parameter annotations in the pattern.
127
+ if issubclass(object_type, Union) and hasattr(object_type, "_types"):
128
+ deps = object_type._types
129
+ else:
130
+ # Evaluate the pattern first — it may create new classes (e.g. any_subclass calls).
131
+ # Build known AFTER so those new classes are visible by name.
132
+ pattern = object_type.pattern
133
+ known = {cls.__name__: cls for cls in _all_subclasses(Object)}
134
+ deps = [known[p.type_name] for p in pattern.parameters.values() if p.type_name in known]
135
+ # Register each dep before the type itself (depth-first, handles transitive deps).
136
+ for dep in deps:
137
+ self.register_parameter_type(dep)
138
+ exists_type = self.parameter_types_by_name.get(name)
139
+ assert exists_type is None, (
140
+ f"Duplicate parameter type: can`t add parameter type '{name}' because it already exists"
141
+ )
142
+ resolved_parser = parser or ObjectParser()
143
+ resolved_parser.localizer = self.localizer
144
+ self.parameter_types_by_name[name] = RegisteredParameterType(
145
+ name=name, type=object_type, parser=resolved_parser
146
+ )
147
+ self._validate_localizer_keys(object_type)
148
+ finally:
149
+ self._registering.discard(name)
130
150
 
131
151
  def _validate_localizer_keys(self, object_type: ObjectType):
132
152
  for pattern in object_type.patterns.values():
@@ -184,15 +204,17 @@ class PatternParser:
184
204
  try:
185
205
  substring = await self._did_parse(obj, parser, string)
186
206
  except ParseError:
187
- logger.debug(f"Failed to parse object {object_type} with parser {parser} from '{string}'")
207
+ logger.debug(f"Failed to parse object {object_type!r} with parser {parser!r} from '{string}'")
188
208
  continue # skip an try the next match candidate
189
209
 
190
- assert substring.strip(), ValueError(f"Parsed substring must not be empty. Object: {obj}, Parser: {parser}")
210
+ assert substring.strip(), ValueError(
211
+ f"Parsed substring must not be empty. Object: {obj!r}, Parser: {parser!r}"
212
+ )
191
213
  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}"
214
+ f"Parsed substring must be a part of the original string. There is no '{substring}' in '{string}'. Object: {obj!r}, Parser: {parser!r}"
193
215
  )
194
216
  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}"
217
+ f"Parsed object {obj!r} must have a `value` property set in did_parse method. Object: {obj!r}, Parser: {parser!r}"
196
218
  )
197
219
 
198
220
  yield ParseResult(obj, substring)
@@ -441,10 +463,17 @@ class PatternParser:
441
463
 
442
464
  # parse the object
443
465
  try:
466
+ from stark.core.types.union import Union
467
+
444
468
  parse_result = await self.parse_object(parameter_reg_type.type, from_string=raw_param_substr)
469
+ parsed_obj = parse_result.obj
470
+ if isinstance(parsed_obj, Union) and getattr(type(parsed_obj), "_transparent", False):
471
+ # Transparent Unions (MakeUnion / | / any_subclass) are unwrapped to the branch.
472
+ # Named Union subclasses (class Foo(Union)) are kept as-is — caller uses .value.
473
+ parsed_obj = parsed_obj.value
445
474
  return ParameterMatch( # take the first match (the only in most cases)
446
475
  name=param.name,
447
- parsed_obj=parse_result.obj,
476
+ parsed_obj=parsed_obj,
448
477
  parsed_substr=parse_result.substring,
449
478
  )
450
479
  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,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
- from abc import ABC
4
+ from abc import ABCMeta
5
5
  from typing import Any
6
6
 
7
7
  from stark.core.patterns.pattern import Pattern
@@ -9,9 +9,37 @@ from stark.general.classproperty import classproperty
9
9
  from stark.general.localisation import LocaleString
10
10
 
11
11
 
12
+ class UnionMeta(ABCMeta):
13
+ """Metaclass that makes | on Object subclasses produce a STARK Union subclass instead of types.UnionType."""
14
+
15
+ def __call__(cls, *args, **kwargs):
16
+ from stark.core.types.union import Union
17
+
18
+ if cls is Union:
19
+ raise TypeError("Union cannot be instantiated directly; subclass it or use | syntax or MakeUnion")
20
+ return super().__call__(*args, **kwargs)
21
+
22
+ def __or__(cls, other: type) -> type:
23
+ from stark.core.types.union import MakeUnion
24
+
25
+ if isinstance(other, type) and issubclass(other, Object):
26
+ return MakeUnion(cls, other)
27
+ return type.__or__(cls, other) # fall through to types.UnionType for X | None etc.
28
+
29
+ def __ror__(cls, other: type) -> type:
30
+ from stark.core.types.union import MakeUnion
31
+
32
+ if isinstance(other, type) and issubclass(other, Object):
33
+ return MakeUnion(other, cls)
34
+ return type.__ror__(cls, other)
35
+
36
+ def __format__(cls, spec) -> str:
37
+ return cls.__name__
38
+
39
+
12
40
  # TODO: review programmable init vs did_parse
13
41
  # TODO: consider storing parsing metadata here like substr and span
14
- class Object[T](ABC):
42
+ class Object[T](metaclass=UnionMeta):
15
43
  value: T
16
44
 
17
45
  @classproperty
@@ -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")
@@ -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