spin-sdk 3.2.0__tar.gz → 3.3.0__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 (105) hide show
  1. {spin_sdk-3.2.0/src/spin_sdk.egg-info → spin_sdk-3.3.0}/PKG-INFO +2 -2
  2. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/README.md +1 -1
  3. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/pyproject.toml +1 -1
  4. spin_sdk-3.3.0/src/spin_sdk/componentize-py.toml +5 -0
  5. spin_sdk-3.3.0/src/spin_sdk/http/__init__.py +236 -0
  6. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/http/poll_loop.py +4 -4
  7. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/__init__.py +1 -0
  8. spin_sdk-3.3.0/src/spin_sdk/wit/deps/keyvalue-2024-10-17/atomic.wit +46 -0
  9. spin_sdk-3.3.0/src/spin_sdk/wit/deps/keyvalue-2024-10-17/batch.wit +63 -0
  10. spin_sdk-3.3.0/src/spin_sdk/wit/deps/keyvalue-2024-10-17/store.wit +122 -0
  11. spin_sdk-3.3.0/src/spin_sdk/wit/deps/keyvalue-2024-10-17/watch.wit +16 -0
  12. spin_sdk-3.3.0/src/spin_sdk/wit/deps/keyvalue-2024-10-17/world.wit +26 -0
  13. spin_sdk-3.3.0/src/spin_sdk/wit/deps/spin-postgres@3.0.0/postgres.wit +100 -0
  14. spin_sdk-3.3.0/src/spin_sdk/wit/deps/spin@3.0.0/world.wit +24 -0
  15. spin_sdk-3.3.0/src/spin_sdk/wit/deps/wasi-runtime-config-2024-09-27/store.wit +30 -0
  16. spin_sdk-3.3.0/src/spin_sdk/wit/deps/wasi-runtime-config-2024-09-27/world.wit +6 -0
  17. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/exports/__init__.py +1 -0
  18. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/exports/inbound_redis.py +1 -0
  19. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/exports/incoming_handler.py +1 -0
  20. spin_sdk-3.3.0/src/spin_sdk/wit/imports/atomics.py +96 -0
  21. spin_sdk-3.3.0/src/spin_sdk/wit/imports/batch.py +88 -0
  22. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/error.py +4 -4
  23. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/key_value.py +5 -10
  24. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/llm.py +1 -0
  25. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/monotonic_clock.py +1 -0
  26. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/mqtt.py +4 -5
  27. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/mysql.py +4 -6
  28. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/outgoing_handler.py +1 -0
  29. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/poll.py +5 -6
  30. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/postgres.py +4 -6
  31. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/rdbms_types.py +3 -2
  32. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/redis.py +4 -13
  33. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/redis_types.py +1 -0
  34. spin_sdk-3.3.0/src/spin_sdk/wit/imports/spin_postgres_postgres.py +275 -0
  35. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/sqlite.py +4 -5
  36. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/streams.py +7 -21
  37. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/types.py +40 -85
  38. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/variables.py +1 -0
  39. spin_sdk-3.3.0/src/spin_sdk/wit/imports/wasi_config_store.py +49 -0
  40. spin_sdk-3.3.0/src/spin_sdk/wit/imports/wasi_keyvalue_store.py +180 -0
  41. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/llm.wit +4 -4
  42. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/rdbms-types.wit +4 -4
  43. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/sqlite.wit +1 -1
  44. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/types.py +1 -0
  45. {spin_sdk-3.2.0 → spin_sdk-3.3.0/src/spin_sdk.egg-info}/PKG-INFO +2 -2
  46. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk.egg-info/SOURCES.txt +15 -1
  47. spin_sdk-3.2.0/src/spin_sdk/componentize-py.toml +0 -2
  48. spin_sdk-3.2.0/src/spin_sdk/http/__init__.py +0 -210
  49. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/LICENSE +0 -0
  50. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/MANIFEST.in +0 -0
  51. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/setup.cfg +0 -0
  52. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/__init__.py +0 -0
  53. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/key_value.py +0 -0
  54. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/llm.py +0 -0
  55. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/mqtt.py +0 -0
  56. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/mysql.py +0 -0
  57. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/postgres.py +0 -0
  58. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/py.typed +0 -0
  59. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/redis.py +0 -0
  60. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/sqlite.py +0 -0
  61. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/variables.py +0 -0
  62. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/command.wit +0 -0
  63. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/environment.wit +0 -0
  64. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/exit.wit +0 -0
  65. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/imports.wit +0 -0
  66. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/run.wit +0 -0
  67. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/stdio.wit +0 -0
  68. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/cli/terminal.wit +0 -0
  69. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/clocks/monotonic-clock.wit +0 -0
  70. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/clocks/wall-clock.wit +0 -0
  71. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/clocks/world.wit +0 -0
  72. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/filesystem/preopens.wit +0 -0
  73. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/filesystem/types.wit +0 -0
  74. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/filesystem/world.wit +0 -0
  75. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/http/handler.wit +0 -0
  76. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/http/proxy.wit +0 -0
  77. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/http/types.wit +0 -0
  78. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/io/error.wit +0 -0
  79. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/io/poll.wit +0 -0
  80. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/io/streams.wit +0 -0
  81. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/io/world.wit +0 -0
  82. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/random/insecure-seed.wit +0 -0
  83. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/random/insecure.wit +0 -0
  84. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/random/random.wit +0 -0
  85. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/random/world.wit +0 -0
  86. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/instance-network.wit +0 -0
  87. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/ip-name-lookup.wit +0 -0
  88. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/network.wit +0 -0
  89. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/tcp-create-socket.wit +0 -0
  90. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/tcp.wit +0 -0
  91. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/udp-create-socket.wit +0 -0
  92. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/udp.wit +0 -0
  93. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/sockets/world.wit +0 -0
  94. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/spin@unversioned/inbound-redis.wit +0 -0
  95. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/deps/spin@unversioned/redis-types.wit +0 -0
  96. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/imports/__init__.py +0 -0
  97. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/key-value.wit +0 -0
  98. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/mqtt.wit +0 -0
  99. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/mysql.wit +0 -0
  100. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/postgres.wit +0 -0
  101. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/redis.wit +0 -0
  102. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/spin.wit +0 -0
  103. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk/wit/variables.wit +0 -0
  104. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk.egg-info/dependency_links.txt +0 -0
  105. {spin_sdk-3.2.0 → spin_sdk-3.3.0}/src/spin_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spin-sdk
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Experimental SDK for Spin and Componentize-Py
5
5
  Author-email: Fermyon Engineering <engineering@fermyon.com>
6
6
  Maintainer-email: Fermyon Engineering <engineering@fermyon.com>
@@ -254,7 +254,7 @@ enter a virtual environment and then install the desired packages
254
254
  ```shell
255
255
  python -m venv .venv
256
256
  source .venv/bin/activate
257
- pip install componentize-py==0.13.3 spin-sdk==3.1.0 mypy==1.8.0
257
+ pip install componentize-py==0.16.0 spin-sdk==3.3.0 mypy==1.8.0
258
258
  ```
259
259
 
260
260
  ### Hello, World
@@ -24,7 +24,7 @@ enter a virtual environment and then install the desired packages
24
24
  ```shell
25
25
  python -m venv .venv
26
26
  source .venv/bin/activate
27
- pip install componentize-py==0.13.3 spin-sdk==3.1.0 mypy==1.8.0
27
+ pip install componentize-py==0.16.0 spin-sdk==3.3.0 mypy==1.8.0
28
28
  ```
29
29
 
30
30
  ### Hello, World
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spin-sdk"
7
- version = "3.2.0"
7
+ version = "3.3.0"
8
8
  description = "Experimental SDK for Spin and Componentize-Py"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1,5 @@
1
+ wit_directory = "wit"
2
+ bindings = "wit"
3
+
4
+ [import_interface_names]
5
+ "fermyon:spin/postgres@2.0.0" = "postgres"
@@ -0,0 +1,236 @@
1
+ """Module with helpers for wasi http"""
2
+
3
+ import asyncio
4
+ import traceback
5
+ from spin_sdk.http import poll_loop
6
+ from spin_sdk.http.poll_loop import PollLoop, Sink, Stream
7
+ from spin_sdk.wit.types import Ok, Err
8
+ from spin_sdk.wit.imports.types import (
9
+ IncomingResponse, Method, Method_Get, Method_Head, Method_Post, Method_Put, Method_Delete, Method_Connect, Method_Options,
10
+ Method_Trace, Method_Patch, Method_Other, IncomingRequest, IncomingBody, ResponseOutparam, OutgoingResponse,
11
+ Fields, Scheme, Scheme_Http, Scheme_Https, Scheme_Other, OutgoingRequest, OutgoingBody
12
+ )
13
+ from spin_sdk.wit.imports.streams import StreamError_Closed
14
+ from dataclasses import dataclass
15
+ from collections.abc import MutableMapping
16
+ from typing import Optional
17
+ from urllib import parse
18
+
19
+ @dataclass
20
+ class Request:
21
+ """An HTTP request"""
22
+ method: str
23
+ uri: str
24
+ headers: MutableMapping[str, str]
25
+ body: Optional[bytes]
26
+
27
+ @dataclass
28
+ class Response:
29
+ """An HTTP response"""
30
+ status: int
31
+ headers: MutableMapping[str, str]
32
+ body: Optional[bytes]
33
+
34
+ try:
35
+ from spin_sdk.wit import exports
36
+ from spin_sdk.wit.exports import IncomingHandler as Base
37
+
38
+ class IncomingHandler(Base):
39
+ """Simplified handler for incoming HTTP requests using blocking, buffered I/O."""
40
+
41
+ def handle_request(self, request: Request) -> Response:
42
+ """Handle an incoming HTTP request and return a response or raise an error"""
43
+ raise NotImplementedError
44
+
45
+ def handle(self, request: IncomingRequest, response_out: ResponseOutparam):
46
+ method = request.method()
47
+
48
+ if isinstance(method, Method_Get):
49
+ method_str = "GET"
50
+ elif isinstance(method, Method_Head):
51
+ method_str = "HEAD"
52
+ elif isinstance(method, Method_Post):
53
+ method_str = "POST"
54
+ elif isinstance(method, Method_Put):
55
+ method_str = "PUT"
56
+ elif isinstance(method, Method_Delete):
57
+ method_str = "DELETE"
58
+ elif isinstance(method, Method_Connect):
59
+ method_str = "CONNECT"
60
+ elif isinstance(method, Method_Options):
61
+ method_str = "OPTIONS"
62
+ elif isinstance(method, Method_Trace):
63
+ method_str = "TRACE"
64
+ elif isinstance(method, Method_Patch):
65
+ method_str = "PATCH"
66
+ elif isinstance(method, Method_Other):
67
+ method_str = method.value
68
+ else:
69
+ raise AssertionError
70
+
71
+ request_body = request.consume()
72
+ request_stream = request_body.stream()
73
+ body = bytearray()
74
+ while True:
75
+ try:
76
+ body += request_stream.blocking_read(16 * 1024)
77
+ except Err as e:
78
+ if isinstance(e.value, StreamError_Closed):
79
+ request_stream.__exit__(None, None, None)
80
+ IncomingBody.finish(request_body)
81
+ break
82
+ else:
83
+ raise e
84
+
85
+ request_uri = request.path_with_query()
86
+ if request_uri is None:
87
+ uri = "/"
88
+ else:
89
+ uri = request_uri
90
+
91
+ try:
92
+ simple_response = self.handle_request(Request(
93
+ method_str,
94
+ uri,
95
+ dict(map(lambda pair: (pair[0], str(pair[1], "utf-8")), request.headers().entries())),
96
+ bytes(body)
97
+ ))
98
+ except:
99
+ traceback.print_exc()
100
+
101
+ response = OutgoingResponse(Fields())
102
+ response.set_status_code(500)
103
+ ResponseOutparam.set(response_out, Ok(response))
104
+ return
105
+
106
+ if simple_response.headers.get('content-length') is None:
107
+ content_length = len(simple_response.body) if simple_response.body is not None else 0
108
+ simple_response.headers['content-length'] = str(content_length)
109
+
110
+ response = OutgoingResponse(Fields.from_list(list(map(
111
+ lambda pair: (pair[0], bytes(pair[1], "utf-8")),
112
+ simple_response.headers.items()
113
+ ))))
114
+ response_body = response.body()
115
+ response.set_status_code(simple_response.status)
116
+ ResponseOutparam.set(response_out, Ok(response))
117
+ response_stream = response_body.write()
118
+ if simple_response.body is not None:
119
+ MAX_BLOCKING_WRITE_SIZE = 4096
120
+ offset = 0
121
+ while offset < len(simple_response.body):
122
+ count = min(len(simple_response.body) - offset, MAX_BLOCKING_WRITE_SIZE)
123
+ response_stream.blocking_write_and_flush(simple_response.body[offset:offset+count])
124
+ offset += count
125
+ response_stream.__exit__(None, None, None)
126
+ OutgoingBody.finish(response_body, None)
127
+
128
+ except ImportError:
129
+ # `spin_sdk.wit.exports` won't exist if the use is targeting `spin-imports`,
130
+ # so just skip this part
131
+ pass
132
+
133
+ def send(request: Request) -> Response:
134
+ """Send an HTTP request and return a response or raise an error"""
135
+ loop = PollLoop()
136
+ asyncio.set_event_loop(loop)
137
+ return loop.run_until_complete(send_async(request))
138
+
139
+
140
+ async def send_async(request: Request) -> Response:
141
+ match request.method:
142
+ case "GET":
143
+ method: Method = Method_Get()
144
+ case "HEAD":
145
+ method = Method_Head()
146
+ case "POST":
147
+ method = Method_Post()
148
+ case "PUT":
149
+ method = Method_Put()
150
+ case "DELETE":
151
+ method = Method_Delete()
152
+ case "CONNECT":
153
+ method = Method_Connect()
154
+ case "OPTIONS":
155
+ method = Method_Options()
156
+ case "TRACE":
157
+ method = Method_Trace()
158
+ case "PATCH":
159
+ method = Method_Patch()
160
+ case _:
161
+ method = Method_Other(request.method)
162
+
163
+ url_parsed = parse.urlparse(request.uri)
164
+
165
+ match url_parsed.scheme:
166
+ case "http":
167
+ scheme: Scheme = Scheme_Http()
168
+ case "https":
169
+ scheme = Scheme_Https()
170
+ case "":
171
+ scheme = Scheme_Http()
172
+ case _:
173
+ scheme = Scheme_Other(url_parsed.scheme)
174
+
175
+ headers_dict = request.headers
176
+
177
+ # Add a `content-length` header if the caller didn't include one, but did
178
+ # specify a body:
179
+ if headers_dict.get('content-length') is None:
180
+ content_length = len(request.body) if request.body is not None else 0
181
+ # Make a copy rather than mutate in place, since the caller might not
182
+ # expect us to mutate it:
183
+ headers_dict = headers_dict.copy()
184
+ headers_dict['content-length'] = str(content_length)
185
+
186
+ headers = list(map(
187
+ lambda pair: (pair[0], bytes(pair[1], "utf-8")),
188
+ headers_dict.items()
189
+ ))
190
+
191
+ outgoing_request = OutgoingRequest(Fields.from_list(headers))
192
+ outgoing_request.set_method(method)
193
+ outgoing_request.set_scheme(scheme)
194
+ if url_parsed.netloc == '':
195
+ if scheme == "http":
196
+ authority = ":80"
197
+ else:
198
+ authority = ":443"
199
+ else:
200
+ authority = url_parsed.netloc
201
+
202
+ outgoing_request.set_authority(authority)
203
+
204
+ path_and_query = url_parsed.path
205
+ if url_parsed.query:
206
+ path_and_query += '?' + url_parsed.query
207
+ outgoing_request.set_path_with_query(path_and_query)
208
+
209
+ outgoing_body = request.body if request.body is not None else bytearray()
210
+ sink = Sink(outgoing_request.body())
211
+ incoming_response: IncomingResponse = (await asyncio.gather(
212
+ poll_loop.send(outgoing_request),
213
+ send_and_close(sink, outgoing_body)
214
+ ))[0]
215
+
216
+ response_body = Stream(incoming_response.consume())
217
+ body = bytearray()
218
+ while True:
219
+ chunk = await response_body.next()
220
+ if chunk is None:
221
+ simple_response = Response(
222
+ incoming_response.status(),
223
+ dict(map(
224
+ lambda pair: (pair[0], str(pair[1], "utf-8")),
225
+ incoming_response.headers().entries()
226
+ )),
227
+ bytes(body)
228
+ )
229
+ incoming_response.__exit__(None, None, None)
230
+ return simple_response
231
+ else:
232
+ body += chunk
233
+
234
+ async def send_and_close(sink: Sink, data: bytes):
235
+ await sink.send(data)
236
+ sink.close()
@@ -31,7 +31,7 @@ async def send(request: OutgoingRequest) -> IncomingResponse:
31
31
  if response is None:
32
32
  await register(cast(PollLoop, asyncio.get_event_loop()), future.subscribe())
33
33
  else:
34
- future.__exit__()
34
+ future.__exit__(None, None, None)
35
35
 
36
36
  if isinstance(response, Ok):
37
37
  if isinstance(response.value, Ok):
@@ -65,7 +65,7 @@ class Stream:
65
65
  except Err as e:
66
66
  if isinstance(e.value, StreamError_Closed):
67
67
  if self.stream is not None:
68
- self.stream.__exit__()
68
+ self.stream.__exit__(None, None, None)
69
69
  self.stream = None
70
70
  if self.body is not None:
71
71
  IncomingBody.finish(self.body)
@@ -104,7 +104,7 @@ class Sink:
104
104
  def close(self):
105
105
  """Close the stream, indicating no further data will be written."""
106
106
 
107
- self.stream.__exit__()
107
+ self.stream.__exit__(None, None, None)
108
108
  self.stream = None
109
109
  OutgoingBody.finish(self.body, None)
110
110
  self.body = None
@@ -143,7 +143,7 @@ class PollLoop(asyncio.AbstractEventLoop):
143
143
 
144
144
  for (ready, pollable), waker in zip(zip(ready, pollables), wakers):
145
145
  if ready:
146
- pollable.__exit__()
146
+ pollable.__exit__(None, None, None)
147
147
  waker.set_result(None)
148
148
  else:
149
149
  new_wakers.append((pollable, waker))
@@ -1,6 +1,7 @@
1
1
  """ Module with the bindings generated from the wit by componentize-py """
2
2
 
3
3
  from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
4
+ from types import TracebackType
4
5
  from enum import Flag, Enum, auto
5
6
  from dataclasses import dataclass
6
7
  from abc import abstractmethod
@@ -0,0 +1,46 @@
1
+ /// A keyvalue interface that provides atomic operations.
2
+ ///
3
+ /// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to
4
+ /// fail, it will appear to the invoker of the atomic operation that the action either completed
5
+ /// successfully or did nothing at all.
6
+ ///
7
+ /// Please note that this interface is bare functions that take a reference to a bucket. This is to
8
+ /// get around the current lack of a way to "extend" a resource with additional methods inside of
9
+ /// wit. Future version of the interface will instead extend these methods on the base `bucket`
10
+ /// resource.
11
+ interface atomics {
12
+ use store.{bucket, error};
13
+
14
+ /// The error returned by a CAS operation
15
+ variant cas-error {
16
+ /// A store error occurred when performing the operation
17
+ store-error(error),
18
+ /// The CAS operation failed because the value was too old. This returns a new CAS handle
19
+ /// for easy retries. Implementors MUST return a CAS handle that has been updated to the
20
+ /// latest version or transaction.
21
+ cas-failed(cas),
22
+ }
23
+
24
+ /// A handle to a CAS (compare-and-swap) operation.
25
+ resource cas {
26
+ /// Construct a new CAS operation. Implementors can map the underlying functionality
27
+ /// (transactions, versions, etc) as desired.
28
+ new: static func(bucket: borrow<bucket>, key: string) -> result<cas, error>;
29
+ /// Get the current value of the key (if it exists). This allows for avoiding reads if all
30
+ /// that is needed to ensure the atomicity of the operation
31
+ current: func() -> result<option<list<u8>>, error>;
32
+ }
33
+
34
+ /// Atomically increment the value associated with the key in the store by the given delta. It
35
+ /// returns the new value.
36
+ ///
37
+ /// If the key does not exist in the store, it creates a new key-value pair with the value set
38
+ /// to the given delta.
39
+ ///
40
+ /// If any other error occurs, it returns an `Err(error)`.
41
+ increment: func(bucket: borrow<bucket>, key: string, delta: s64) -> result<s64, error>;
42
+
43
+ /// Perform the swap on a CAS operation. This consumes the CAS handle and returns an error if
44
+ /// the CAS operation failed.
45
+ swap: func(cas: cas, value: list<u8>) -> result<_, cas-error>;
46
+ }
@@ -0,0 +1,63 @@
1
+ /// A keyvalue interface that provides batch operations.
2
+ ///
3
+ /// A batch operation is an operation that operates on multiple keys at once.
4
+ ///
5
+ /// Batch operations are useful for reducing network round-trip time. For example, if you want to
6
+ /// get the values associated with 100 keys, you can either do 100 get operations or you can do 1
7
+ /// batch get operation. The batch operation is faster because it only needs to make 1 network call
8
+ /// instead of 100.
9
+ ///
10
+ /// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some
11
+ /// of the keys may have been modified and some may not.
12
+ ///
13
+ /// This interface does has the same consistency guarantees as the `store` interface, meaning that
14
+ /// you should be able to "read your writes."
15
+ ///
16
+ /// Please note that this interface is bare functions that take a reference to a bucket. This is to
17
+ /// get around the current lack of a way to "extend" a resource with additional methods inside of
18
+ /// wit. Future version of the interface will instead extend these methods on the base `bucket`
19
+ /// resource.
20
+ interface batch {
21
+ use store.{bucket, error};
22
+
23
+ /// Get the key-value pairs associated with the keys in the store. It returns a list of
24
+ /// key-value pairs.
25
+ ///
26
+ /// If any of the keys do not exist in the store, it returns a `none` value for that pair in the
27
+ /// list.
28
+ ///
29
+ /// MAY show an out-of-date value if there are concurrent writes to the store.
30
+ ///
31
+ /// If any other error occurs, it returns an `Err(error)`.
32
+ get-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<list<tuple<string, option<list<u8>>>>, error>;
33
+
34
+ /// Set the values associated with the keys in the store. If the key already exists in the
35
+ /// store, it overwrites the value.
36
+ ///
37
+ /// Note that the key-value pairs are not guaranteed to be set in the order they are provided.
38
+ ///
39
+ /// If any of the keys do not exist in the store, it creates a new key-value pair.
40
+ ///
41
+ /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
42
+ /// rollback the key-value pairs that were already set. Thus, this batch operation does not
43
+ /// guarantee atomicity, implying that some key-value pairs could be set while others might
44
+ /// fail.
45
+ ///
46
+ /// Other concurrent operations may also be able to see the partial results.
47
+ set-many: func(bucket: borrow<bucket>, key-values: list<tuple<string, list<u8>>>) -> result<_, error>;
48
+
49
+ /// Delete the key-value pairs associated with the keys in the store.
50
+ ///
51
+ /// Note that the key-value pairs are not guaranteed to be deleted in the order they are
52
+ /// provided.
53
+ ///
54
+ /// If any of the keys do not exist in the store, it skips the key.
55
+ ///
56
+ /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
57
+ /// rollback the key-value pairs that were already deleted. Thus, this batch operation does not
58
+ /// guarantee atomicity, implying that some key-value pairs could be deleted while others might
59
+ /// fail.
60
+ ///
61
+ /// Other concurrent operations may also be able to see the partial results.
62
+ delete-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<_, error>;
63
+ }
@@ -0,0 +1,122 @@
1
+ /// A keyvalue interface that provides eventually consistent key-value operations.
2
+ ///
3
+ /// Each of these operations acts on a single key-value pair.
4
+ ///
5
+ /// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is
6
+ /// the common denominator for all data types defined by different key-value stores to handle data,
7
+ /// ensuring compatibility between different key-value stores. Note: the clients will be expecting
8
+ /// serialization/deserialization overhead to be handled by the key-value store. The value could be
9
+ /// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects.
10
+ ///
11
+ /// Data consistency in a key value store refers to the guarantee that once a write operation
12
+ /// completes, all subsequent read operations will return the value that was written.
13
+ ///
14
+ /// Any implementation of this interface must have enough consistency to guarantee "reading your
15
+ /// writes." In particular, this means that the client should never get a value that is older than
16
+ /// the one it wrote, but it MAY get a newer value if one was written around the same time. These
17
+ /// guarantees only apply to the same client (which will likely be provided by the host or an
18
+ /// external capability of some kind). In this context a "client" is referring to the caller or
19
+ /// guest that is consuming this interface. Once a write request is committed by a specific client,
20
+ /// all subsequent read requests by the same client will reflect that write or any subsequent
21
+ /// writes. Another client running in a different context may or may not immediately see the result
22
+ /// due to the replication lag. As an example of all of this, if a value at a given key is A, and
23
+ /// the client writes B, then immediately reads, it should get B. If something else writes C in
24
+ /// quick succession, then the client may get C. However, a client running in a separate context may
25
+ /// still see A or B
26
+ interface store {
27
+ /// The set of errors which may be raised by functions in this package
28
+ variant error {
29
+ /// The host does not recognize the store identifier requested.
30
+ no-such-store,
31
+
32
+ /// The requesting component does not have access to the specified store
33
+ /// (which may or may not exist).
34
+ access-denied,
35
+
36
+ /// Some implementation-specific error has occurred (e.g. I/O)
37
+ other(string)
38
+ }
39
+
40
+ /// A response to a `list-keys` operation.
41
+ record key-response {
42
+ /// The list of keys returned by the query.
43
+ keys: list<string>,
44
+ /// The continuation token to use to fetch the next page of keys. If this is `null`, then
45
+ /// there are no more keys to fetch.
46
+ cursor: option<string>
47
+ }
48
+
49
+ /// Get the bucket with the specified identifier.
50
+ ///
51
+ /// `identifier` must refer to a bucket provided by the host.
52
+ ///
53
+ /// `error::no-such-store` will be raised if the `identifier` is not recognized.
54
+ open: func(identifier: string) -> result<bucket, error>;
55
+
56
+ /// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the
57
+ /// bucket, and the bucket itself acts as a collection of all these entries.
58
+ ///
59
+ /// It is worth noting that the exact terminology for bucket in key-value stores can very
60
+ /// depending on the specific implementation. For example:
61
+ ///
62
+ /// 1. Amazon DynamoDB calls a collection of key-value pairs a table
63
+ /// 2. Redis has hashes, sets, and sorted sets as different types of collections
64
+ /// 3. Cassandra calls a collection of key-value pairs a column family
65
+ /// 4. MongoDB calls a collection of key-value pairs a collection
66
+ /// 5. Riak calls a collection of key-value pairs a bucket
67
+ /// 6. Memcached calls a collection of key-value pairs a slab
68
+ /// 7. Azure Cosmos DB calls a collection of key-value pairs a container
69
+ ///
70
+ /// In this interface, we use the term `bucket` to refer to a collection of key-value pairs
71
+ resource bucket {
72
+ /// Get the value associated with the specified `key`
73
+ ///
74
+ /// The value is returned as an option. If the key-value pair exists in the
75
+ /// store, it returns `Ok(value)`. If the key does not exist in the
76
+ /// store, it returns `Ok(none)`.
77
+ ///
78
+ /// If any other error occurs, it returns an `Err(error)`.
79
+ get: func(key: string) -> result<option<list<u8>>, error>;
80
+
81
+ /// Set the value associated with the key in the store. If the key already
82
+ /// exists in the store, it overwrites the value.
83
+ ///
84
+ /// If the key does not exist in the store, it creates a new key-value pair.
85
+ ///
86
+ /// If any other error occurs, it returns an `Err(error)`.
87
+ set: func(key: string, value: list<u8>) -> result<_, error>;
88
+
89
+ /// Delete the key-value pair associated with the key in the store.
90
+ ///
91
+ /// If the key does not exist in the store, it does nothing.
92
+ ///
93
+ /// If any other error occurs, it returns an `Err(error)`.
94
+ delete: func(key: string) -> result<_, error>;
95
+
96
+ /// Check if the key exists in the store.
97
+ ///
98
+ /// If the key exists in the store, it returns `Ok(true)`. If the key does
99
+ /// not exist in the store, it returns `Ok(false)`.
100
+ ///
101
+ /// If any other error occurs, it returns an `Err(error)`.
102
+ exists: func(key: string) -> result<bool, error>;
103
+
104
+ /// Get all the keys in the store with an optional cursor (for use in pagination). It
105
+ /// returns a list of keys. Please note that for most KeyValue implementations, this is a
106
+ /// can be a very expensive operation and so it should be used judiciously. Implementations
107
+ /// can return any number of keys in a single response, but they should never attempt to
108
+ /// send more data than is reasonable (i.e. on a small edge device, this may only be a few
109
+ /// KB, while on a large machine this could be several MB). Any response should also return
110
+ /// a cursor that can be used to fetch the next page of keys. See the `key-response` record
111
+ /// for more information.
112
+ ///
113
+ /// Note that the keys are not guaranteed to be returned in any particular order.
114
+ ///
115
+ /// If the store is empty, it returns an empty list.
116
+ ///
117
+ /// MAY show an out-of-date list of keys if there are concurrent writes to the store.
118
+ ///
119
+ /// If any error occurs, it returns an `Err(error)`.
120
+ list-keys: func(cursor: option<string>) -> result<key-response, error>;
121
+ }
122
+ }
@@ -0,0 +1,16 @@
1
+ /// A keyvalue interface that provides watch operations.
2
+ ///
3
+ /// This interface is used to provide event-driven mechanisms to handle
4
+ /// keyvalue changes.
5
+ interface watcher {
6
+ /// A keyvalue interface that provides handle-watch operations.
7
+ use store.{bucket};
8
+
9
+ /// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket`
10
+ /// that can be used to interact with the store.
11
+ on-set: func(bucket: bucket, key: string, value: list<u8>);
12
+
13
+ /// Handle the `delete` event for the given bucket and key. It includes a reference to the
14
+ /// `bucket` that can be used to interact with the store.
15
+ on-delete: func(bucket: bucket, key: string);
16
+ }
@@ -0,0 +1,26 @@
1
+ package wasi: keyvalue@0.2.0-draft2;
2
+
3
+ /// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores.
4
+ /// Components targeting this world will be able to do:
5
+ ///
6
+ /// 1. CRUD (create, read, update, delete) operations on key-value stores.
7
+ /// 2. Atomic `increment` and CAS (compare-and-swap) operations.
8
+ /// 3. Batch operations that can reduce the number of round trips to the network.
9
+ world imports {
10
+ /// The `store` capability allows the component to perform eventually consistent operations on
11
+ /// the key-value store.
12
+ import store;
13
+
14
+ /// The `atomic` capability allows the component to perform atomic / `increment` and CAS
15
+ /// (compare-and-swap) operations.
16
+ import atomics;
17
+
18
+ /// The `batch` capability allows the component to perform eventually consistent batch
19
+ /// operations that can reduce the number of round trips to the network.
20
+ import batch;
21
+ }
22
+
23
+ world watch-service {
24
+ include imports;
25
+ export watcher;
26
+ }