77
88import json
99import os
10- from io import BytesIO
1110from pathlib import Path
12- from urllib .parse import quote
1311
1412from transloadit .client import Transloadit
15- from tusclient import client as tus
1613
1714
1815def required_env (name ):
@@ -35,45 +32,6 @@ def load_scenario():
3532 return json .load (scenario_file )
3633
3734
38- def read_path (value , path_parts , label ):
39- current = value
40- for part in path_parts :
41- if isinstance (current , list ) and isinstance (part , int ):
42- if part >= len (current ):
43- fail (f"{ label } path { path_parts !r} index { part } is out of range" )
44- current = current [part ]
45- continue
46-
47- if isinstance (current , dict ) and isinstance (part , str ):
48- if part not in current :
49- fail (f"{ label } path { path_parts !r} is missing key { part !r} " )
50- current = current [part ]
51- continue
52-
53- fail (f"{ label } path { path_parts !r} cannot read { part !r} from { current !r} " )
54-
55- return current
56-
57-
58- def resolve_value (value_spec , context , label ):
59- if "value" in value_spec :
60- return value_spec ["value" ]
61-
62- source = value_spec .get ("source" )
63- if not isinstance (source , dict ):
64- fail (f"{ label } value spec has no literal value or source" )
65-
66- root = source .get ("root" )
67- if root not in context :
68- fail (f"{ label } value source root { root !r} is unavailable" )
69-
70- path_parts = source .get ("path" ) or []
71- if not isinstance (path_parts , list ):
72- fail (f"{ label } value source path must be a list" )
73-
74- return read_path (context [root ], path_parts , label )
75-
76-
7735def response_data (response , operation ):
7836 data = response .data
7937 if not isinstance (data , dict ):
@@ -100,19 +58,13 @@ def feature_step(scenario, collection_name, feature_id, kind):
10058 fail (f"scenario has no { collection_name } step for feature { feature_id !r} " )
10159
10260
103- def create_assembly ( client , scenario ):
61+ def file_count ( scenario ):
10462 feature = feature_step (scenario , "preparations" , "createTusAssembly" , "feature-call" )
10563 input_values = list (feature ["input" ].values ())
10664 if len (input_values ) != 1 :
10765 fail (f"{ feature ['featureId' ]} expected exactly one input value" )
10866
109- response = client .create_tus_assembly (input_values [0 ])
110- data = response_data (response , feature ["featureId" ])
111- for required_path in feature ["requiredResponsePaths" ]:
112- value = read_path (data , required_path , feature ["featureId" ])
113- if value is None or value == "" :
114- fail (f"{ feature ['featureId' ]} returned empty value at { required_path !r} " )
115- return data
67+ return input_values [0 ]
11668
11769
11870def scenario_bytes (scenario ):
@@ -124,53 +76,14 @@ def scenario_bytes(scenario):
12476 return source ["value" ].encode ("utf-8" )
12577
12678
127- def upload_metadata (scenario , create_response ):
128- context = {"createResponse" : create_response , "scenario" : scenario }
129- metadata = {}
130- for field in scenario ["upload" ]["metadata" ]:
131- metadata [field ["name" ]] = str (resolve_value (field ["value" ], context , field ["name" ]))
132- return metadata
133-
134-
135- def upload_with_tus (scenario , create_response ):
136- context = {"createResponse" : create_response , "scenario" : scenario }
137- endpoint_url = str (resolve_value (scenario ["upload" ]["tusUrl" ], context , "tusUrl" ))
138- content = scenario_bytes (scenario )
139- chunk_size = len (content ) if scenario ["upload" ]["chunkSize" ] == "full-file" else None
140- if chunk_size is None :
141- fail (f"unsupported chunk size policy { scenario ['upload' ]['chunkSize' ]!r} " )
142-
143- uploader = tus .TusClient (endpoint_url ).uploader (
144- file_stream = BytesIO (content ),
145- chunk_size = chunk_size ,
146- metadata = upload_metadata (scenario , create_response ),
147- retries = scenario ["upload" ]["retries" ],
148- )
149- uploader .upload ()
150- if not uploader .url :
151- fail ("TUS upload did not expose an upload URL" )
152- if uploader .offset != len (content ):
153- fail (f"TUS upload offset { uploader .offset } , expected { len (content )} " )
154- return uploader .url
155-
156-
157- def render_path_template (template_config , context , label ):
158- rendered = template_config ["template" ]
159- for name , value_spec in template_config ["replacements" ].items ():
160- value = resolve_value (value_spec , context , f"{ label } .{ name } " )
161- rendered = rendered .replace ("{" + name + "}" , quote (str (value ), safe = "" ))
162-
163- if "{" in rendered or "}" in rendered :
164- fail (f"{ label } still has unresolved placeholders: { rendered } " )
165-
166- return rendered
167-
168-
169- def wait_for_assembly (client , scenario , create_response ):
170- feature = feature_step (scenario , "observations" , "waitForAssembly" , "feature-poll" )
171- context = {"createResponse" : create_response , "scenario" : scenario }
172- wait_input = render_path_template (feature ["input" ], context , feature ["featureId" ])
173- return response_data (client .wait_for_assembly (wait_input ), feature ["featureId" ])
79+ def upload_config (scenario ):
80+ upload = scenario ["upload" ]
81+ return {
82+ "content" : scenario_bytes (scenario ),
83+ "fieldname" : upload ["fieldName" ],
84+ "filename" : upload ["fileName" ],
85+ "user_meta" : upload .get ("userMeta" ) or {},
86+ }
17487
17588
17689def write_result (create_response , status , upload_url ):
@@ -200,10 +113,16 @@ def main():
200113 service = endpoint ,
201114 )
202115
203- create_response = create_assembly (client , scenario )
204- upload_url = upload_with_tus (scenario , create_response )
205- status = wait_for_assembly (client , scenario , create_response )
206- write_result (create_response , status , upload_url )
116+ upload = upload_config (scenario )
117+ completed_assembly , upload_url = client .upload_tus_assembly (
118+ file_count (scenario ),
119+ upload ["content" ],
120+ upload ["fieldname" ],
121+ upload ["filename" ],
122+ upload ["user_meta" ],
123+ )
124+ status = response_data (completed_assembly , "uploadTusAssembly" )
125+ write_result (status , status , upload_url )
207126
208127 print (f"Python Transloadit SDK devdock scenario { scenario ['scenarioId' ]} passed for { endpoint } " )
209128
0 commit comments