Skip to content

Commit 11f49eb

Browse files
committed
fix: recurse through nested JSON file inputs
1 parent bba08da commit 11f49eb

3 files changed

Lines changed: 118 additions & 21 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Test `--json` with list[Path] input
2+
3+
cog build -t $TEST_IMAGE
4+
cog predict $TEST_IMAGE --json '{"paths": ["@1.txt", "@2.txt"]}'
5+
stdout '"status": "succeeded"'
6+
stdout '"output": "test1test2"'
7+
8+
-- cog.yaml --
9+
build:
10+
python_version: "3.12"
11+
predict: "predict.py:Predictor"
12+
13+
-- predict.py --
14+
from cog import BasePredictor, Path
15+
16+
17+
class Predictor(BasePredictor):
18+
def predict(self, paths: list[Path]) -> str:
19+
output_parts = []
20+
for path in paths:
21+
with open(path) as f:
22+
output_parts.append(f.read())
23+
return "".join(output_parts)
24+
25+
-- 1.txt --
26+
test1
27+
-- 2.txt --
28+
test2

pkg/cli/predict.go

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -159,33 +159,59 @@ func transformPathsToBase64URLs(inputs map[string]any) (map[string]any, error) {
159159
result := make(map[string]any)
160160

161161
for key, value := range inputs {
162-
if strValue, ok := value.(string); ok && strings.HasPrefix(strValue, "@") {
163-
// This is a file path, convert to base64 data URL
164-
filePath := strValue[1:]
162+
transformed, err := transformJSONValuePathsToBase64URLs(value)
163+
if err != nil {
164+
return nil, err
165+
}
166+
result[key] = transformed
167+
}
165168

166-
// Read file
167-
data, err := os.ReadFile(filePath)
168-
if err != nil {
169-
return nil, fmt.Errorf("Failed to read file %q: %w", filePath, err)
170-
}
169+
return result, nil
170+
}
171171

172-
// Get MIME type
173-
mimeType := mime.TypeByExtension(filepath.Ext(filePath))
174-
if mimeType == "" {
175-
mimeType = "application/octet-stream"
176-
}
172+
func transformJSONValuePathsToBase64URLs(value any) (any, error) {
173+
switch v := value.(type) {
174+
case string:
175+
if !strings.HasPrefix(v, "@") {
176+
return v, nil
177+
}
177178

178-
// Create base64 data URL
179-
base64Data := base64.StdEncoding.EncodeToString(data)
180-
dataURL := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
179+
filePath := v[1:]
180+
data, err := os.ReadFile(filePath)
181+
if err != nil {
182+
return nil, fmt.Errorf("Failed to read file %q: %w", filePath, err)
183+
}
181184

182-
result[key] = dataURL
183-
} else {
184-
result[key] = value
185+
mimeType := mime.TypeByExtension(filepath.Ext(filePath))
186+
if mimeType == "" {
187+
mimeType = "application/octet-stream"
185188
}
186-
}
187189

188-
return result, nil
190+
base64Data := base64.StdEncoding.EncodeToString(data)
191+
return fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data), nil
192+
case []any:
193+
out := make([]any, len(v))
194+
for i, item := range v {
195+
transformed, err := transformJSONValuePathsToBase64URLs(item)
196+
if err != nil {
197+
return nil, err
198+
}
199+
out[i] = transformed
200+
}
201+
return out, nil
202+
case map[string]any:
203+
out := make(map[string]any, len(v))
204+
for key, item := range v {
205+
transformed, err := transformJSONValuePathsToBase64URLs(item)
206+
if err != nil {
207+
return nil, err
208+
}
209+
out[key] = transformed
210+
}
211+
return out, nil
212+
default:
213+
return value, nil
214+
}
189215
}
190216

191217
func cmdPredict(cmd *cobra.Command, args []string) error {

pkg/cli/predict_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cli
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
57

68
"github.com/getkin/kin-openapi/openapi3"
@@ -169,3 +171,44 @@ func TestExtractOutputSchemaFromValidSchema(t *testing.T) {
169171
require.NotNil(t, outputSchema, "expected non-nil output schema for valid input")
170172
require.Contains(t, outputSchema.Type.Slice(), "string", "expected string type")
171173
}
174+
175+
func TestTransformPathsToBase64URLsRecursesIntoNestedJSON(t *testing.T) {
176+
dir := t.TempDir()
177+
fileA := filepath.Join(dir, "a.txt")
178+
fileB := filepath.Join(dir, "b.txt")
179+
fileC := filepath.Join(dir, "c.txt")
180+
require.NoError(t, os.WriteFile(fileA, []byte("alpha"), 0o644))
181+
require.NoError(t, os.WriteFile(fileB, []byte("beta"), 0o644))
182+
require.NoError(t, os.WriteFile(fileC, []byte("gamma"), 0o644))
183+
184+
inputs := map[string]any{
185+
"single": "@" + fileA,
186+
"files": []any{
187+
"@" + fileB,
188+
map[string]any{
189+
"inner": "@" + fileC,
190+
},
191+
},
192+
"count": float64(3),
193+
"plain": "hello",
194+
}
195+
196+
transformed, err := transformPathsToBase64URLs(inputs)
197+
require.NoError(t, err)
198+
199+
require.IsType(t, "", transformed["single"])
200+
require.Contains(t, transformed["single"].(string), "data:text/plain;base64,")
201+
202+
files, ok := transformed["files"].([]any)
203+
require.True(t, ok)
204+
require.IsType(t, "", files[0])
205+
require.Contains(t, files[0].(string), "data:text/plain;base64,")
206+
207+
innerObj, ok := files[1].(map[string]any)
208+
require.True(t, ok)
209+
require.IsType(t, "", innerObj["inner"])
210+
require.Contains(t, innerObj["inner"].(string), "data:text/plain;base64,")
211+
212+
require.Equal(t, float64(3), transformed["count"])
213+
require.Equal(t, "hello", transformed["plain"])
214+
}

0 commit comments

Comments
 (0)