Go Client SDK

Official Go client library for Providence API

The Providence Go SDK provides a type-safe, idiomatic Go interface for interacting with the Providence API.

Installation

Install the SDK using go get:

go get github.com/providence-ai/providence-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/providence-ai/providence-go"
)

func main() {
    // Create a new client
    client := providence.NewClient(
        providence.WithAPIKey("pvd_live_your_api_key"),
        providence.WithBaseURL("https://api.providence.io"), // Optional
    )
    
    // List projects
    projects, err := client.Projects.List(context.Background(), &providence.ListProjectsParams{
        Limit: providence.Int(10),
    })
    if err != nil {
        log.Fatal(err)
    }
    
    for _, project := range projects.Projects {
        fmt.Printf("Project: %s (%s)\n", project.Name, project.ID)
    }
    
    // Execute a natural language query
    result, err := client.Queries.Execute(context.Background(), "proj_123", &providence.ExecuteQueryRequest{
        Query: "What were the top 10 products by revenue last month?",
        Type:  providence.QueryTypeNaturalLanguage,
    })
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Query returned %d rows\n", result.Results.RowCount)
}

Authentication

API Key Authentication

client := providence.NewClient(
    providence.WithAPIKey("pvd_live_your_api_key"),
)

JWT Token Authentication

client := providence.NewClient(
    providence.WithToken("your_jwt_token"),
)

Custom HTTP Client

httpClient := &http.Client{
    Timeout: 30 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:    10,
        IdleConnTimeout: 30 * time.Second,
    },
}

client := providence.NewClient(
    providence.WithHTTPClient(httpClient),
    providence.WithAPIKey("pvd_live_your_api_key"),
)

Core Resources

Projects

// List projects
projects, err := client.Projects.List(ctx, &providence.ListProjectsParams{
    OrganizationID: providence.String("org_123"),
    Limit:          providence.Int(20),
    Page:           providence.Int(1),
})

// Create a project
project, err := client.Projects.Create(ctx, &providence.CreateProjectRequest{
    Name:           "Q1 Analytics",
    Slug:           "q1-analytics",
    Description:    "First quarter business analytics",
    OrganizationID: "org_123",
})

// Get a project
project, err := client.Projects.Get(ctx, "proj_123")

// Update a project
project, err := client.Projects.Update(ctx, "proj_123", &providence.UpdateProjectRequest{
    Name:        providence.String("Q1 Analytics Updated"),
    Description: providence.String("Updated description"),
})

// Delete a project
err := client.Projects.Delete(ctx, "proj_123", &providence.DeleteProjectRequest{
    Confirmation: "DELETE_PROJECT_Q1_ANALYTICS",
})

Datasets

// List datasets in a project
datasets, err := client.Datasets.List(ctx, "proj_123", &providence.ListDatasetsParams{
    Type:   providence.String("file"),
    Search: providence.String("sales"),
})

// Upload a file for dataset
file, err := os.Open("sales_data.csv")
defer file.Close()

uploadResult, err := client.Datasets.Upload(ctx, "proj_123", &providence.UploadDatasetRequest{
    File:        file,
    Name:        "Sales Data 2024",
    Description: "Annual sales data",
})

// Create dataset from upload
dataset, err := client.Datasets.Create(ctx, "proj_123", &providence.CreateDatasetRequest{
    Name:        "Sales Data 2024",
    Description: "Annual sales data",
    Type:        "file",
    Source: &providence.DatasetSource{
        Type:   "csv",
        FileID: uploadResult.FileID,
    },
})

// Get dataset details
dataset, err := client.Datasets.Get(ctx, "proj_123", "ds_456")

// Preview dataset
preview, err := client.Datasets.Preview(ctx, "proj_123", "ds_456", &providence.PreviewDatasetParams{
    Limit: providence.Int(100),
})

// Delete dataset
err := client.Datasets.Delete(ctx, "proj_123", "ds_456")

Queries

// Execute a query
result, err := client.Queries.Execute(ctx, "proj_123", &providence.ExecuteQueryRequest{
    Query: "Show me total sales by region",
    Type:  providence.QueryTypeNaturalLanguage,
    Options: &providence.QueryOptions{
        Limit:          providence.Int(1000),
        TimeoutSeconds: providence.Int(30),
        Explain:        providence.Bool(true),
        Cache:          providence.Bool(true),
    },
})

// Handle results
for _, row := range result.Results.Rows {
    // Process each row
    region := row[0].(string)
    sales := row[1].(float64)
    fmt.Printf("Region: %s, Sales: $%.2f\n", region, sales)
}

// Stream query execution
stream, err := client.Queries.Stream(ctx, "proj_123", &providence.ExecuteQueryRequest{
    Query: "SELECT * FROM large_dataset",
    Type:  providence.QueryTypeSQL,
})

// Process streaming results
for event := range stream.Events() {
    switch e := event.(type) {
    case *providence.QueryStatusEvent:
        fmt.Printf("Status: %s\n", e.Message)
    case *providence.QuerySchemaEvent:
        fmt.Printf("Columns: %v\n", e.Columns)
    case *providence.QueryRowEvent:
        fmt.Printf("Row: %v\n", e.Values)
    case *providence.QueryCompleteEvent:
        fmt.Printf("Completed: %d rows in %dms\n", e.RowCount, e.ExecutionTimeMs)
    case *providence.QueryErrorEvent:
        log.Printf("Error: %s\n", e.Error)
    }
}

// Get query history
history, err := client.Queries.List(ctx, "proj_123", &providence.ListQueriesParams{
    Type:   providence.String("natural_language"),
    Status: providence.String("completed"),
    Limit:  providence.Int(50),
})

// Save a query
saved, err := client.Queries.Save(ctx, "proj_123", "qry_789", &providence.SaveQueryRequest{
    Name:        "Monthly Sales Report",
    Description: "Top customers by monthly sales",
    Tags:        []string{"sales", "monthly", "customers"},
})

Organizations

// List organizations
orgs, err := client.Organizations.List(ctx, &providence.ListOrganizationsParams{
    Role: providence.String("admin"),
})

// Get organization details
org, err := client.Organizations.Get(ctx, "org_123")

// Update organization
org, err := client.Organizations.Update(ctx, "org_123", &providence.UpdateOrganizationRequest{
    Name:        providence.String("Acme Corp International"),
    Description: providence.String("Global leader in innovation"),
})

// Manage teams
team, err := client.Organizations.CreateTeam(ctx, "org_123", &providence.CreateTeamRequest{
    Name:        "Data Science",
    Slug:        "data-science",
    Description: "Data science and ML team",
})

// Invite users
invite, err := client.Organizations.InviteUser(ctx, "org_123", &providence.InviteUserRequest{
    Email:   "[email protected]",
    Role:    "member",
    Teams:   []string{"team_123"},
    Message: "Welcome to our team!",
})

Advanced Features

Error Handling

result, err := client.Queries.Execute(ctx, "proj_123", &providence.ExecuteQueryRequest{
    Query: "invalid query",
})

if err != nil {
    // Check for specific error types
    if apiErr, ok := err.(*providence.APIError); ok {
        fmt.Printf("API Error: %s (Code: %s)\n", apiErr.Message, apiErr.Code)
        
        // Handle specific error codes
        switch apiErr.Code {
        case "INVALID_QUERY":
            // Handle invalid query
        case "QUERY_TIMEOUT":
            // Handle timeout
        case "RATE_LIMIT_EXCEEDED":
            // Handle rate limit
            retryAfter := apiErr.Details["retry_after"].(int)
            time.Sleep(time.Duration(retryAfter) * time.Second)
        }
    }
}

Pagination

// Helper function for paginating through all results
func getAllProjects(client *providence.Client, orgID string) ([]*providence.Project, error) {
    var allProjects []*providence.Project
    page := 1
    
    for {
        projects, err := client.Projects.List(context.Background(), &providence.ListProjectsParams{
            OrganizationID: providence.String(orgID),
            Page:           providence.Int(page),
            Limit:          providence.Int(100),
        })
        if err != nil {
            return nil, err
        }
        
        allProjects = append(allProjects, projects.Projects...)
        
        if page >= projects.Pagination.Pages {
            break
        }
        page++
    }
    
    return allProjects, nil
}

Retry Logic

// Configure client with retry
client := providence.NewClient(
    providence.WithAPIKey("pvd_live_your_api_key"),
    providence.WithRetry(providence.RetryConfig{
        MaxAttempts:  3,
        InitialDelay: 1 * time.Second,
        MaxDelay:     10 * time.Second,
        Multiplier:   2,
    }),
)

Context and Cancellation

// Use context for timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

result, err := client.Queries.Execute(ctx, "proj_123", &providence.ExecuteQueryRequest{
    Query: "Complex analytical query",
})

// Check if context was cancelled
if err != nil && errors.Is(err, context.Canceled) {
    fmt.Println("Query was cancelled")
}

Concurrent Operations

// Execute multiple queries concurrently
queries := []string{
    "Total sales by region",
    "Top customers by revenue",
    "Product performance metrics",
}

type queryResult struct {
    Query  string
    Result *providence.QueryResult
    Error  error
}

resultsChan := make(chan queryResult, len(queries))
var wg sync.WaitGroup

for _, query := range queries {
    wg.Add(1)
    go func(q string) {
        defer wg.Done()
        
        result, err := client.Queries.Execute(context.Background(), "proj_123", 
            &providence.ExecuteQueryRequest{
                Query: q,
                Type:  providence.QueryTypeNaturalLanguage,
            },
        )
        
        resultsChan <- queryResult{Query: q, Result: result, Error: err}
    }(query)
}

go func() {
    wg.Wait()
    close(resultsChan)
}()

// Collect results
for res := range resultsChan {
    if res.Error != nil {
        log.Printf("Error executing '%s': %v\n", res.Query, res.Error)
    } else {
        fmt.Printf("Query '%s' returned %d rows\n", res.Query, res.Result.Results.RowCount)
    }
}

Custom Request Options

// Add custom headers or modify requests
client := providence.NewClient(
    providence.WithAPIKey("pvd_live_your_api_key"),
    providence.WithRequestHook(func(req *http.Request) error {
        // Add custom header
        req.Header.Set("X-Request-ID", uuid.New().String())
        
        // Add tracing
        fmt.Printf("[%s] %s %s\n", time.Now().Format(time.RFC3339), req.Method, req.URL)
        
        return nil
    }),
)

Type Definitions

The SDK provides comprehensive type definitions for all API resources:

// Project represents a Providence project
type Project struct {
    ID             string            `json:"id"`
    Name           string            `json:"name"`
    Slug           string            `json:"slug"`
    Description    string            `json:"description"`
    OrganizationID string            `json:"organization_id"`
    Organization   *Organization     `json:"organization,omitempty"`
    Visibility     string            `json:"visibility"`
    Status         string            `json:"status"`
    Settings       *ProjectSettings  `json:"settings,omitempty"`
    Statistics     *ProjectStats     `json:"statistics,omitempty"`
    Tags           []string          `json:"tags,omitempty"`
    CreatedAt      time.Time         `json:"created_at"`
    UpdatedAt      time.Time         `json:"updated_at"`
    CreatedBy      *User             `json:"created_by,omitempty"`
}

// QueryResult represents the result of a query execution
type QueryResult struct {
    QueryID      string           `json:"query_id"`
    Status       string           `json:"status"`
    Type         string           `json:"type"`
    OriginalQuery string          `json:"original_query"`
    GeneratedSQL string           `json:"generated_sql,omitempty"`
    Results      *ResultSet       `json:"results"`
    Explanation  *QueryExplanation `json:"explanation,omitempty"`
    Metadata     *QueryMetadata   `json:"metadata"`
    CreatedAt    time.Time        `json:"created_at"`
}

Testing

Mock Client

// Use the mock client for testing
mockClient := providence.NewMockClient()

// Configure mock responses
mockClient.Projects.OnList().Return(&providence.ListProjectsResponse{
    Projects: []*providence.Project{
        {
            ID:   "proj_test",
            Name: "Test Project",
        },
    },
}, nil)

// Use in tests
projects, err := mockClient.Projects.List(context.Background(), nil)
assert.NoError(t, err)
assert.Len(t, projects.Projects, 1)

Integration Testing

// Create a test client with sandbox environment
testClient := providence.NewClient(
    providence.WithAPIKey(os.Getenv("PROVIDENCE_TEST_API_KEY")),
    providence.WithBaseURL("https://sandbox.api.providence.io"),
)

// Run integration tests
t.Run("CreateAndQueryDataset", func(t *testing.T) {
    // Create test project
    project, err := testClient.Projects.Create(context.Background(), 
        &providence.CreateProjectRequest{
            Name: fmt.Sprintf("Test Project %d", time.Now().Unix()),
            OrganizationID: "org_test",
        },
    )
    require.NoError(t, err)
    defer testClient.Projects.Delete(context.Background(), project.ID, nil)
    
    // Upload dataset
    // ... test dataset operations
})

Best Practices

  1. Error Handling: Always check and handle errors appropriately
  2. Context Usage: Use context for cancellation and timeouts
  3. Resource Cleanup: Use defer for cleaning up resources
  4. Pagination: Handle pagination for large result sets
  5. Rate Limiting: Implement backoff for rate limit errors
  6. Logging: Add structured logging for debugging

Migration Guide

From v1 to v2

// v1
client := providence.New("api_key")
projects, err := client.ListProjects()

// v2
client := providence.NewClient(
    providence.WithAPIKey("api_key"),
)
projects, err := client.Projects.List(ctx, nil)

Support