Skip to content

Commit d8d71aa

Browse files
Leeon-Tangclaude
andcommitted
refactor: Improve client initialization with functional options pattern
- Replace positional parameters with functional options in NewClient - Add ClientOption type and With* functions for all configuration - Update README examples to align with Python SDK style - Update all tests to use new API - Eliminates redundant zero values (no more "0, 0, 0, 0") - Provides self-documenting, flexible configuration Breaking change: NewClient signature changed from: NewClient(apiKey, baseURL, timeout, retries, connRetries, interval) To: NewClient(opts ...ClientOption) Migration example: Old: NewClient("key", "", 0, 3, 5, 1.0) New: NewClient(WithAPIKey("key"), WithClientMaxRetries(3)) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 014a5bf commit d8d71aa

4 files changed

Lines changed: 116 additions & 56 deletions

File tree

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Or pass it directly:
5555
```go
5656
import "github.com/WaveSpeedAI/wavespeed-go/api"
5757

58-
client := api.NewClient("your-api-key", "", 0, 0, 0, 0)
58+
client := api.NewClient(api.WithAPIKey("your-api-key"))
5959
output, err := client.Run(
6060
"wavespeed-ai/z-image/turbo",
6161
map[string]any{"prompt": "Cat"},
@@ -99,12 +99,10 @@ Configure retries at the client level:
9999
import "github.com/WaveSpeedAI/wavespeed-go/api"
100100

101101
client := api.NewClient(
102-
"your-api-key",
103-
"", // baseURL (empty = default)
104-
10.0, // connectionTimeout in seconds (default: 10.0)
105-
0, // maxRetries: Task-level retries (default: 0)
106-
5, // maxConnectionRetries: HTTP connection retries (default: 5)
107-
1.0, // retryInterval: Base delay between retries in seconds (default: 1.0)
102+
api.WithAPIKey("your-api-key"),
103+
api.WithClientMaxRetries(0), // Task-level retries (default: 0)
104+
api.WithMaxConnectionRetries(5), // HTTP connection retries (default: 5)
105+
api.WithRetryInterval(1.0), // Base delay between retries in seconds (default: 1.0)
108106
)
109107
```
110108

api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var defaultClient *Client
44

55
func getDefaultClient() *Client {
66
if defaultClient == nil {
7-
defaultClient = NewClient("", "", 0, 0, 0, 0)
7+
defaultClient = NewClient()
88
}
99
return defaultClient
1010
}

api/client.go

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

18+
// ClientOption is a function that configures a Client.
19+
type ClientOption func(*Client)
20+
21+
// WithAPIKey sets the API key for the client.
22+
func WithAPIKey(apiKey string) ClientOption {
23+
return func(c *Client) {
24+
c.apiKey = apiKey
25+
}
26+
}
27+
28+
// WithBaseURL sets the base URL for the client.
29+
func WithBaseURL(baseURL string) ClientOption {
30+
return func(c *Client) {
31+
c.baseURL = baseURL
32+
}
33+
}
34+
35+
// WithConnectionTimeout sets the connection timeout in seconds.
36+
func WithConnectionTimeout(timeout float64) ClientOption {
37+
return func(c *Client) {
38+
c.connectionTimeout = timeout
39+
}
40+
}
41+
42+
// WithClientMaxRetries sets the maximum number of task-level retries.
43+
func WithClientMaxRetries(maxRetries int) ClientOption {
44+
return func(c *Client) {
45+
c.maxRetries = maxRetries
46+
}
47+
}
48+
49+
// WithMaxConnectionRetries sets the maximum number of HTTP connection retries.
50+
func WithMaxConnectionRetries(maxRetries int) ClientOption {
51+
return func(c *Client) {
52+
c.maxConnectionRetries = maxRetries
53+
}
54+
}
55+
56+
// WithRetryInterval sets the base interval between retries in seconds.
57+
func WithRetryInterval(interval float64) ClientOption {
58+
return func(c *Client) {
59+
c.retryInterval = interval
60+
}
61+
}
62+
1863
// RunOption is a function that configures RunOptions.
1964
type RunOption func(*RunOptions)
2065

@@ -110,34 +155,51 @@ type uploadResponse struct {
110155
Data map[string]interface{} `json:"data"`
111156
}
112157

113-
// NewClient creates a new WaveSpeed API client.
114-
func NewClient(apiKey string, baseURL string, connectionTimeout float64, maxRetries int, maxConnectionRetries int, retryInterval float64) *Client {
115-
if apiKey == "" {
116-
apiKey = os.Getenv("WAVESPEED_API_KEY")
117-
}
118-
if baseURL == "" {
119-
baseURL = "https://api.wavespeed.ai"
158+
// NewClient creates a new WaveSpeed API client with optional configuration.
159+
//
160+
// All parameters are optional and can be configured using functional options.
161+
// If not specified, the following defaults are used:
162+
// - apiKey: from WAVESPEED_API_KEY environment variable
163+
// - baseURL: "https://api.wavespeed.ai"
164+
// - connectionTimeout: 10.0 seconds
165+
// - maxRetries: 0 (no task-level retries)
166+
// - maxConnectionRetries: 5
167+
// - retryInterval: 1.0 second
168+
//
169+
// Example:
170+
//
171+
// // With defaults (API key from environment)
172+
// client := api.NewClient()
173+
//
174+
// // With custom API key
175+
// client := api.NewClient(api.WithAPIKey("your-api-key"))
176+
//
177+
// // With multiple options
178+
// client := api.NewClient(
179+
// api.WithAPIKey("your-api-key"),
180+
// api.WithClientMaxRetries(3),
181+
// api.WithRetryInterval(2.0),
182+
// )
183+
func NewClient(opts ...ClientOption) *Client {
184+
// Create client with default values
185+
client := &Client{
186+
apiKey: os.Getenv("WAVESPEED_API_KEY"),
187+
baseURL: "https://api.wavespeed.ai",
188+
connectionTimeout: 10.0,
189+
maxRetries: 0,
190+
maxConnectionRetries: 5,
191+
retryInterval: 1.0,
120192
}
121-
baseURL = strings.TrimRight(baseURL, "/")
122193

123-
if connectionTimeout == 0 {
124-
connectionTimeout = 10.0
125-
}
126-
if maxConnectionRetries == 0 {
127-
maxConnectionRetries = 5
128-
}
129-
if retryInterval == 0 {
130-
retryInterval = 1.0
194+
// Apply user-provided options
195+
for _, opt := range opts {
196+
opt(client)
131197
}
132198

133-
return &Client{
134-
apiKey: apiKey,
135-
baseURL: baseURL,
136-
connectionTimeout: connectionTimeout,
137-
maxRetries: maxRetries,
138-
maxConnectionRetries: maxConnectionRetries,
139-
retryInterval: retryInterval,
140-
}
199+
// Normalize baseURL
200+
client.baseURL = strings.TrimRight(client.baseURL, "/")
201+
202+
return client
141203
}
142204

143205
func (c *Client) getHeaders() (map[string]string, error) {

api/client_test.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
func TestInitWithAPIKey(t *testing.T) {
15-
client := NewClient("test-key", "", 0, 0, 0, 0)
15+
client := NewClient(WithAPIKey("test-key"))
1616
if client.apiKey != "test-key" {
1717
t.Errorf("expected apiKey=test-key, got %s", client.apiKey)
1818
}
@@ -22,14 +22,14 @@ func TestInitWithAPIKey(t *testing.T) {
2222
}
2323

2424
func TestInitWithCustomBaseURL(t *testing.T) {
25-
client := NewClient("test-key", "https://custom.api.com/", 0, 0, 0, 0)
25+
client := NewClient(WithAPIKey("test-key"), WithBaseURL("https://custom.api.com/"))
2626
if client.baseURL != "https://custom.api.com" {
2727
t.Errorf("expected baseURL=https://custom.api.com, got %s", client.baseURL)
2828
}
2929
}
3030

3131
func TestGetHeadersRaisesWithoutAPIKey(t *testing.T) {
32-
client := NewClient("", "", 0, 0, 0, 0)
32+
client := NewClient()
3333
client.apiKey = ""
3434
_, err := client.getHeaders()
3535
if err == nil {
@@ -41,7 +41,7 @@ func TestGetHeadersRaisesWithoutAPIKey(t *testing.T) {
4141
}
4242

4343
func TestGetHeadersReturnsAuthHeader(t *testing.T) {
44-
client := NewClient("test-key", "", 0, 0, 0, 0)
44+
client := NewClient(WithAPIKey("test-key"))
4545
headers, err := client.getHeaders()
4646
if err != nil {
4747
t.Fatalf("getHeaders error: %v", err)
@@ -63,7 +63,7 @@ func TestSubmitSuccess(t *testing.T) {
6363
server := httptest.NewServer(mux)
6464
defer server.Close()
6565

66-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
66+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
6767
requestID, result, err := client.submit("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, false, 0)
6868
if err != nil {
6969
t.Fatalf("submit error: %v", err)
@@ -85,7 +85,7 @@ func TestSubmitFailure(t *testing.T) {
8585
server := httptest.NewServer(mux)
8686
defer server.Close()
8787

88-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
88+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
8989
_, _, err := client.submit("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, false, 0)
9090
if err == nil {
9191
t.Fatal("expected error for HTTP 500")
@@ -104,7 +104,7 @@ func TestGetResultSuccess(t *testing.T) {
104104
server := httptest.NewServer(mux)
105105
defer server.Close()
106106

107-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
107+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
108108
result, err := client.getResult("req-123", 0)
109109
if err != nil {
110110
t.Fatalf("getResult error: %v", err)
@@ -131,7 +131,7 @@ func TestRunSuccess(t *testing.T) {
131131
server := httptest.NewServer(mux)
132132
defer server.Close()
133133

134-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
134+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
135135
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)
@@ -161,7 +161,7 @@ func TestRunFailure(t *testing.T) {
161161
server := httptest.NewServer(mux)
162162
defer server.Close()
163163

164-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
164+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
165165
_, 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")
@@ -205,7 +205,7 @@ func TestUploadFilePath(t *testing.T) {
205205
}
206206
defer os.Remove(tmpFile)
207207

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

218218
func TestUploadFileNotFound(t *testing.T) {
219-
client := NewClient("test-key", "", 0, 0, 0, 0)
219+
client := NewClient(WithAPIKey("test-key"))
220220
_, err := client.Upload("/nonexistent/path/to/file.png")
221221
if err == nil {
222222
t.Fatal("expected error for non-existent file")
@@ -227,7 +227,7 @@ func TestUploadFileNotFound(t *testing.T) {
227227
}
228228

229229
func TestUploadRaisesWithoutAPIKey(t *testing.T) {
230-
client := NewClient("", "", 0, 0, 0, 0)
230+
client := NewClient()
231231
client.apiKey = ""
232232
_, err := client.Upload("/some/file.png")
233233
if err == nil {
@@ -253,7 +253,7 @@ func TestUploadHTTPError(t *testing.T) {
253253
}
254254
defer os.Remove(tmpFile)
255255

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

281-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
281+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
282282
_, err := client.Upload(tmpFile)
283283
if err == nil {
284284
t.Fatal("expected error for API error response")
@@ -298,7 +298,7 @@ func TestRunSyncModeFailure(t *testing.T) {
298298
server := httptest.NewServer(mux)
299299
defer server.Close()
300300

301-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
301+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
302302
_, 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")
@@ -328,7 +328,7 @@ func TestRunTimeout(t *testing.T) {
328328
server := httptest.NewServer(mux)
329329
defer server.Close()
330330

331-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
331+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
332332
_, 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")
@@ -474,7 +474,7 @@ func TestRunAllRetriesFailed(t *testing.T) {
474474
server := httptest.NewServer(mux)
475475
defer server.Close()
476476

477-
client := NewClient("test-key", server.URL, 0, 2, 0, 0.01) // maxRetries=2
477+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL), WithClientMaxRetries(2), WithRetryInterval(0.01)) // maxRetries=2
478478
_, err := client.Run("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, WithPollInterval(0.01), WithMaxRetries(2))
479479

480480
if err == nil {
@@ -500,7 +500,7 @@ func TestGetResultConnectionRetry(t *testing.T) {
500500
server := httptest.NewServer(mux)
501501
defer server.Close()
502502

503-
client := NewClient("test-key", server.URL, 0, 0, 5, 0.01)
503+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL), WithMaxConnectionRetries(5), WithRetryInterval(0.01))
504504
_, err := client.getResult("req-123", 0)
505505

506506
if err == nil {
@@ -518,7 +518,7 @@ func TestGetResultConnectionRetry(t *testing.T) {
518518
}
519519

520520
func TestIsRetryableError(t *testing.T) {
521-
client := NewClient("test-key", "", 0, 0, 0, 0)
521+
client := NewClient(WithAPIKey("test-key"))
522522

523523
tests := []struct {
524524
name string
@@ -560,7 +560,7 @@ func TestSubmitConnectionRetry(t *testing.T) {
560560
server := httptest.NewServer(mux)
561561
defer server.Close()
562562

563-
client := NewClient("test-key", server.URL, 0, 0, 5, 0.01)
563+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL), WithMaxConnectionRetries(5), WithRetryInterval(0.01))
564564
_, _, err := client.submit("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, false, 0)
565565

566566
if err == nil {
@@ -588,7 +588,7 @@ func TestWaitInvalidResponse(t *testing.T) {
588588
server := httptest.NewServer(mux)
589589
defer server.Close()
590590

591-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
591+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
592592
_, err := client.wait("req-123", 0.1, 0.01)
593593

594594
if err == nil {
@@ -609,7 +609,7 @@ func TestGetResultNon200Status(t *testing.T) {
609609
server := httptest.NewServer(mux)
610610
defer server.Close()
611611

612-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
612+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
613613
_, err := client.getResult("req-123", 0)
614614

615615
if err == nil {
@@ -635,7 +635,7 @@ func TestSubmitMissingRequestID(t *testing.T) {
635635
server := httptest.NewServer(mux)
636636
defer server.Close()
637637

638-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
638+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
639639
_, _, err := client.submit("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "test"}, false, 0)
640640

641641
if err == nil {
@@ -658,7 +658,7 @@ func TestWaitMissingStatus(t *testing.T) {
658658
server := httptest.NewServer(mux)
659659
defer server.Close()
660660

661-
client := NewClient("test-key", server.URL, 0, 0, 0, 0)
661+
client := NewClient(WithAPIKey("test-key"), WithBaseURL(server.URL))
662662
_, err := client.wait("req-123", 0.1, 0.01)
663663

664664
if err == nil {

0 commit comments

Comments
 (0)