From 0e6263fec1561fd06e186574986511665f7df4d1 Mon Sep 17 00:00:00 2001 From: Sarp Tan Doven <119648987+sarptandoven@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:58:29 -0700 Subject: [PATCH] fix: reject file path input defaults --- pkg/schema/python/inputs.go | 37 ++++++++++++++------ pkg/schema/python/parser_test.go | 60 ++++++++++++++++++++++++++++++++ pkg/schema/types.go | 29 +++++++++++++++ 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/pkg/schema/python/inputs.go b/pkg/schema/python/inputs.go index 06669f089d..f85cb07473 100644 --- a/pkg/schema/python/inputs.go +++ b/pkg/schema/python/inputs.go @@ -9,15 +9,16 @@ import ( ) type inputCallInfo struct { - Default *schema.DefaultValue - Description *string - GE *float64 - LE *float64 - MinLength *uint64 - MaxLength *uint64 - Regex *string - Choices []schema.DefaultValue - Deprecated *bool + Default *schema.DefaultValue + DefaultUnresolved bool + Description *string + GE *float64 + LE *float64 + MinLength *uint64 + MaxLength *uint64 + Regex *string + Choices []schema.DefaultValue + Deprecated *bool } type inputMethodInfo struct { @@ -216,6 +217,11 @@ func resolveInputReference(node *sitter.Node, source []byte, registry *inputRegi case "default": if val, ok := parseDefaultValue(callNode, source); ok { resolved.Default = &val + resolved.DefaultUnresolved = false + } else { + none := schema.DefaultValue{Kind: schema.DefaultNone} + resolved.Default = &none + resolved.DefaultUnresolved = true } case "description": if s, ok := parseStringLiteral(callNode, source); ok { @@ -285,6 +291,14 @@ func inputFieldWithInfo(name string, order int, inputType schema.InputType, fiel return field } +func validateInputFieldWithInfo(field schema.InputField, info inputCallInfo) error { + if info.DefaultUnresolved && field.InputType != nil && schema.InputTypeContainsFileOrPath(*field.InputType) { + return schema.WrapError(schema.ErrDefaultNotResolvable, + fmt.Sprintf("parameter '%s': Path and File defaults cannot be statically resolved", field.Name), nil) + } + return schema.ValidateInputField(field) +} + func firstParamIsSelf(params *sitter.Node, source []byte) bool { for _, child := range AllChildren(params) { if child.Type() == "identifier" { @@ -399,13 +413,13 @@ func parseTypedDefaultParameter(node *sitter.Node, source []byte, order int, ctx return schema.InputField{}, err } field := inputFieldWithInfo(name, order, inputType, fieldType, info) - return field, schema.ValidateInputField(field) + return field, validateInputFieldWithInfo(field, info) } // 2. Reference to Input() via class attribute or static method if info, ok := resolveInputReference(valNode, source, ctx.registry); ok { field := inputFieldWithInfo(name, order, inputType, fieldType, info) - return field, schema.ValidateInputField(field) + return field, validateInputFieldWithInfo(field, info) } // 3. Plain default — must be statically resolvable @@ -469,6 +483,7 @@ func parseInputCall(node *sitter.Node, source []byte, paramName string, scope mo if !ok { none := schema.DefaultValue{Kind: schema.DefaultNone} val = none + info.DefaultUnresolved = true } info.Default = &val case "default_factory": diff --git a/pkg/schema/python/parser_test.go b/pkg/schema/python/parser_test.go index 7f094cceda..7ee76986d9 100644 --- a/pkg/schema/python/parser_test.go +++ b/pkg/schema/python/parser_test.go @@ -361,6 +361,66 @@ class Predictor(BasePredictor): require.False(t, scale.IsRequired()) } +func TestPathAndFileInputsRejectDefaults(t *testing.T) { + tests := []struct { + name string + source string + wantErr schema.SchemaErrorKind + }{ + { + name: "path string default", + source: ` +from cog import BasePredictor, Input, Path + +class Predictor(BasePredictor): + def predict(self, image: Path = Input(default="image.png")) -> str: + return "ok" +`, + wantErr: schema.ErrUnsupportedType, + }, + { + name: "file string default", + source: ` +from cog import BasePredictor, File, Input + +class Predictor(BasePredictor): + def predict(self, image: File = Input(default="image.png")) -> str: + return "ok" +`, + wantErr: schema.ErrUnsupportedType, + }, + { + name: "path constructor default", + source: ` +from cog import BasePredictor, Input, Path + +class Predictor(BasePredictor): + def predict(self, image: Path = Input(default=Path("image.png"))) -> str: + return "ok" +`, + wantErr: schema.ErrDefaultNotResolvable, + }, + { + name: "path list default", + source: ` +from cog import BasePredictor, Input, Path + +class Predictor(BasePredictor): + def predict(self, images: list[Path] = Input(default=["image.png"])) -> str: + return "ok" +`, + wantErr: schema.ErrUnsupportedType, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + se := parseErr(t, tt.source, "Predictor", schema.ModePredict) + require.Equal(t, tt.wantErr, se.Kind) + }) + } +} + // --------------------------------------------------------------------------- // Optional / union inputs // --------------------------------------------------------------------------- diff --git a/pkg/schema/types.go b/pkg/schema/types.go index 38d7878f5d..c83fda5b2c 100644 --- a/pkg/schema/types.go +++ b/pkg/schema/types.go @@ -245,9 +245,38 @@ func ValidateInputField(field InputField) error { return errUnsupportedType("constraints and choices are not supported on union inputs") } } + if inputFieldContainsFileOrPath(field) && field.Default != nil && field.Default.Kind != DefaultNone { + return errUnsupportedType("defaults are not supported on Path or File inputs") + } return nil } +// InputTypeContainsFileOrPath reports whether a recursive input type contains +// a Path or File primitive. It is used by parser frontends that need to reject +// unsupported file/path defaults after annotation resolution. +func InputTypeContainsFileOrPath(inputType InputType) bool { + switch inputType.Kind { + case InputKindPrimitive: + return inputType.Primitive == TypePath || inputType.Primitive == TypeFile + case InputKindArray: + return inputType.Elem != nil && InputTypeContainsFileOrPath(*inputType.Elem) + case InputKindUnion: + for _, variant := range inputType.Variants { + if InputTypeContainsFileOrPath(variant) { + return true + } + } + } + return false +} + +func inputFieldContainsFileOrPath(field InputField) bool { + if field.InputType != nil { + return InputTypeContainsFileOrPath(*field.InputType) + } + return field.FieldType.Primitive == TypePath || field.FieldType.Primitive == TypeFile +} + // PredictorInfo is the top-level extraction result. type PredictorInfo struct { Inputs *OrderedMap[string, InputField]