ommlds 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev480__py3-none-any.whl

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 (271) hide show
  1. ommlds/.omlish-manifests.json +332 -35
  2. ommlds/__about__.py +15 -9
  3. ommlds/_hacks/__init__.py +4 -0
  4. ommlds/_hacks/funcs.py +110 -0
  5. ommlds/_hacks/names.py +158 -0
  6. ommlds/_hacks/params.py +73 -0
  7. ommlds/_hacks/patches.py +0 -3
  8. ommlds/backends/anthropic/protocol/_marshal.py +2 -2
  9. ommlds/backends/anthropic/protocol/sse/_marshal.py +1 -1
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +23 -7
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/anthropic/protocol/types.py +30 -9
  13. ommlds/backends/google/protocol/__init__.py +3 -0
  14. ommlds/backends/google/protocol/_marshal.py +16 -0
  15. ommlds/backends/google/protocol/types.py +626 -0
  16. ommlds/backends/groq/_marshal.py +23 -0
  17. ommlds/backends/groq/protocol.py +249 -0
  18. ommlds/backends/mlx/generation.py +1 -1
  19. ommlds/backends/mlx/loading.py +58 -1
  20. ommlds/backends/ollama/__init__.py +0 -0
  21. ommlds/backends/ollama/protocol.py +170 -0
  22. ommlds/backends/openai/protocol/__init__.py +9 -28
  23. ommlds/backends/openai/protocol/_common.py +18 -0
  24. ommlds/backends/openai/protocol/_marshal.py +27 -0
  25. ommlds/backends/openai/protocol/chatcompletion/chunk.py +58 -31
  26. ommlds/backends/openai/protocol/chatcompletion/contentpart.py +49 -44
  27. ommlds/backends/openai/protocol/chatcompletion/message.py +55 -43
  28. ommlds/backends/openai/protocol/chatcompletion/request.py +114 -66
  29. ommlds/backends/openai/protocol/chatcompletion/response.py +71 -45
  30. ommlds/backends/openai/protocol/chatcompletion/responseformat.py +27 -20
  31. ommlds/backends/openai/protocol/chatcompletion/tokenlogprob.py +16 -7
  32. ommlds/backends/openai/protocol/completionusage.py +24 -15
  33. ommlds/backends/tavily/__init__.py +0 -0
  34. ommlds/backends/tavily/protocol.py +301 -0
  35. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  36. ommlds/backends/transformers/__init__.py +0 -0
  37. ommlds/backends/transformers/filecache.py +109 -0
  38. ommlds/backends/transformers/streamers.py +73 -0
  39. ommlds/cli/asyncs.py +30 -0
  40. ommlds/cli/backends/catalog.py +93 -0
  41. ommlds/cli/backends/configs.py +9 -0
  42. ommlds/cli/backends/inject.py +31 -36
  43. ommlds/cli/backends/injection.py +16 -0
  44. ommlds/cli/backends/types.py +46 -0
  45. ommlds/cli/content/__init__.py +0 -0
  46. ommlds/cli/content/messages.py +34 -0
  47. ommlds/cli/content/strings.py +42 -0
  48. ommlds/cli/inject.py +15 -32
  49. ommlds/cli/inputs/__init__.py +0 -0
  50. ommlds/cli/inputs/asyncs.py +32 -0
  51. ommlds/cli/inputs/sync.py +75 -0
  52. ommlds/cli/main.py +270 -110
  53. ommlds/cli/rendering/__init__.py +0 -0
  54. ommlds/cli/rendering/configs.py +9 -0
  55. ommlds/cli/rendering/inject.py +31 -0
  56. ommlds/cli/rendering/markdown.py +52 -0
  57. ommlds/cli/rendering/raw.py +73 -0
  58. ommlds/cli/rendering/types.py +21 -0
  59. ommlds/cli/secrets.py +21 -0
  60. ommlds/cli/sessions/base.py +1 -1
  61. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  63. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  64. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  65. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  66. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  67. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  68. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  69. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  70. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  71. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  72. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  73. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  74. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  75. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  76. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  77. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  78. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  79. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  80. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  81. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  82. ommlds/cli/sessions/chat/configs.py +27 -0
  83. ommlds/cli/sessions/chat/driver.py +43 -0
  84. ommlds/cli/sessions/chat/inject.py +33 -65
  85. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  86. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  87. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  88. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  89. ommlds/cli/sessions/chat/phases/types.py +29 -0
  90. ommlds/cli/sessions/chat/session.py +27 -0
  91. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  92. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  93. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  94. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  95. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  96. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  97. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  98. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  99. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  100. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  101. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  102. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  103. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  104. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  105. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  106. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  107. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  108. ommlds/cli/sessions/completion/configs.py +21 -0
  109. ommlds/cli/sessions/completion/inject.py +42 -0
  110. ommlds/cli/sessions/completion/session.py +35 -0
  111. ommlds/cli/sessions/embedding/configs.py +21 -0
  112. ommlds/cli/sessions/embedding/inject.py +42 -0
  113. ommlds/cli/sessions/embedding/session.py +33 -0
  114. ommlds/cli/sessions/inject.py +28 -11
  115. ommlds/cli/state/__init__.py +0 -0
  116. ommlds/cli/state/inject.py +28 -0
  117. ommlds/cli/{state.py → state/storage.py} +41 -24
  118. ommlds/minichain/__init__.py +84 -24
  119. ommlds/minichain/_marshal.py +49 -9
  120. ommlds/minichain/_typedvalues.py +2 -4
  121. ommlds/minichain/backends/catalogs/base.py +20 -1
  122. ommlds/minichain/backends/catalogs/simple.py +2 -2
  123. ommlds/minichain/backends/catalogs/strings.py +10 -8
  124. ommlds/minichain/backends/impls/anthropic/chat.py +65 -27
  125. ommlds/minichain/backends/impls/anthropic/names.py +10 -8
  126. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  127. ommlds/minichain/backends/impls/anthropic/stream.py +111 -43
  128. ommlds/minichain/backends/impls/duckduckgo/search.py +1 -1
  129. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  130. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  131. ommlds/minichain/backends/impls/google/chat.py +114 -22
  132. ommlds/minichain/backends/impls/google/search.py +7 -2
  133. ommlds/minichain/backends/impls/google/stream.py +219 -0
  134. ommlds/minichain/backends/impls/google/tools.py +149 -0
  135. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  136. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  137. ommlds/minichain/backends/impls/groq/names.py +48 -0
  138. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  139. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  140. ommlds/minichain/backends/impls/llamacpp/chat.py +33 -18
  141. ommlds/minichain/backends/impls/llamacpp/completion.py +1 -1
  142. ommlds/minichain/backends/impls/llamacpp/format.py +4 -2
  143. ommlds/minichain/backends/impls/llamacpp/stream.py +37 -20
  144. ommlds/minichain/backends/impls/mistral.py +20 -5
  145. ommlds/minichain/backends/impls/mlx/chat.py +96 -22
  146. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  147. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  148. ommlds/minichain/backends/impls/openai/chat.py +18 -8
  149. ommlds/minichain/backends/impls/openai/completion.py +10 -3
  150. ommlds/minichain/backends/impls/openai/embedding.py +10 -3
  151. ommlds/minichain/backends/impls/openai/format.py +131 -106
  152. ommlds/minichain/backends/impls/openai/names.py +31 -5
  153. ommlds/minichain/backends/impls/openai/stream.py +43 -25
  154. ommlds/minichain/backends/impls/tavily.py +66 -0
  155. ommlds/minichain/backends/impls/tinygrad/chat.py +23 -16
  156. ommlds/minichain/backends/impls/transformers/sentence.py +1 -1
  157. ommlds/minichain/backends/impls/transformers/tokens.py +1 -1
  158. ommlds/minichain/backends/impls/transformers/transformers.py +155 -34
  159. ommlds/minichain/backends/strings/parsing.py +1 -1
  160. ommlds/minichain/backends/strings/resolving.py +4 -1
  161. ommlds/minichain/chat/_marshal.py +16 -9
  162. ommlds/minichain/chat/choices/adapters.py +4 -4
  163. ommlds/minichain/chat/choices/services.py +1 -1
  164. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  165. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  166. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  167. ommlds/minichain/chat/choices/stream/services.py +45 -0
  168. ommlds/minichain/chat/choices/stream/types.py +43 -0
  169. ommlds/minichain/chat/choices/types.py +2 -2
  170. ommlds/minichain/chat/history.py +3 -3
  171. ommlds/minichain/chat/messages.py +55 -19
  172. ommlds/minichain/chat/services.py +3 -3
  173. ommlds/minichain/chat/stream/_marshal.py +16 -0
  174. ommlds/minichain/chat/stream/joining.py +85 -0
  175. ommlds/minichain/chat/stream/services.py +15 -21
  176. ommlds/minichain/chat/stream/types.py +32 -19
  177. ommlds/minichain/chat/tools/execution.py +8 -7
  178. ommlds/minichain/chat/tools/ids.py +9 -15
  179. ommlds/minichain/chat/tools/parsing.py +17 -26
  180. ommlds/minichain/chat/transforms/base.py +29 -38
  181. ommlds/minichain/chat/transforms/metadata.py +30 -4
  182. ommlds/minichain/chat/transforms/services.py +9 -11
  183. ommlds/minichain/content/_marshal.py +44 -20
  184. ommlds/minichain/content/json.py +13 -0
  185. ommlds/minichain/content/materialize.py +14 -21
  186. ommlds/minichain/content/prepare.py +4 -0
  187. ommlds/minichain/content/transforms/interleave.py +1 -1
  188. ommlds/minichain/content/transforms/squeeze.py +1 -1
  189. ommlds/minichain/content/transforms/stringify.py +1 -1
  190. ommlds/minichain/json.py +20 -0
  191. ommlds/minichain/lib/code/__init__.py +0 -0
  192. ommlds/minichain/lib/code/prompts.py +6 -0
  193. ommlds/minichain/lib/fs/binfiles.py +108 -0
  194. ommlds/minichain/lib/fs/context.py +126 -0
  195. ommlds/minichain/lib/fs/errors.py +101 -0
  196. ommlds/minichain/lib/fs/suggestions.py +36 -0
  197. ommlds/minichain/lib/fs/tools/__init__.py +0 -0
  198. ommlds/minichain/lib/fs/tools/edit.py +104 -0
  199. ommlds/minichain/lib/fs/tools/ls.py +38 -0
  200. ommlds/minichain/lib/fs/tools/read.py +115 -0
  201. ommlds/minichain/lib/fs/tools/recursivels/__init__.py +0 -0
  202. ommlds/minichain/lib/fs/tools/recursivels/execution.py +40 -0
  203. ommlds/minichain/lib/todo/__init__.py +0 -0
  204. ommlds/minichain/lib/todo/context.py +54 -0
  205. ommlds/minichain/lib/todo/tools/__init__.py +0 -0
  206. ommlds/minichain/lib/todo/tools/read.py +44 -0
  207. ommlds/minichain/lib/todo/tools/write.py +335 -0
  208. ommlds/minichain/lib/todo/types.py +60 -0
  209. ommlds/minichain/llms/_marshal.py +25 -17
  210. ommlds/minichain/llms/types.py +4 -0
  211. ommlds/minichain/registries/globals.py +18 -4
  212. ommlds/minichain/resources.py +66 -43
  213. ommlds/minichain/search.py +1 -1
  214. ommlds/minichain/services/_marshal.py +46 -39
  215. ommlds/minichain/services/facades.py +3 -3
  216. ommlds/minichain/services/services.py +1 -1
  217. ommlds/minichain/standard.py +8 -0
  218. ommlds/minichain/stream/services.py +152 -38
  219. ommlds/minichain/stream/wrap.py +22 -24
  220. ommlds/minichain/tools/_marshal.py +1 -1
  221. ommlds/minichain/tools/execution/catalog.py +2 -1
  222. ommlds/minichain/tools/execution/context.py +34 -14
  223. ommlds/minichain/tools/execution/errors.py +15 -0
  224. ommlds/minichain/tools/execution/executors.py +8 -3
  225. ommlds/minichain/tools/execution/reflect.py +40 -5
  226. ommlds/minichain/tools/fns.py +46 -9
  227. ommlds/minichain/tools/jsonschema.py +14 -5
  228. ommlds/minichain/tools/reflect.py +54 -18
  229. ommlds/minichain/tools/types.py +33 -1
  230. ommlds/minichain/utils.py +27 -0
  231. ommlds/minichain/vectors/_marshal.py +11 -10
  232. ommlds/nanochat/LICENSE +21 -0
  233. ommlds/nanochat/__init__.py +0 -0
  234. ommlds/nanochat/rustbpe/LICENSE +21 -0
  235. ommlds/nanochat/tokenizers.py +406 -0
  236. ommlds/server/server.py +3 -3
  237. ommlds/specs/__init__.py +0 -0
  238. ommlds/specs/mcp/__init__.py +0 -0
  239. ommlds/specs/mcp/_marshal.py +23 -0
  240. ommlds/specs/mcp/protocol.py +266 -0
  241. ommlds/tools/git.py +27 -10
  242. ommlds/tools/ocr.py +8 -9
  243. ommlds/wiki/analyze.py +2 -2
  244. ommlds/wiki/text/mfh.py +1 -5
  245. ommlds/wiki/text/wtp.py +1 -3
  246. ommlds/wiki/utils/xml.py +5 -5
  247. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/METADATA +24 -21
  248. ommlds-0.0.0.dev480.dist-info/RECORD +427 -0
  249. ommlds/cli/backends/standard.py +0 -20
  250. ommlds/cli/sessions/chat/base.py +0 -42
  251. ommlds/cli/sessions/chat/interactive.py +0 -73
  252. ommlds/cli/sessions/chat/printing.py +0 -96
  253. ommlds/cli/sessions/chat/prompt.py +0 -143
  254. ommlds/cli/sessions/chat/state.py +0 -109
  255. ommlds/cli/sessions/chat/tools.py +0 -91
  256. ommlds/cli/sessions/completion/completion.py +0 -44
  257. ommlds/cli/sessions/embedding/embedding.py +0 -42
  258. ommlds/cli/tools/config.py +0 -13
  259. ommlds/cli/tools/inject.py +0 -64
  260. ommlds/minichain/chat/stream/adapters.py +0 -69
  261. ommlds/minichain/lib/fs/ls/execution.py +0 -32
  262. ommlds-0.0.0.dev440.dist-info/RECORD +0 -303
  263. /ommlds/{cli/tools → backends/google}/__init__.py +0 -0
  264. /ommlds/{minichain/lib/fs/ls → backends/groq}/__init__.py +0 -0
  265. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  266. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/rendering.py +0 -0
  267. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/running.py +0 -0
  268. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/WHEEL +0 -0
  269. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/entry_points.txt +0 -0
  270. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/licenses/LICENSE +0 -0
  271. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,6 @@ TODO:
3
3
  - unify with omlish.sql.api.resources -> omlish.resources
4
4
  """
5
5
  import contextlib
6
- import threading
7
6
  import typing as ta
8
7
 
9
8
  from omlish import check
@@ -32,6 +31,7 @@ class ResourcesRefNotRegisteredError(Exception):
32
31
  pass
33
32
 
34
33
 
34
+ @ta.final
35
35
  class Resources(lang.Final, lang.NotPicklable):
36
36
  def __init__(
37
37
  self,
@@ -43,26 +43,25 @@ class Resources(lang.Final, lang.NotPicklable):
43
43
 
44
44
  self._no_autoclose = no_autoclose
45
45
 
46
- self._lock = threading.RLock()
47
-
48
46
  self._closed = False
49
47
 
50
48
  self._refs: ta.MutableSet[ResourcesRef] = col.IdentitySet()
51
49
 
52
- self._es = contextlib.ExitStack()
53
- self._es.__enter__()
50
+ self._aes = contextlib.AsyncExitStack()
54
51
 
55
52
  if init_ref is not None:
56
53
  self.add_ref(init_ref)
57
54
 
55
+ async def init(self) -> None:
56
+ await self._aes.__aenter__()
57
+
58
58
  @property
59
59
  def autoclose(self) -> bool:
60
60
  return not self._no_autoclose
61
61
 
62
62
  @property
63
63
  def num_refs(self) -> int:
64
- with self._lock:
65
- return len(self._refs)
64
+ return len(self._refs)
66
65
 
67
66
  @property
68
67
  def closed(self) -> bool:
@@ -77,15 +76,16 @@ class Resources(lang.Final, lang.NotPicklable):
77
76
  pass
78
77
 
79
78
  @classmethod
80
- def new(cls, **kwargs: ta.Any) -> ta.ContextManager['Resources']:
81
- @contextlib.contextmanager
82
- def inner():
79
+ def new(cls, **kwargs: ta.Any) -> ta.AsyncContextManager['Resources']:
80
+ @contextlib.asynccontextmanager
81
+ async def inner():
83
82
  init_ref = Resources._InitRef()
84
83
  res = Resources(init_ref=init_ref, **kwargs)
84
+ await res.init()
85
85
  try:
86
86
  yield res
87
87
  finally:
88
- res.remove_ref(init_ref)
88
+ await res.remove_ref(init_ref)
89
89
 
90
90
  return inner()
91
91
 
@@ -93,30 +93,30 @@ class Resources(lang.Final, lang.NotPicklable):
93
93
 
94
94
  def add_ref(self, ref: ResourcesRef) -> None:
95
95
  check.isinstance(ref, ResourcesRef)
96
- with self._lock:
97
- check.state(not self._closed)
98
- self._refs.add(ref)
96
+ check.state(not self._closed)
97
+ self._refs.add(ref)
99
98
 
100
99
  def has_ref(self, ref: ResourcesRef) -> bool:
101
- with self._lock:
102
- return ref in self._refs
100
+ return ref in self._refs
103
101
 
104
- def remove_ref(self, ref: ResourcesRef) -> None:
102
+ async def remove_ref(self, ref: ResourcesRef) -> None:
105
103
  check.isinstance(ref, ResourcesRef)
106
- with self._lock:
107
- try:
108
- self._refs.remove(ref)
109
- except KeyError:
110
- raise ResourcesRefNotRegisteredError(ref) from None
111
- if not self._no_autoclose and not self._refs:
112
- self.close()
104
+ try:
105
+ self._refs.remove(ref)
106
+ except KeyError:
107
+ raise ResourcesRefNotRegisteredError(ref) from None
108
+ if not self._no_autoclose and not self._refs:
109
+ await self.aclose()
113
110
 
114
111
  #
115
112
 
116
113
  def enter_context(self, cm: ta.ContextManager[T]) -> T:
117
- with self._lock:
118
- check.state(not self._closed)
119
- return self._es.enter_context(cm)
114
+ check.state(not self._closed)
115
+ return self._aes.enter_context(cm)
116
+
117
+ async def enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
118
+ check.state(not self._closed)
119
+ return await self._aes.enter_async_context(cm)
120
120
 
121
121
  #
122
122
 
@@ -125,12 +125,11 @@ class Resources(lang.Final, lang.NotPicklable):
125
125
 
126
126
  #
127
127
 
128
- def close(self) -> None:
129
- with self._lock:
130
- try:
131
- self._es.__exit__(None, None, None)
132
- finally:
133
- self._closed = True
128
+ async def aclose(self) -> None:
129
+ try:
130
+ await self._aes.__aexit__(None, None, None)
131
+ finally:
132
+ self._closed = True
134
133
 
135
134
  def __del__(self) -> None:
136
135
  if not self._closed:
@@ -147,24 +146,48 @@ class Resources(lang.Final, lang.NotPicklable):
147
146
  ##
148
147
 
149
148
 
149
+ @ta.final
150
150
  class ResourceManaged(ResourcesRef, lang.Final, lang.NotPicklable, ta.Generic[T]):
151
+ """
152
+ A class to 'handoff' a ref to a `Resources`, allowing the `Resources` to temporarily survive being passed from
153
+ instantiation within a callee to being `__aenter__`'d in the caller.
154
+
155
+ The ref to the `Resources` is allocated in the ctor, so the contract is that an instance of this must be immediately
156
+ `__aenter__`'d before doing anything else with the return value of the call. Failure to do so leaks the `Resources`.
157
+ """
158
+
151
159
  def __init__(self, v: T, resources: Resources) -> None:
152
160
  super().__init__()
153
161
 
154
- self._v = v
162
+ self.__v = v
155
163
  self.__resources = resources
156
164
 
157
165
  resources.add_ref(self)
158
166
 
167
+ __state: ta.Literal['new', 'entered', 'exited'] = 'new'
168
+
159
169
  def __repr__(self) -> str:
160
- return f'{self.__class__.__name__}<{self._v!r}>'
170
+ return f'{self.__class__.__name__}<{self.__v!r}, {self.__state}>'
161
171
 
162
- def __enter__(self) -> T:
163
- return self._v
172
+ async def __aenter__(self) -> T:
173
+ check.state(self.__state == 'new')
174
+ self.__state = 'entered'
175
+ return self.__v
164
176
 
165
- def __exit__(self, exc_type, exc_val, exc_tb):
166
- self.__resources.remove_ref(self)
177
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
178
+ check.state(self.__state == 'entered')
179
+ self.__state = 'exited'
180
+ await self.__resources.remove_ref(self)
167
181
 
182
+ def __del__(self) -> None:
183
+ if self.__state != 'exited':
184
+ log.error(
185
+ f'{__package__}.{self.__class__.__name__}.__del__: ' # noqa
186
+ f'%r deleted without being entered and exited! '
187
+ f'resources: %s',
188
+ repr(self),
189
+ repr(self.__resources),
190
+ )
168
191
 
169
192
  ##
170
193
 
@@ -175,11 +198,11 @@ class ResourcesOption(Option, lang.Abstract):
175
198
 
176
199
  class UseResources(tv.UniqueScalarTypedValue[Resources], ResourcesOption, lang.Final):
177
200
  @classmethod
178
- @contextlib.contextmanager
179
- def or_new(cls, options: ta.Sequence[Option]) -> ta.Iterator[Resources]:
201
+ @contextlib.asynccontextmanager
202
+ async def or_new(cls, options: ta.Sequence[Option]) -> ta.AsyncGenerator[Resources]:
180
203
  if (ur := tv.as_collection(options).get(UseResources)) is not None:
181
- with ResourceManaged(ur.v, ur.v) as rs:
204
+ async with ResourceManaged(ur.v, ur.v) as rs:
182
205
  yield rs
183
206
  else:
184
- with Resources.new() as rs:
207
+ async with Resources.new() as rs:
185
208
  yield rs
@@ -19,7 +19,7 @@ class SearchHit(lang.Final):
19
19
  title: str | None
20
20
  url: str | None
21
21
  description: str | None = None
22
- snippets: lang.SequenceNotStr[str] | None
22
+ snippets: lang.SequenceNotStr[str] | None = None
23
23
 
24
24
 
25
25
  @dc.dataclass(frozen=True, kw_only=True)
@@ -40,14 +40,14 @@ class _RequestResponseMarshaler(msh.Marshaler):
40
40
  def marshal(self, ctx: msh.MarshalContext, o: ta.Any) -> msh.Value:
41
41
  tv_types_set = o._typed_values_info().tv_types_set # noqa # FIXME
42
42
  tv_ta = tv.TypedValues[ta.Union[*tv_types_set]] # type: ignore
43
- tv_m = ctx.make(tv_ta)
43
+ tv_m = ctx.marshal_factory_context.make_marshaler(tv_ta) # FIXME:
44
44
  tv_v = check.isinstance(tv_m.marshal(ctx, o._typed_values), ta.Sequence) # noqa
45
45
 
46
46
  if self.v_m is None:
47
47
  orty: rfl.Generic = check.isinstance(rfl.type_(rfl.get_orig_class(o)), rfl.Generic)
48
48
  check.state(orty.cls in (Request, Response))
49
49
  v_rty, tv_rty = orty.args
50
- v_v = ctx.make(v_rty).marshal(ctx, o.v)
50
+ v_v = ctx.marshal_factory_context.make_marshaler(v_rty).marshal(ctx, o.v) # FIXME
51
51
  else:
52
52
  v_v = self.v_m.marshal(ctx, o.v)
53
53
 
@@ -57,26 +57,30 @@ class _RequestResponseMarshaler(msh.Marshaler):
57
57
  }
58
58
 
59
59
 
60
- class _RequestResponseMarshalerFactory(msh.SimpleMarshalerFactory):
61
- def guard(self, ctx: msh.MarshalContext, rty: rfl.Type) -> bool:
62
- return _is_rr_rty(rty)
60
+ class _RequestResponseMarshalerFactory(msh.MarshalerFactory):
61
+ def make_marshaler(self, ctx: msh.MarshalFactoryContext, rty: rfl.Type) -> ta.Callable[[], msh.Marshaler] | None:
62
+ if not _is_rr_rty(rty):
63
+ return None
63
64
 
64
- def fn(self, ctx: msh.MarshalContext, rty: rfl.Type) -> msh.Marshaler:
65
65
  if isinstance(rty, type):
66
66
  rty = rfl.type_(rfl.get_orig_class(rty))
67
- if isinstance(rty, rfl.Generic):
68
- v_rty, tv_rty = rty.args
69
- else:
70
- # FIXME: ...
71
- raise TypeError(rty)
72
- v_m: msh.Marshaler | None = None
73
- if not isinstance(v_rty, ta.TypeVar):
74
- v_m = ctx.make(v_rty)
75
- return _RequestResponseMarshaler(
76
- rty,
77
- _get_tv_fld(rty),
78
- v_m,
79
- )
67
+
68
+ def inner() -> msh.Marshaler:
69
+ if isinstance(rty, rfl.Generic):
70
+ v_rty, tv_rty = rty.args
71
+ else:
72
+ # FIXME: ...
73
+ raise TypeError(rty)
74
+ v_m: msh.Marshaler | None = None
75
+ if not isinstance(v_rty, ta.TypeVar):
76
+ v_m = ctx.make_marshaler(v_rty)
77
+ return _RequestResponseMarshaler(
78
+ rty,
79
+ _get_tv_fld(rty),
80
+ v_m,
81
+ )
82
+
83
+ return inner
80
84
 
81
85
 
82
86
  #
@@ -108,33 +112,36 @@ class _RequestResponseUnmarshaler(msh.Unmarshaler):
108
112
  return cty(v, tvs) # type: ignore
109
113
 
110
114
 
111
- class _RequestResponseUnmarshalerFactory(msh.SimpleUnmarshalerFactory):
112
- def guard(self, ctx: msh.UnmarshalContext, rty: rfl.Type) -> bool:
113
- return _is_rr_rty(rty)
115
+ class _RequestResponseUnmarshalerFactory(msh.UnmarshalerFactory):
116
+ def make_unmarshaler(self, ctx: msh.UnmarshalFactoryContext, rty: rfl.Type) -> ta.Callable[[], msh.Unmarshaler] | None: # noqa
117
+ if not _is_rr_rty(rty):
118
+ return None
114
119
 
115
- def fn(self, ctx: msh.UnmarshalContext, rty: rfl.Type) -> msh.Unmarshaler:
116
- if isinstance(rty, rfl.Generic):
117
- v_rty, tv_rty = rty.args
118
- else:
119
- # FIXME: ...
120
- raise TypeError(rty)
121
- tv_types_set = check.isinstance(tv_rty, rfl.Union).args
122
- tv_ta = tv.TypedValues[ta.Union[*tv_types_set]] # type: ignore
123
- tv_u = ctx.make(tv_ta)
124
- v_u = ctx.make(v_rty)
125
- return _RequestResponseUnmarshaler(
126
- rty,
127
- _get_tv_fld(rty),
128
- v_u,
129
- tv_u,
130
- )
120
+ def inner() -> msh.Unmarshaler:
121
+ if isinstance(rty, rfl.Generic):
122
+ v_rty, tv_rty = rty.args
123
+ else:
124
+ # FIXME: ...
125
+ raise TypeError(rty)
126
+ tv_types_set = check.isinstance(tv_rty, rfl.Union).args
127
+ tv_ta = tv.TypedValues[ta.Union[*tv_types_set]] # type: ignore
128
+ tv_u = ctx.make_unmarshaler(tv_ta)
129
+ v_u = ctx.make_unmarshaler(v_rty)
130
+ return _RequestResponseUnmarshaler(
131
+ rty,
132
+ _get_tv_fld(rty),
133
+ v_u,
134
+ tv_u,
135
+ )
136
+
137
+ return inner
131
138
 
132
139
 
133
140
  ##
134
141
 
135
142
 
136
143
  @lang.static_init
137
- def _install_standard_marshalling() -> None:
144
+ def _install_standard_marshaling() -> None:
138
145
  msh.install_standard_factories(
139
146
  _RequestResponseMarshalerFactory(),
140
147
  _RequestResponseUnmarshalerFactory(),
@@ -44,15 +44,15 @@ class ServiceFacade(
44
44
  ],
45
45
  ]
46
46
 
47
- def invoke(self, request: Request[RequestV, OptionT]) -> Response[ResponseV, OutputT]:
47
+ def invoke(self, request: Request[RequestV, OptionT]) -> ta.Awaitable[Response[ResponseV, OutputT]]:
48
48
  return self.service.invoke(request)
49
49
 
50
50
  @ta.overload
51
- def __call__(self, request: Request[RequestV, OptionT]) -> Response[ResponseV, OutputT]:
51
+ def __call__(self, request: Request[RequestV, OptionT]) -> ta.Awaitable[Response[ResponseV, OutputT]]:
52
52
  ...
53
53
 
54
54
  @ta.overload
55
- def __call__(self, v: RequestV, *options: OptionT) -> Response[ResponseV, OutputT]:
55
+ def __call__(self, v: RequestV, *options: OptionT) -> ta.Awaitable[Response[ResponseV, OutputT]]:
56
56
  ...
57
57
 
58
58
  def __call__(self, o, *args):
@@ -11,4 +11,4 @@ from .responses import ResponseT_co
11
11
 
12
12
  @ta.runtime_checkable
13
13
  class Service(lang.ProtocolForbiddenAsBaseClass, ta.Protocol[RequestT_contra, ResponseT_co]):
14
- def invoke(self, request: RequestT_contra) -> ResponseT_co: ...
14
+ def invoke(self, request: RequestT_contra) -> ta.Awaitable[ResponseT_co]: ...
@@ -25,6 +25,14 @@ class Device(tv.UniqueScalarTypedValue[ta.Any], Config):
25
25
  ##
26
26
 
27
27
 
28
+ # TODO: ApiEndpointPath, ApiEndpointUrl, ApiBaseUrl, ...
29
+ class ApiUrl(tv.UniqueScalarTypedValue[str], Config):
30
+ pass
31
+
32
+
33
+ ##
34
+
35
+
28
36
  @dc.dataclass(frozen=True)
29
37
  class SecretConfig(Config, lang.Abstract):
30
38
  v: sec.SecretRefOrStr = dc.field() | sec.secret_field
@@ -1,4 +1,6 @@
1
- import contextlib
1
+ import abc
2
+ import itertools
3
+ import types
2
4
  import typing as ta
3
5
 
4
6
  from omlish import check
@@ -14,6 +16,8 @@ from ..types import Output
14
16
 
15
17
 
16
18
  V = ta.TypeVar('V')
19
+ V2 = ta.TypeVar('V2')
20
+
17
21
  OutputT = ta.TypeVar('OutputT', bound=Output)
18
22
  StreamOutputT = ta.TypeVar('StreamOutputT', bound=Output)
19
23
 
@@ -31,44 +35,155 @@ StreamOptions: ta.TypeAlias = StreamOption | ResourcesOption
31
35
  ##
32
36
 
33
37
 
34
- class ResponseGenerator(lang.Final, ta.Generic[V, OutputT]):
35
- def __init__(self, g: ta.Generator[V, None, ta.Sequence[OutputT] | None]) -> None:
38
+ class StreamResponseSink(lang.Abstract, ta.Generic[V]):
39
+ @abc.abstractmethod
40
+ def emit(self, value: V) -> ta.Awaitable[None]:
41
+ raise NotImplementedError
42
+
43
+
44
+ class StreamResponseIterator(
45
+ ta.AsyncContextManager['StreamResponseIterator[V, OutputT]'],
46
+ lang.Abstract,
47
+ ta.Generic[V, OutputT],
48
+ ):
49
+ @property
50
+ @abc.abstractmethod
51
+ def outputs(self) -> tv.TypedValues[OutputT]:
52
+ raise NotImplementedError
53
+
54
+ @ta.final
55
+ def __aiter__(self) -> ta.AsyncIterator[V]:
56
+ return self
57
+
58
+ @abc.abstractmethod
59
+ def __anext__(self) -> ta.Awaitable[V]:
60
+ raise NotImplementedError
61
+
62
+
63
+ ##
64
+
65
+
66
+ class StreamServiceCancelledError(BaseException):
67
+ pass
68
+
69
+
70
+ class StreamServiceNotAwaitedError(Exception):
71
+ pass
72
+
73
+
74
+ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
75
+ def __init__(
76
+ self,
77
+ fn: ta.Callable[[StreamResponseSink[V]], ta.Awaitable[ta.Sequence[OutputT] | None]],
78
+ ) -> None:
36
79
  super().__init__()
37
80
 
38
- self._g = g
39
- self._is_done = False
81
+ self._fn = fn
40
82
 
41
- def __repr__(self) -> str:
42
- return f'{self.__class__.__name__}<{self._g!r}>'
83
+ @ta.final
84
+ class _Emit(ta.Generic[V2]):
85
+ def __init__(self, ssr: '_StreamServiceResponse', value: V2) -> None:
86
+ self.ssr, self.value = ssr, value
43
87
 
44
- _outputs: tv.TypedValues[OutputT]
88
+ done: bool = False
45
89
 
46
- @property
47
- def is_done(self) -> bool:
48
- return self._is_done
90
+ def __await__(self) -> ta.Generator['_StreamServiceResponse._Emit[V2]']:
91
+ if not self.done:
92
+ yield self
93
+ if not self.done:
94
+ raise StreamServiceNotAwaitedError
95
+
96
+ @ta.final
97
+ class _Sink(StreamResponseSink[V2]):
98
+ def __init__(self, ssr: '_StreamServiceResponse') -> None:
99
+ super().__init__()
100
+
101
+ self._ssr = ssr
102
+
103
+ def emit(self, item: V2) -> ta.Awaitable[None]:
104
+ return _StreamServiceResponse._Emit(self._ssr, item)
105
+
106
+ _state: ta.Literal['new', 'running', 'closed'] = 'new'
107
+ _sink: _Sink[V]
108
+ _a: ta.Any
109
+ _cr: ta.Any
110
+
111
+ async def __aenter__(self) -> ta.Self:
112
+ check.state(self._state == 'new')
113
+ self._state = 'running'
114
+ self._sink = _StreamServiceResponse._Sink(self)
115
+ self._cr = self._fn(self._sink)
116
+ self._a = self._cr.__await__()
117
+ self._g = iter(self._a)
118
+ return self
119
+
120
+ @types.coroutine
121
+ def _aexit(self, exc_type, exc_val, exc_tb):
122
+ old_state = self._state
123
+ self._state = 'closed'
124
+ if old_state != 'running':
125
+ return
126
+ if self._cr.cr_running or self._cr.cr_suspended:
127
+ cex = StreamServiceCancelledError()
128
+ i = None
129
+ for n in itertools.count():
130
+ try:
131
+ if not n:
132
+ x = self._g.throw(cex)
133
+ else:
134
+ x = self._g.send(i)
135
+ except StreamServiceCancelledError as cex2:
136
+ if cex2 is cex:
137
+ break
138
+ raise
139
+ i = yield x
140
+ if self._cr.cr_running:
141
+ raise RuntimeError(f'Coroutine {self._cr!r} not terminated')
142
+ if self._g is not self._a:
143
+ self._g.close()
144
+ self._a.close()
145
+ self._cr.close()
146
+
147
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
148
+ return await self._aexit(exc_type, exc_val, exc_tb)
149
+
150
+ _outputs: tv.TypedValues[OutputT]
49
151
 
50
152
  @property
51
153
  def outputs(self) -> tv.TypedValues[OutputT]:
52
154
  return self._outputs
53
155
 
54
- def __iter__(self) -> ta.Iterator[V]:
55
- return self
156
+ @types.coroutine
157
+ def _anext(self):
158
+ check.state(self._state == 'running')
159
+ i = None
160
+ while True:
161
+ try:
162
+ x = self._g.send(i)
163
+ except StopIteration as e:
164
+ if e.value is not None:
165
+ self._outputs = tv.TypedValues(*check.isinstance(e.value, ta.Sequence))
166
+ else:
167
+ self._outputs = tv.TypedValues()
168
+ raise StopAsyncIteration from None
169
+
170
+ if isinstance(x, _StreamServiceResponse._Emit) and x.ssr is self:
171
+ check.state(not x.done)
172
+ x.done = True
173
+ return x.value
56
174
 
57
- def __next__(self) -> V:
58
- try:
59
- return next(self._g)
60
- except StopIteration as e:
61
- self._is_done = True
62
- if e.value is not None:
63
- self._outputs = tv.TypedValues(*check.isinstance(e.value, ta.Sequence))
64
- else:
65
- self._outputs = tv.TypedValues()
66
- raise
175
+ i = yield x
176
+
177
+ async def __anext__(self) -> V:
178
+ return await self._anext()
179
+
180
+
181
+ ##
67
182
 
68
183
 
69
184
  StreamResponse: ta.TypeAlias = Response[
70
185
  ResourceManaged[
71
- ResponseGenerator[
186
+ StreamResponseIterator[
72
187
  V,
73
188
  OutputT,
74
189
  ],
@@ -77,20 +192,19 @@ StreamResponse: ta.TypeAlias = Response[
77
192
  ]
78
193
 
79
194
 
80
- def new_stream_response(
195
+ async def new_stream_response(
81
196
  rs: Resources,
82
- g: ta.Generator[V, None, ta.Sequence[OutputT] | None],
197
+ fn: ta.Callable[[StreamResponseSink[V]], ta.Awaitable[ta.Sequence[OutputT] | None]],
83
198
  outputs: ta.Sequence[StreamOutputT] | None = None,
84
199
  ) -> StreamResponse[V, OutputT, StreamOutputT]:
85
- return StreamResponse(
86
- rs.new_managed(
87
- ResponseGenerator(
88
- rs.enter_context(
89
- contextlib.closing(
90
- g,
91
- ),
92
- ),
93
- ),
94
- ),
95
- outputs or [],
96
- )
200
+ ssr = _StreamServiceResponse(fn)
201
+
202
+ v = rs.new_managed(await rs.enter_async_context(ssr))
203
+ try:
204
+ return StreamResponse(v, outputs or [])
205
+ except BaseException: # noqa
206
+ # The StreamResponse ctor can raise - for example in `_tv_field_coercer` - in which case we need to clean up the
207
+ # resources ref we have already allocated before reraising.
208
+ async with v:
209
+ pass
210
+ raise
@@ -1,12 +1,12 @@
1
1
  # ruff: noqa: UP028
2
2
  import typing as ta
3
3
 
4
- from ..resources import Resources
4
+ from ..resources import UseResources
5
5
  from ..services import Request
6
6
  from ..services import Service
7
7
  from ..types import Output
8
- from .services import ResponseGenerator
9
8
  from .services import StreamResponse
9
+ from .services import StreamResponseSink
10
10
  from .services import new_stream_response
11
11
 
12
12
 
@@ -29,36 +29,34 @@ class WrappedStreamService(ta.Generic[StreamRequestT, V, OutputT, StreamOutputT]
29
29
 
30
30
  #
31
31
 
32
- def _process_request(self, request: StreamRequestT) -> StreamRequestT:
32
+ async def _process_request(self, request: StreamRequestT) -> StreamRequestT:
33
33
  return request
34
34
 
35
- def _process_stream_outputs(self, outputs: ta.Sequence[StreamOutputT]) -> ta.Sequence[StreamOutputT]:
35
+ async def _process_stream_outputs(self, outputs: ta.Sequence[StreamOutputT]) -> ta.Sequence[StreamOutputT]:
36
36
  return outputs
37
37
 
38
- def _process_vs(self, vs: ta.Iterator[V]) -> ta.Iterator[V]:
39
- return vs
38
+ async def _process_value(self, v: V) -> ta.Iterable[V]:
39
+ return [v]
40
40
 
41
- def _process_outputs(self, outputs: ta.Sequence[OutputT]) -> ta.Sequence[OutputT]:
41
+ async def _process_outputs(self, outputs: ta.Sequence[OutputT]) -> ta.Sequence[OutputT]:
42
42
  return outputs
43
43
 
44
44
  #
45
45
 
46
- def invoke(self, request: StreamRequestT) -> StreamResponse[V, OutputT, StreamOutputT]:
47
- with Resources.new() as rs:
48
- in_response = self._inner.invoke(self._process_request(request))
49
- in_vs: ResponseGenerator[V, OutputT] = rs.enter_context(in_response.v)
50
- out_vs = self._process_vs(in_vs)
51
-
52
- def yield_vs() -> ta.Generator[V, None, ta.Sequence[OutputT] | None]:
53
- while True:
54
- try:
55
- out_v = next(out_vs)
56
- except StopIteration as se:
57
- return self._process_outputs(se.value)
58
- yield out_v
59
-
60
- return new_stream_response(
46
+ async def invoke(self, request: StreamRequestT) -> StreamResponse[V, OutputT, StreamOutputT]:
47
+ async with UseResources.or_new(request.options) as rs: # noqa
48
+ in_resp = await self._inner.invoke(await self._process_request(request))
49
+ in_vs = await rs.enter_async_context(in_resp.v)
50
+
51
+ async def inner(sink: StreamResponseSink[V]) -> ta.Sequence[OutputT] | None:
52
+ async for in_v in in_vs:
53
+ for out_v in (await self._process_value(in_v)):
54
+ await sink.emit(out_v)
55
+
56
+ return await self._process_outputs(in_vs.outputs)
57
+
58
+ return await new_stream_response(
61
59
  rs,
62
- yield_vs(),
63
- self._process_stream_outputs(in_response.outputs),
60
+ inner,
61
+ await self._process_stream_outputs(in_resp.outputs),
64
62
  )