Setting the file. One moment.
Upload To Stitch · Stitch::upload To Stitch · google-labs-code/stitch-skills · Skills Docs
ContentsBack to the top of the page scripts/upload_to_stitch.py
scripts/ upload_to_stitch.py
Python · 268 lines · 7 KB
15
16 SUPPORTED FILE TYPES:
17 - Images: .png, .jpg, .jpeg, .webp
18 - HTML: .html, .htm
19 - Markdown: .md
20
21 Usage:
22 python3 upload_to_stitch.py \
23 --project-id <PROJECT_ID> \
24 --file-path <PATH_TO_FILE> \
25 [--api-url <STITCH_API_BASE_URL>] \
26 [--api-key <API_KEY>] \
27 [--title <SCREEN_TITLE>] \
28 [--generated-by <GENERATED_BY>] \
29 [--create-screen-instances]
30 """
31
32 import argparse
33 import base64
34 import json
35 import pathlib
36 import sys
37 from typing import Any
38 import urllib.request
39
40 try :
41 import ssl
42 import certifi
43 _SSL_CONTEXT = ssl.create_default_context( cafile = certifi.where())
44 except ImportError :
45 _SSL_CONTEXT = None
46
47
48 # Maps file extensions to MIME types.
49 _MIME_TYPES = {
50 ".png" : "image/png" ,
51 ".jpg" : "image/jpeg" ,
52 ".jpeg" : "image/jpeg" ,
53 ".webp" : "image/webp" ,
54 ".html" : "text/html" ,
55 ".htm" : "text/html" ,
56 ".md" : "text/markdown" ,
57 }
58
59
60 def encode_file (path: pathlib.Path) -> str :
61 """Read and base64-encode a file."""
62 with open (path, "rb" ) as f:
63 return base64.b64encode(f.read()).decode( "utf-8" )
64
65
66 def call_batch_create_screens (
67 api_url: str ,
68 api_key: str ,
69 project_id: str ,
70 requests: list[dict[ str , Any]],
71 create_screen_instances: bool = False ,
72 urlopen: Any = urllib.request.urlopen,
73 ) -> dict[ str , Any]:
74 """Call BatchCreateScreens REST API directly.
75
76 Endpoint: POST /v1/{parent=projects/*}/screens:batchCreate
77
78 Args:
79 api_url: Base URL of the Stitch API (e.g. https://stitch.googleapis.com).
80 api_key: API key for authentication.
81 project_id: The Stitch project ID.
82 requests: List of CreateScreenRequest dicts, each containing a screen.
83 create_screen_instances: Whether to create screen instances for display.
84 urlopen: The urlopen function to use (for testing).
85
86 Returns:
87 Parsed JSON response dict.
88 """
89 url = f " { api_url.rstrip( '/' ) } /v1/projects/ { project_id } /screens:batchCreate"
90
91 payload = {
92 "parent" : f "projects/ { project_id } " ,
93 "requests" : requests,
94 "createScreenInstances" : create_screen_instances,
95 }
96
97 data = json.dumps(payload).encode( "utf-8" )
98 req = urllib.request.Request(
99 url,
100 data = data,
101 headers = {
102 "Content-Type" : "application/json" ,
103 "X-Goog-Api-Key" : api_key,
104 },
105 method = "POST" ,
106 )
107
108 try :
109 print ( "Calling urlopen..." )
110 urlopen_kwargs = { "timeout" : 120 }
111 if _SSL_CONTEXT is not None :
112 urlopen_kwargs[ "context" ] = _SSL_CONTEXT
113 with urlopen(req, ** urlopen_kwargs) as resp:
114 print ( f "urlopen returned. Status: { resp.getcode() } " )
115 body = resp.read().decode( "utf-8" )
116 print ( f "Response status: { resp.getcode() } " )
117 print ( f "Response body (first 1000 chars): { body[: 1000 ] } " )
118 if not body:
119 print ( "Error: Empty response body" )
120 sys.exit( 1 )
121 return json.loads(body)
122 except urllib.error.HTTPError as e:
123 error_body = e.read().decode( "utf-8" )
124 print ( f "HTTP Error { e.code } : { e.reason } " )
125 print ( f "Response: { error_body } " )
126 sys.exit( 1 )
127
128
129 def build_screen_request (
130 mime_type: str ,
131 b64_data: str ,
132 title: str | None = None ,
133 generated_by: str | None = None ,
134 ) -> dict[ str , Any]:
135 """Build a CreateScreenRequest dict from a file.
136
137 For images, the file is set as the screenshot.
138 For HTML, the file is set as the html_code.
139
140 Args:
141 mime_type: The MIME type of the file.
142 b64_data: Base64-encoded file content.
143 title: Optional title for the screen.
144 generated_by: Optional value for the generatedBy field (HTML/markdown only).
145
146 Returns:
147 A CreateScreenRequest-shaped dict.
148 """
149 file_obj = {
150 "fileContentBase64" : b64_data,
151 "mimeType" : mime_type,
152 }
153
154 if mime_type in ( "text/html" , "text/markdown" ):
155 screen = {
156 "htmlCode" : file_obj,
157 "screenType" : "DOCUMENT" ,
158 "isCreatedByClient" : True ,
159 }
160 if not generated_by:
161 if mime_type == "text/markdown" :
162 generated_by = "UserUploadedDesignMd"
163 elif mime_type == "text/html" :
164 generated_by = "UserUploadedHtml"
165 if generated_by:
166 screen[ "generatedBy" ] = generated_by
167 else :
168 screen = {
169 "screenshot" : file_obj,
170 "screenType" : "IMAGE" ,
171 "isCreatedByClient" : True ,
172 }
173
174 if title:
175 screen[ "title" ] = title
176
177 return { "screen" : screen}
178
179
180 def parse_args ():
181 """Parse command-line arguments."""
182 parser = argparse.ArgumentParser(
183 description = "Upload a file to a Stitch project via BatchCreateScreens."
184 )
185 parser.add_argument( "--project-id" , required = True , help = "Stitch project ID" )
186 parser.add_argument(
187 "--file-path" ,
188 required = True ,
189 type = pathlib.Path,
190 help = (
191 "Path to the file to upload. Supported types:"
192 f " { ', ' .join( sorted ( _MIME_TYPES .keys())) } "
193 ),
194 )
195 parser.add_argument(
196 "--api-url" ,
197 default = "https://stitch.googleapis.com" ,
198 help = "Stitch API base URL. Defaults to https://stitch.googleapis.com." ,
199 )
200 parser.add_argument(
201 "--api-key" ,
202 required = True ,
203 help = "API key for the Stitch API." ,
204 )
205 parser.add_argument(
206 "--title" ,
207 default = None ,
208 help = "Optional title for the created screen" ,
209 )
210 parser.add_argument(
211 "--generated-by" ,
212 default = None ,
213 help = (
214 "Value for the generatedBy field in the screen proto"
215 " (HTML/markdown uploads only)."
216 ),
217 )
218 return parser.parse_args()
219
220
221 def main ():
222 args = parse_args()
223
224 file_path = args.file_path
225 file_suffix = file_path.suffix.lower()
226 mime_type = _MIME_TYPES .get(file_suffix)
227
228 if mime_type is None :
229 print (
230 f "Error: Unsupported file type ' { file_suffix } '. Supported types:"
231 f " { ', ' .join( sorted ( _MIME_TYPES .keys())) } "
232 )
233 sys.exit( 1 )
234
235 if not file_path.exists():
236 print ( f "Error: File not found: { file_path } " )
237 sys.exit( 1 )
238
239 if args.generated_by and mime_type not in ( "text/html" , "text/markdown" ):
240 print ( "Warning: --generated-by is ignored for image uploads." )
241
242 print ( f "File: { file_path } " )
243 print ( f "MIME type: { mime_type } " )
244
245 b64_data = encode_file(file_path)
246 print ( f "Base64: { len (b64_data) } chars" )
247
248 screen_request = build_screen_request(
249 mime_type, b64_data, title = args.title, generated_by = args.generated_by,
250 )
251
252 print ( f " \n Uploading to project: { args.project_id } " )
253 print ( f "API URL: { args.api_url } " )
254
255 result = call_batch_create_screens(
256 api_url = args.api_url,
257 api_key = args.api_key,
258 project_id = args.project_id,
259 requests = [screen_request],
260 create_screen_instances = True ,
261 )
262
263 print ( " \n Response:" )
264 print (json.dumps(result, indent = 2 ))
265
266
267 if __name__ == "__main__" :
268 main()