Skip to content

Commit 014a5bf

Browse files
Leeon-Tangclaude
andcommitted
Refactor: Align API with Python SDK using functional options pattern
BREAKING CHANGE: API signatures have changed to use functional options Before: output, _ := wavespeed.Run(model, input, 0, 0, false, 0) url, _ := wavespeed.Upload(file, 0) After: output, _ := wavespeed.Run(model, input) output, _ := wavespeed.Run(model, input, wavespeed.WithSyncMode(true)) url, _ := wavespeed.Upload(file) url, _ := wavespeed.Upload(file, wavespeed.WithUploadTimeout(30)) Changes: - Add functional options pattern (WithTimeout, WithSyncMode, etc.) - Simplify API to match Python SDK usage - All parameters now optional with sensible defaults - Update all tests to use new API - Update README with new examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 8566f0a commit 014a5bf

5 files changed

Lines changed: 166 additions & 60 deletions

File tree

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import "github.com/WaveSpeedAI/wavespeed-go"
3434
output, err := wavespeed.Run(
3535
"wavespeed-ai/z-image/turbo",
3636
map[string]any{"prompt": "Cat"},
37-
0, 0, false, 0,
3837
)
3938
if err != nil {
4039
log.Fatal(err)
@@ -60,34 +59,35 @@ client := api.NewClient("your-api-key", "", 0, 0, 0, 0)
6059
output, err := client.Run(
6160
"wavespeed-ai/z-image/turbo",
6261
map[string]any{"prompt": "Cat"},
63-
0, 0, false, 0,
6462
)
6563
```
6664

6765
### Options
6866

67+
All parameters are optional. Use option functions to customize behavior:
68+
6969
```go
7070
output, err := wavespeed.Run(
7171
"wavespeed-ai/z-image/turbo",
7272
map[string]any{"prompt": "Cat"},
73-
36000.0, // timeout: Max wait time in seconds (default: 36000.0)
74-
1.0, // pollInterval: Status check interval (default: 1.0)
75-
false, // enableSyncMode: Single request mode, no polling (default: false)
76-
0, // maxRetries: Maximum retries (default: 0)
73+
wavespeed.WithTimeout(60), // Max wait time in seconds
74+
wavespeed.WithPollInterval(1.0), // Status check interval
75+
wavespeed.WithSyncMode(true), // Enable synchronous mode
76+
wavespeed.WithMaxRetries(3), // Maximum task retries
7777
)
7878
```
7979

8080
### Sync Mode
8181

82-
Use `enableSyncMode=true` for a single request that waits for the result (no polling).
82+
Use `WithSyncMode(true)` for a single request that waits for the result (no polling).
8383

8484
> **Note:** Not all models support sync mode. Check the model documentation for availability.
8585
8686
```go
8787
output, err := wavespeed.Run(
8888
"wavespeed-ai/z-image/turbo",
8989
map[string]any{"prompt": "Cat"},
90-
0, 0, true, 0, // enableSyncMode=true
90+
wavespeed.WithSyncMode(true),
9191
)
9292
```
9393

@@ -115,11 +115,15 @@ Upload images, videos, or audio files:
115115
```go
116116
import "github.com/WaveSpeedAI/wavespeed-go"
117117

118-
url, err := wavespeed.Upload("/path/to/image.png", 0)
118+
// Simple upload
119+
url, err := wavespeed.Upload("/path/to/image.png")
119120
if err != nil {
120121
log.Fatal(err)
121122
}
122123
fmt.Println(url)
124+
125+
// With timeout
126+
url, err := wavespeed.Upload("/path/to/image.png", wavespeed.WithUploadTimeout(30))
123127
```
124128

125129
## Running Tests

api/api.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,48 @@ func getDefaultClient() *Client {
1414
// Args:
1515
// - model: Model identifier (e.g., "wavespeed-ai/flux-dev").
1616
// - input: Input parameters for the model.
17-
// - timeout: Maximum time to wait for completion (0 = no timeout).
18-
// - pollInterval: Interval between status checks in seconds.
19-
// - enableSyncMode: If true, use synchronous mode (single request).
20-
// - maxRetries: Maximum retries for this request (overrides default setting).
17+
// - opts: Optional parameters (WithTimeout, WithSyncMode, etc.)
2118
//
2219
// Returns:
2320
// - map[string]any containing "outputs" array with model outputs.
2421
//
2522
// Example:
2623
//
27-
// output := api.Run(
24+
// // Simple usage
25+
// output, _ := api.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "Cat"})
26+
// fmt.Println(output["outputs"].([]any)[0])
27+
//
28+
// // With options
29+
// output, _ := api.Run(
2830
// "wavespeed-ai/z-image/turbo",
2931
// map[string]any{"prompt": "Cat"},
30-
// 0, 1.0, false, 0,
32+
// api.WithSyncMode(true),
33+
// api.WithTimeout(60),
3134
// )
32-
// fmt.Println(output["outputs"].([]any)[0])
33-
func Run(model string, input map[string]any, timeout float64, pollInterval float64, enableSyncMode bool, maxRetries int) (map[string]any, error) {
34-
return getDefaultClient().Run(model, input, timeout, pollInterval, enableSyncMode, maxRetries)
35+
func Run(model string, input map[string]any, opts ...RunOption) (map[string]any, error) {
36+
return getDefaultClient().Run(model, input, opts...)
3537
}
3638

3739
// Upload uploads a file to WaveSpeed.
3840
//
3941
// Args:
4042
// - file: File path string to upload.
41-
// - timeout: Total API call timeout in seconds.
43+
// - opts: Optional upload options (WithUploadTimeout, etc.)
4244
//
4345
// Returns:
4446
// - URL of the uploaded file.
4547
//
4648
// Example:
4749
//
48-
// url, err := api.Upload("/path/to/image.png", 0)
50+
// // Simple usage
51+
// url, err := api.Upload("/path/to/image.png")
4952
// if err != nil {
5053
// log.Fatal(err)
5154
// }
5255
// fmt.Println(url)
53-
func Upload(file string, timeout float64) (string, error) {
54-
return getDefaultClient().Upload(file, timeout)
56+
//
57+
// // With timeout
58+
// url, err := api.Upload("/path/to/image.png", api.WithUploadTimeout(30))
59+
func Upload(file string, opts ...UploadOption) (string, error) {
60+
return getDefaultClient().Upload(file, opts...)
5561
}

api/client.go

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,60 @@ import (
1515
"time"
1616
)
1717

18+
// RunOption is a function that configures RunOptions.
19+
type RunOption func(*RunOptions)
20+
21+
// RunOptions contains optional parameters for Run.
22+
type RunOptions struct {
23+
Timeout float64
24+
PollInterval float64
25+
EnableSyncMode bool
26+
MaxRetries int
27+
}
28+
29+
// WithTimeout sets the maximum time to wait for completion.
30+
func WithTimeout(timeout float64) RunOption {
31+
return func(o *RunOptions) {
32+
o.Timeout = timeout
33+
}
34+
}
35+
36+
// WithPollInterval sets the interval between status checks.
37+
func WithPollInterval(interval float64) RunOption {
38+
return func(o *RunOptions) {
39+
o.PollInterval = interval
40+
}
41+
}
42+
43+
// WithSyncMode enables or disables synchronous mode.
44+
func WithSyncMode(enable bool) RunOption {
45+
return func(o *RunOptions) {
46+
o.EnableSyncMode = enable
47+
}
48+
}
49+
50+
// WithMaxRetries sets the maximum number of task-level retries.
51+
func WithMaxRetries(retries int) RunOption {
52+
return func(o *RunOptions) {
53+
o.MaxRetries = retries
54+
}
55+
}
56+
57+
// UploadOption is a function that configures UploadOptions.
58+
type UploadOption func(*UploadOptions)
59+
60+
// UploadOptions contains optional parameters for Upload.
61+
type UploadOptions struct {
62+
Timeout float64
63+
}
64+
65+
// WithUploadTimeout sets the timeout for file upload.
66+
func WithUploadTimeout(timeout float64) UploadOption {
67+
return func(o *UploadOptions) {
68+
o.Timeout = timeout
69+
}
70+
}
71+
1872
// Client is the WaveSpeed API client.
1973
type Client struct {
2074
apiKey string
@@ -314,18 +368,25 @@ func (c *Client) isRetryableError(err error) bool {
314368
}
315369

316370
// Run executes a model and waits for the output.
317-
func (c *Client) Run(model string, input map[string]any, timeout float64, pollInterval float64, enableSyncMode bool, maxRetries int) (map[string]any, error) {
318-
if timeout == 0 {
319-
timeout = 36000.0
371+
func (c *Client) Run(model string, input map[string]any, opts ...RunOption) (map[string]any, error) {
372+
// Apply default options
373+
options := &RunOptions{
374+
Timeout: 36000.0,
375+
PollInterval: 1.0,
376+
EnableSyncMode: false,
377+
MaxRetries: c.maxRetries,
320378
}
321-
if pollInterval == 0 {
322-
pollInterval = 1.0
323-
}
324-
taskRetries := maxRetries
325-
if taskRetries == 0 {
326-
taskRetries = c.maxRetries
379+
380+
// Apply user-provided options
381+
for _, opt := range opts {
382+
opt(options)
327383
}
328384

385+
timeout := options.Timeout
386+
pollInterval := options.PollInterval
387+
enableSyncMode := options.EnableSyncMode
388+
taskRetries := options.MaxRetries
389+
329390
var lastError error
330391

331392
for attempt := 0; attempt <= taskRetries; attempt++ {
@@ -381,19 +442,26 @@ func (c *Client) Run(model string, input map[string]any, timeout float64, pollIn
381442
}
382443

383444
// Upload uploads a file to WaveSpeed.
384-
func (c *Client) Upload(file string, timeout float64) (string, error) {
445+
func (c *Client) Upload(file string, opts ...UploadOption) (string, error) {
385446
if c.apiKey == "" {
386447
return "", errors.New("API key is required. Set WAVESPEED_API_KEY environment variable or pass api_key to Client()")
387448
}
388449

450+
// Apply default options
451+
options := &UploadOptions{
452+
Timeout: 36000.0,
453+
}
454+
455+
// Apply user-provided options
456+
for _, opt := range opts {
457+
opt(options)
458+
}
459+
389460
url := c.baseURL + "/api/v3/media/upload/binary"
390461
headers := map[string]string{
391462
"Authorization": "Bearer " + c.apiKey,
392463
}
393-
requestTimeout := timeout
394-
if requestTimeout == 0 {
395-
requestTimeout = 36000.0
396-
}
464+
requestTimeout := options.Timeout
397465

398466
if _, err := os.Stat(file); os.IsNotExist(err) {
399467
return "", fmt.Errorf("file not found: %s", file)

api/client_test.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestRunSuccess(t *testing.T) {
132132
defer server.Close()
133133

134134
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
135-
result, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0, 0.01, false, 0)
135+
result, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithPollInterval(0.01))
136136
if err != nil {
137137
t.Fatalf("run error: %v", err)
138138
}
@@ -162,7 +162,7 @@ func TestRunFailure(t *testing.T) {
162162
defer server.Close()
163163

164164
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
165-
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0, 0.01, false, 0)
165+
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithPollInterval(0.01))
166166
if err == nil {
167167
t.Fatal("expected error for failed prediction")
168168
}
@@ -206,7 +206,7 @@ func TestUploadFilePath(t *testing.T) {
206206
defer os.Remove(tmpFile)
207207

208208
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
209-
url, err := client.Upload(tmpFile, 0)
209+
url, err := client.Upload(tmpFile)
210210
if err != nil {
211211
t.Fatalf("upload error: %v", err)
212212
}
@@ -217,7 +217,7 @@ func TestUploadFilePath(t *testing.T) {
217217

218218
func TestUploadFileNotFound(t *testing.T) {
219219
client := NewClient("test-key", "", 0, 0, 0, 0)
220-
_, err := client.Upload("/nonexistent/path/to/file.png", 0)
220+
_, err := client.Upload("/nonexistent/path/to/file.png")
221221
if err == nil {
222222
t.Fatal("expected error for non-existent file")
223223
}
@@ -229,7 +229,7 @@ func TestUploadFileNotFound(t *testing.T) {
229229
func TestUploadRaisesWithoutAPIKey(t *testing.T) {
230230
client := NewClient("", "", 0, 0, 0, 0)
231231
client.apiKey = ""
232-
_, err := client.Upload("/some/file.png", 0)
232+
_, err := client.Upload("/some/file.png")
233233
if err == nil {
234234
t.Fatal("expected error when no API key provided")
235235
}
@@ -254,7 +254,7 @@ func TestUploadHTTPError(t *testing.T) {
254254
defer os.Remove(tmpFile)
255255

256256
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
257-
_, err := client.Upload(tmpFile, 0)
257+
_, err := client.Upload(tmpFile)
258258
if err == nil {
259259
t.Fatal("expected error for HTTP 500")
260260
}
@@ -279,7 +279,7 @@ func TestUploadAPIError(t *testing.T) {
279279
defer os.Remove(tmpFile)
280280

281281
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
282-
_, err := client.Upload(tmpFile, 0)
282+
_, err := client.Upload(tmpFile)
283283
if err == nil {
284284
t.Fatal("expected error for API error response")
285285
}
@@ -299,7 +299,7 @@ func TestRunSyncModeFailure(t *testing.T) {
299299
defer server.Close()
300300

301301
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
302-
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0, 0, true, 0)
302+
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithSyncMode(true))
303303
if err == nil {
304304
t.Fatal("expected error for non-completed status in sync mode")
305305
}
@@ -329,7 +329,7 @@ func TestRunTimeout(t *testing.T) {
329329
defer server.Close()
330330

331331
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
332-
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0.1, 0.01, false, 0)
332+
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithTimeout(0.1), WithPollInterval(0.01))
333333
if err == nil {
334334
t.Fatal("expected timeout error")
335335
}
@@ -366,7 +366,7 @@ func TestRunUsesDefaultClient(t *testing.T) {
366366
defaultClient = nil
367367

368368
// Use module-level Run function
369-
result, err := Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0, 0.01, false, 0)
369+
result, err := Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithPollInterval(0.01))
370370
if err != nil {
371371
// This will fail because we can't override the base URL for the default client
372372
// But we're testing that it attempts to use the default client
@@ -395,7 +395,6 @@ func TestRunRealAPI(t *testing.T) {
395395
output, err := Run(
396396
"wavespeed-ai/z-image/turbo",
397397
map[string]any{"prompt": "A simple red circle on white background"},
398-
0, 0, false, 0,
399398
)
400399
if err != nil {
401400
t.Fatalf("run error: %v", err)
@@ -450,7 +449,7 @@ func TestUploadRealAPI(t *testing.T) {
450449
}
451450
defer os.Remove(tmpFile)
452451

453-
url, err := Upload(tmpFile, 0)
452+
url, err := Upload(tmpFile)
454453
if err != nil {
455454
t.Fatalf("upload error: %v", err)
456455
}
@@ -476,7 +475,7 @@ func TestRunAllRetriesFailed(t *testing.T) {
476475
defer server.Close()
477476

478477
client := NewClient("test-key", server.URL, 0, 2, 0, 0.01) // maxRetries=2
479-
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, 0, 0.01, false, 2)
478+
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithPollInterval(0.01), WithMaxRetries(2))
480479

481480
if err == nil {
482481
t.Fatal("expected error after all retries failed")

0 commit comments

Comments
 (0)