Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper functions #144

Merged
merged 12 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ authors = ["Chris Parmer <chris@plotly.com>", "Alexandr Romanenko <waralex@gmail
version = "1.0.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
DashBase = "03207cf0-e2b3-4b91-9ca8-690cf0fb507e"
DashCoreComponents = "1b08a953-4be3-4667-9a23-9da06441d987"
Expand Down
1 change: 1 addition & 0 deletions src/Dash.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include("resources/application.jl")
include("handlers.jl")
include("server.jl")
include("init.jl")
include("components_utils/_components_utils.jl")
include("plotly_base.jl")

@doc """
Expand Down
2 changes: 2 additions & 0 deletions src/components_utils/_components_utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include("express.jl")
include("table_format.jl")
105 changes: 105 additions & 0 deletions src/components_utils/express.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import Base64
export dcc_send_file, dcc_send_string, dcc_send_bytes
"""
dbc_send_file(path::AbstractString, filename = nothing; type = nothing)

Convert a file into the format expected by the Download component.

# Arguments
- `path` - path to the file to be sent
- `filename` - name of the file, if not provided the original filename is used
- `type` - type of the file (optional, passed to Blob in the javascript layer)
"""
function dcc_send_file(path, filename = nothing; type = nothing)
filename = isnothing(filename) ? basename(path) : filename
return dcc_send_bytes(read(path), filename, type = type)
end

"""
dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing)
dcc_send_bytes(src::Function, filename; type = nothing)

Convert vector of bytes into the format expected by the Download component.

# Examples

Sending binary content
```julia
file_data = read("path/to/file")
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
return dcc_send_bytes(file_data, "filename.fl")
end
```

Sending `DataFrame` in `Arrow` format
```julia
using DataFrames, Arrow
...
df = DataFrame(...)
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
return dcc_send_bytes("df.arr") do io
Arrow.write(io, df)
end
Copy link
Contributor

Choose a reason for hiding this comment

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

This form where the first arg to dcc_send_bytes is a function - it's a bit more cumbersome and confusing than the Python version that looks like

@app.callback(Output("download", "data"), Input("btn", "n_clicks"))
def func(n_clicks):
    return dcx.send_data_frame(df.to_csv, "mydf.csv")

The Python one is already a little unintuitive - I wish pandas would have just allowed df.to_csv to output a string if called with no buffer or file path - but at least you don't have to explicitly create a new function referencing an io that you don't care about. I think if I were using this I'd prefer to just write it out myself:

callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
    io = IOBuffer()
    Arrow.write(io, df)
    return dcc_send_bytes(take!(io), "df.arr")

More characters but the same number of lines in total, and less to learn about this interface. Is there any other way we can make this cleaner? Are there other use cases where the function to be called as src(io) is already defined, the way df.to_csv is? If not I might suggest dropping the src::Function form.

Copy link
Collaborator Author

@waralex waralex Nov 9, 2021

Choose a reason for hiding this comment

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

dcc_send_bytes(take!(io), "df.arr") is also already possible. (dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing))
The version with callback and io seemed convenient to me. However, if you think that it is confusing, then I can remove it

Copy link
Collaborator Author

@waralex waralex Nov 9, 2021

Choose a reason for hiding this comment

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

I did not make an analogue of send_data_frame because this would lead to a direct dependence of Dash on DataFrames and all packages of a particular serialization (CSV, Arrow, etc.) and thus would mean the need for a release every time the version of these packages changes

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

at 925744e i change dcc_send_bytes(src::Function, filename; type = nothing) to dcc_send_bytes(writer::Function, data, filename; type = nothing) so example above is now look like

return dcc_send_bytes(Arrow.write, df, "df.arr")

Copy link
Contributor

Choose a reason for hiding this comment

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

Much cleaner! 🎉

end
```
"""
function dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing)

return Dict(
:content => Base64.base64encode(src),
:filename => filename,
:type => type,
:base64 => true
)
end

function dcc_send_bytes(src::Function, filename; type = nothing)
io = IOBuffer()
src(io)
return dcc_send_bytes(take!(io), filename, type = type)
end

"""
dcc_send_data(src::AbstractString, filename; type = nothing)
dcc_send_data(src::Function, filename; type = nothing)

Convert string into the format expected by the Download component.
`src` is a string or function that takes a single argument - io and writes data to it

# Examples

Sending string content
```julia
text_data = "this is the test"
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
return dcc_send_string(text_data, "text.txt")
end
```

Sending `DataFrame` in `CSV` format
```julia
using DataFrames, CSV
...
df = DataFrame(...)
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
return dcc_send_string("df.csv") do io
CSV.write(io, df)
end
end
```
"""
function dcc_send_string(src::AbstractString, filename; type = nothing)

return Dict(
:content => src,
:filename => filename,
:type => type,
:base64 => false
)
end

function dcc_send_string(src::Function, filename; type = nothing)
io = IOBuffer()
src(io)
return dcc_send_string(String(take!(io)), filename, type = type)
end
Loading