Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .codegen/workspaces.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
type WorkspaceClient struct {
Config *config.Config

Files *files.FilesAPI

{{range .Services}}{{if not .IsAccounts}}
{{.Comment " // " 80}}
{{.Name}} *{{.Package.Name}}.{{.Name}}API
Expand All @@ -34,6 +36,7 @@ func NewWorkspaceClient(c ...*Config) (*WorkspaceClient, error) {
}
return &WorkspaceClient{
Config: cfg,
Files: files.NewFiles(apiClient),
{{range .Services}}{{if not .IsAccounts}}
{{.Name}}: {{.Package.Name}}.New{{.Name}}(apiClient),
{{- end}}{{end}}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ raw-specs/**
out/**
*.log
.databricks/
.DS_Store
12 changes: 11 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ func (c *DatabricksClient) unmarshal(body []byte, response any) error {
if len(body) == 0 {
return nil
}
// If the destination is bytes.Buffer, write the body over there
if raw, ok := response.(*bytes.Buffer); ok {
_, err := raw.Write(body)
return err
}
// If the destination is a byte slice, pass the body verbatim.
if raw, ok := response.(*[]byte); ok {
*raw = body
Expand All @@ -113,7 +118,6 @@ func (c *DatabricksClient) addHostToRequestUrl(r *http.Request) error {
if r.URL == nil {
return fmt.Errorf("no URL found in request")
}
r.Header.Set("Content-Type", "application/json")
url, err := url.Parse(c.Config.Host)
if err != nil {
return err
Expand All @@ -123,6 +127,11 @@ func (c *DatabricksClient) addHostToRequestUrl(r *http.Request) error {
return nil
}

func (c *DatabricksClient) addApplicationJsonContentType(r *http.Request) error {
r.Header.Set("Content-Type", "application/json")
return nil
}

func (c *DatabricksClient) redactedDump(prefix string, body []byte) (res string) {
return bodyLogger{
debugTruncateBytes: c.debugTruncateBytes,
Expand Down Expand Up @@ -275,6 +284,7 @@ func (c *DatabricksClient) perform(
visitors = append([]func(*http.Request) error{
c.Config.Authenticate,
c.addHostToRequestUrl,
c.addApplicationJsonContentType,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this doesn't change behavior, but strictly speaking we only need to set this header on requests with a body. We can set the Accept header on all of them as it pertains to the response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we set the multipart header or octet-stream header in request visitors. multipart requests do require a Content-Type because of the multipart boundary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not talking about why this change is done, I'm saying this header should not be set for GETs, or other requests without a request body.

c.addAuthHeaderToUserAgent,
}, visitors...)
resp, err := retries.Poll(ctx, c.retryTimeout,
Expand Down
17 changes: 17 additions & 0 deletions internal/dbfs_test.go → internal/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math/rand"
"path"
"path/filepath"
"strings"
"testing"
"time"

Expand All @@ -24,6 +25,22 @@ func (buf hashable) Hash() uint32 {
return h.Sum32()
}

func TestAccFilesAPI(t *testing.T) {
t.SkipNow() // until available on prod
ctx, w := workspaceTest(t)

filePath := RandomName("/Volumes/bogdanghita/default/v3_shared/sdk-testing/txt-")
err := w.Files.Upload(ctx, filePath, strings.NewReader("abcd"))
require.NoError(t, err)
t.Cleanup(func() {
err = w.Files.Delete(ctx, filePath)
assert.NoError(t, err)
})
raw, err := w.Files.ReadFile(ctx, filePath)
require.NoError(t, err)
assert.Equal(t, "abcd", string(raw))
}

func TestAccDbfsOpen(t *testing.T) {
ctx, w := workspaceTest(t)
if w.Config.IsGcp() {
Expand Down
94 changes: 84 additions & 10 deletions internal/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"path/filepath"
"strings"
"testing"

"github.com/databricks/databricks-sdk-go"
Expand Down Expand Up @@ -53,9 +54,8 @@ func TestAccWorkspaceIntegration(t *testing.T) {

// Export the notebook and assert the contents
exportResponse, err := w.Workspace.Export(ctx, workspace.ExportRequest{
DirectDownload: false,
Format: workspace.ExportFormatSource,
Path: notebook,
Format: workspace.ExportFormatSource,
Path: notebook,
})
require.NoError(t, err)
assert.True(t, exportResponse.Content == base64.StdEncoding.EncodeToString([]byte("# Databricks notebook source\nprint('hello from job')")))
Expand All @@ -74,18 +74,92 @@ func TestAccWorkspaceIntegration(t *testing.T) {
assert.Contains(t, paths, notebook)
}

func TestAccWorkspaceUploadNotebookWithFileExtensionNoTranspile(t *testing.T) {
// TODO: remove NoTranspile suffix once other languages get Upload/Donwload features

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where?..

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Donwload

ctx, w := workspaceTest(t)

notebookPath := filepath.Join("/Users", me(t, w).UserName, RandomName("notebook-")+".py")

err := w.Workspace.Upload(ctx, notebookPath, strings.NewReader("print(1)"))
assert.NoError(t, err)
Comment thread
pietern marked this conversation as resolved.
t.Cleanup(func() {
err = w.Workspace.Delete(ctx, workspace.Delete{
Path: notebookPath,
})
require.NoError(t, err)
})

info, err := w.Workspace.GetStatusByPath(ctx, notebookPath)
assert.NoError(t, err)
assert.Equal(t, workspace.LanguagePython, info.Language)
assert.Equal(t, workspace.ObjectTypeNotebook, info.ObjectType)

contents, err := w.Workspace.ReadFile(ctx, notebookPath)
assert.NoError(t, err)

assert.Equal(t, "# Databricks notebook source\nprint(1)", string(contents))
}

func TestAccWorkspaceUploadNotebookWithFileNoExtensionNoTranspile(t *testing.T) {
// TODO: remove NoTranspile suffix once other languages get Upload/Donwload features
ctx, w := workspaceTest(t)

notebookPath := filepath.Join("/Users", me(t, w).UserName, RandomName("notebook-"))

err := w.Workspace.Upload(ctx, notebookPath, strings.NewReader("print(1)"),
workspace.UploadLanguage(workspace.LanguagePython))
assert.NoError(t, err)
t.Cleanup(func() {
err = w.Workspace.Delete(ctx, workspace.Delete{
Path: notebookPath,
})
require.NoError(t, err)
})

info, err := w.Workspace.GetStatusByPath(ctx, notebookPath)
assert.NoError(t, err)
assert.Equal(t, workspace.LanguagePython, info.Language)
assert.Equal(t, workspace.ObjectTypeNotebook, info.ObjectType)

contents, err := w.Workspace.ReadFile(ctx, notebookPath)
assert.NoError(t, err)

assert.Equal(t, "# Databricks notebook source\nprint(1)", string(contents))
}

func TestAccWorkspaceUploadFileNoTranspile(t *testing.T) {
// TODO: remove NoTranspile suffix once other languages get Upload/Donwload features
ctx, w := workspaceTest(t)

txtPath := filepath.Join("/Users", me(t, w).UserName, RandomName("txt-"))

err := w.Workspace.Upload(ctx, txtPath, strings.NewReader("print(1)"),
workspace.UploadFormat(workspace.ExportFormatAuto))
assert.NoError(t, err)
t.Cleanup(func() {
err = w.Workspace.Delete(ctx, workspace.Delete{
Path: txtPath,
})
require.NoError(t, err)
})

info, err := w.Workspace.GetStatusByPath(ctx, txtPath)
assert.NoError(t, err)
assert.Equal(t, workspace.ObjectTypeFile, info.ObjectType)

contents, err := w.Workspace.ReadFile(ctx, txtPath)
assert.NoError(t, err)

assert.Equal(t, "print(1)", string(contents))
}

func TestAccWorkspaceRecursiveListNoTranspile(t *testing.T) {
ctx, w := workspaceTest(t)
notebook := myNotebookPath(t, w)

// Import the test notebook
err := w.Workspace.Import(ctx, workspace.Import{
Path: notebook,
Format: workspace.ExportFormatSource,
Language: workspace.LanguagePython,
Content: base64.StdEncoding.EncodeToString([]byte("# Databricks notebook source\nprint('hello from job')")),
Overwrite: true,
})
err := w.Workspace.Upload(ctx, notebook, strings.NewReader("print(1)"),
workspace.UploadOverwrite())
require.NoError(t, err)

allMyNotebooks, err := w.Workspace.RecursiveList(ctx, filepath.Join("/Users", me(t, w).UserName))
Expand Down
5 changes: 5 additions & 0 deletions openapi/code/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ func (pkg *Package) Load(spec *openapi.Specification, tag *openapi.Tag) error {
if seenParams[param.Name] {
continue
}
if prefix == "/api/2.0/workspace/export" && param.Name == "direct_download" {
// prevent changing the response content type via request parameter
// https://github.com/databricks/databricks-sdk-py/issues/104
continue
}
params = append(params, *param)
seenParams[param.Name] = true
}
Expand Down
2 changes: 1 addition & 1 deletion openapi/roll/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func NewSuite(dirname string) (*suite, error) {
// not transpilable
return nil
}
if strings.HasSuffix(path, "dbfs_test.go") {
if strings.HasSuffix(path, "files_test.go") {
// not transpilable
return nil
}
Expand Down
81 changes: 81 additions & 0 deletions service/files/files_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package files

import (
"bytes"
"context"
"fmt"
"io"
"net/http"

"github.com/databricks/databricks-sdk-go/client"
)

// BEGIN TODO: remove this once FilesAPI gets OpenAPI spec

func NewFiles(client *client.DatabricksClient) *FilesAPI {
return &FilesAPI{
impl: &filesImpl{
client: client,
},
}
}

type FilesService interface{}

type FilesAPI struct {
impl FilesService
}

type filesImpl struct {
client *client.DatabricksClient
}

// END TODO: remove this once FilesAPI gets OpenAPI spec

func (a *FilesAPI) Upload(ctx context.Context, path string, r io.Reader) error {
impl, ok := a.impl.(*filesImpl)
if !ok {
return fmt.Errorf("wrong impl: %v", a.impl)
}
return impl.client.Do(ctx, "PUT", "/api/2.0/fs/files"+path, r, nil,
func(r *http.Request) error {
r.Header.Set("Content-Type", "application/octet-stream")
return nil
})
}

// WriteFile is identical to [os.WriteFile] but for Files API.
func (a *FilesAPI) WriteFile(ctx context.Context, name string, data []byte) error {
return a.Upload(ctx, name, bytes.NewBuffer(data))
}

func (a *FilesAPI) Download(ctx context.Context, path string) (io.ReadCloser, error) {
impl, ok := a.impl.(*filesImpl)
if !ok {
return nil, fmt.Errorf("wrong impl: %v", a.impl)
}
var buf bytes.Buffer
err := impl.client.Do(ctx, "GET", "/api/2.0/fs/files"+path, nil, &buf)
Comment thread
nfx marked this conversation as resolved.
if err != nil {
return nil, err
}
// TODO: direclty call lower-level APIs to return raw HTTP response stream
return io.NopCloser(&buf), nil
}

// ReadFile is identical to [os.ReadFile] but for Files API.
func (a *FilesAPI) ReadFile(ctx context.Context, name string) ([]byte, error) {
b, err := a.Download(ctx, name)
if err != nil {
return nil, err
}
return io.ReadAll(b)
}

func (a *FilesAPI) Delete(ctx context.Context, path string) error {
impl, ok := a.impl.(*filesImpl)
if !ok {
return fmt.Errorf("wrong impl: %v", a.impl)
}
return impl.client.Do(ctx, "DELETE", "/api/2.0/fs/files"+path, nil, nil)
}
4 changes: 0 additions & 4 deletions service/workspace/model.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading