@palettelab/cli 0.3.45 → 0.3.46
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.
- package/README.md +2 -0
- package/backend-sdk/palette_sdk/storage.py +70 -29
- package/docs/python-backend-sdk.md +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,6 +328,8 @@ async def sync_invoices(ctx: PluginContext = Depends(get_plugin_context)):
|
|
|
328
328
|
return {"room": room, "folder": folder, "bytes": len(content or b"")}
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
+
Storage upload responses include both snake_case and camelCase aliases for object metadata (`object_path`/`objectPath`, `file_url`/`fileUrl`, `content_type`/`contentType`) so local simulator and hosted OS responses can be consumed with the same app code.
|
|
332
|
+
|
|
331
333
|
App storage is different from Data Room uploads. Use `ctx.storage` or `palette.storage` for app-owned files written directly to the OS-configured storage backend, currently GCS in hosted environments. Use `ctx.data_rooms` / `palette.dataRooms` only when the file should be managed as a Data Room document.
|
|
332
334
|
|
|
333
335
|
Python backend app-storage example:
|
|
@@ -83,6 +83,47 @@ def _content_type(filename: str | None, content_type: str | None) -> str:
|
|
|
83
83
|
return browser_ct
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _storage_metadata(
|
|
87
|
+
*,
|
|
88
|
+
bucket: str,
|
|
89
|
+
object_path: str,
|
|
90
|
+
file_url: str,
|
|
91
|
+
content_type: str,
|
|
92
|
+
size: int | None,
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
return {
|
|
95
|
+
"bucket": bucket,
|
|
96
|
+
"object_path": object_path,
|
|
97
|
+
"objectPath": object_path,
|
|
98
|
+
"file_url": file_url,
|
|
99
|
+
"fileUrl": file_url,
|
|
100
|
+
"content_type": content_type,
|
|
101
|
+
"contentType": content_type,
|
|
102
|
+
"size": size,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _resumable_metadata(
|
|
107
|
+
*,
|
|
108
|
+
bucket: str,
|
|
109
|
+
object_path: str,
|
|
110
|
+
file_url: str,
|
|
111
|
+
upload_url: str | None,
|
|
112
|
+
content_type: str,
|
|
113
|
+
size: int | None,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
payload = _storage_metadata(
|
|
116
|
+
bucket=bucket,
|
|
117
|
+
object_path=object_path,
|
|
118
|
+
file_url=file_url,
|
|
119
|
+
content_type=content_type,
|
|
120
|
+
size=size,
|
|
121
|
+
)
|
|
122
|
+
payload["upload_url"] = upload_url
|
|
123
|
+
payload["uploadUrl"] = upload_url
|
|
124
|
+
return payload
|
|
125
|
+
|
|
126
|
+
|
|
86
127
|
@dataclass
|
|
87
128
|
class LocalStorageService:
|
|
88
129
|
"""Filesystem-backed ctx.storage implementation used by local SDK dev."""
|
|
@@ -122,13 +163,13 @@ class LocalStorageService:
|
|
|
122
163
|
target = self._target(object_path)
|
|
123
164
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
124
165
|
target.write_bytes(content)
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
166
|
+
return _storage_metadata(
|
|
167
|
+
bucket="local",
|
|
168
|
+
object_path=object_path,
|
|
169
|
+
file_url=target.as_uri(),
|
|
170
|
+
content_type=_content_type(filename, content_type),
|
|
171
|
+
size=len(content),
|
|
172
|
+
)
|
|
132
173
|
|
|
133
174
|
async def upload_file_stream(
|
|
134
175
|
self,
|
|
@@ -168,13 +209,13 @@ class LocalStorageService:
|
|
|
168
209
|
chunk_count=chunk_count,
|
|
169
210
|
state="complete",
|
|
170
211
|
)
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
return _storage_metadata(
|
|
213
|
+
bucket="local",
|
|
214
|
+
object_path=object_path,
|
|
215
|
+
file_url=target.as_uri(),
|
|
216
|
+
content_type=_content_type(filename, content_type),
|
|
217
|
+
size=0,
|
|
218
|
+
)
|
|
178
219
|
with target.open("wb") as out:
|
|
179
220
|
chunk_index = 0
|
|
180
221
|
while True:
|
|
@@ -194,13 +235,13 @@ class LocalStorageService:
|
|
|
194
235
|
)
|
|
195
236
|
if loaded != total:
|
|
196
237
|
raise RuntimeError(f"storage upload incomplete: uploaded {loaded} of {total} bytes")
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
238
|
+
return _storage_metadata(
|
|
239
|
+
bucket="local",
|
|
240
|
+
object_path=object_path,
|
|
241
|
+
file_url=target.as_uri(),
|
|
242
|
+
content_type=_content_type(filename, content_type),
|
|
243
|
+
size=loaded,
|
|
244
|
+
)
|
|
204
245
|
|
|
205
246
|
async def upload_file_path(
|
|
206
247
|
self,
|
|
@@ -234,11 +275,11 @@ class LocalStorageService:
|
|
|
234
275
|
key: str | None = None,
|
|
235
276
|
) -> dict[str, Any]:
|
|
236
277
|
object_path = self.object_path(filename, key=key)
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
278
|
+
return _resumable_metadata(
|
|
279
|
+
bucket="local",
|
|
280
|
+
object_path=object_path,
|
|
281
|
+
file_url=self._target(object_path).as_uri(),
|
|
282
|
+
upload_url=None,
|
|
283
|
+
content_type=_content_type(filename, content_type),
|
|
284
|
+
size=size,
|
|
285
|
+
)
|
|
@@ -1044,3 +1044,5 @@ async def create_note(
|
|
|
1044
1044
|
|
|
1045
1045
|
This route stores app-owned data in the app database and writes a related file
|
|
1046
1046
|
into Palette Data Rooms from the Python backend.
|
|
1047
|
+
|
|
1048
|
+
Backend storage responses include both snake_case and camelCase aliases for object metadata (`object_path`/`objectPath`, `file_url`/`fileUrl`, `content_type`/`contentType`). The concrete values differ by environment, but the key contract is stable across local dev and hosted OS.
|