Skip to content

Commit 4f4e3ac

Browse files
authored
Merge pull request #147 from plotly/dev
Release 1.1.0
2 parents b31952d + 2bcaecf commit 4f4e3ac

34 files changed

+1020
-207
lines changed

.circleci/config.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 2
33
jobs:
44

55
test:
6-
working_directory: ~/dashjl
6+
working_directory: ~/dashjl
77
docker:
88
- image: plotly/julia:ci
99
auth:
@@ -30,15 +30,15 @@ jobs:
3030
- run:
3131
name: 🔎 Unit tests
3232
command: |
33-
julia test/ci_prepare.jl
33+
julia test/ci_prepare.jl
3434
3535
- run:
3636
name: ⚙️ Integration tests
3737
command: |
3838
python -m venv venv
3939
. venv/bin/activate
4040
git clone --depth 1 https://github.com/plotly/dash.git -b dev dash-main
41-
cd dash-main && pip install -e .[dev,testing] --progress-bar off && cd ~/dashjl
41+
cd dash-main && pip install -e .[dev,testing] --progress-bar off && cd ~/dashjl
4242
export PATH=$PATH:/home/circleci/.local/bin/
4343
pytest --headless --nopercyfinalize --junitxml=test-reports/dashjl.xml --percy-assets=test/assets/ test/integration/
4444
- store_artifacts:
@@ -51,7 +51,6 @@ jobs:
5151
- run:
5252
name: 🦔 percy finalize
5353
command: npx percy finalize --all
54-
when: on_fail
5554

5655
workflows:
5756
version: 2

Project.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
name = "Dash"
22
uuid = "1b08a953-4be3-4667-9a23-3db579824955"
33
authors = ["Chris Parmer <chris@plotly.com>", "Alexandr Romanenko <waralex@gmail.com>"]
4-
version = "1.0.0"
4+
version = "1.1.0"
55

66
[deps]
7+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
78
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
89
DashBase = "03207cf0-e2b3-4b91-9ca8-690cf0fb507e"
910
DashCoreComponents = "1b08a953-4be3-4667-9a23-9da06441d987"
@@ -12,7 +13,7 @@ DashTable = "1b08a953-4be3-4667-9a23-f0e2ba4deb9a"
1213
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1314
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
1415
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
15-
JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
16+
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
1617
MD5 = "6ac74813-4b46-53a4-afec-0b5dc9d7885c"
1718
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1819
PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5"
@@ -30,11 +31,11 @@ DashTable = "5.0.0"
3031
DataStructures = "0.17, 0.18"
3132
HTTP = "0.8.10, 0.9"
3233
JSON = "0.21"
33-
JSON2 = "0.3"
34+
JSON3 = "1.9"
3435
MD5 = "0.2"
3536
PlotlyBase = "0.8.5, 0.8.6"
36-
julia = "1.3"
3737
YAML = "0.4.7"
38+
julia = "1.3"
3839

3940
[extras]
4041
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ As of v1.15.0 of Dash, Julia components can be generated in tandem with Python a
1010

1111
#### Create beautiful, analytic applications in Julia
1212

13-
🚧 Dash.jl is a work-in-progress. Feel free to test the waters and submit issues.
14-
1513
Built on top of Plotly.js, React and HTTP.jl, [Dash](https://plotly.com/dash/) ties modern UI elements like dropdowns, sliders, and graphs directly to your analytical Julia code.
1614

1715
## Installation
@@ -191,6 +189,5 @@ Be careful - in Dash.jl states come first in an arguments list.
191189

192190
### JSON:
193191

194-
I use JSON2.jl for JSON serialization/deserialization, so in callbacks all JSON objects are `NamedTuples` rather than dictionaries. Within component properties you can use both `Dict` and `NamedTuple` for JSON objects.
195-
192+
I use JSON3.jl for JSON serialization/deserialization.
196193
Note when declaring elements with a single properly that `layout = (title = "Test graph")` is not interpreted as a `NamedTuple` by Julia - you'll need to add a comma when declaring the layout, e.g. `layout = (title = "Test graph",)`

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,4 @@ Be careful - in Dashboards states came first in arguments list
174174

175175
### json:
176176

177-
I use JSON2 for json serialization/deserialization, so in callbacks all json objects are NamedTuples not Dicts. In component props you can use both Dicts and NamedTuples for json objects. But be careful with single property objects: `layout = (title = "Test graph")` is not interpreted as NamedTuple by Julia - you need add comma at the end `layout = (title = "Test graph",)`
177+
I use JSON3 for json serialization/deserialization. In component props you can use both Dicts and NamedTuples for json objects. But be careful with single property objects: `layout = (title = "Test graph")` is not interpreted as NamedTuple by Julia - you need add comma at the end `layout = (title = "Test graph",)`

src/Dash.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module Dash
22
using DashBase
3-
import HTTP, JSON2, CodecZlib, MD5
3+
import HTTP, JSON3, CodecZlib, MD5
44
using Sockets
55
using Pkg.Artifacts
6+
67
const ROOT_PATH = realpath(joinpath(@__DIR__, ".."))
78
#const RESOURCE_PATH = realpath(joinpath(ROOT_PATH, "resources"))
89
include("exceptions.jl")
@@ -25,6 +26,8 @@ include("resources/application.jl")
2526
include("handlers.jl")
2627
include("server.jl")
2728
include("init.jl")
29+
include("components_utils/_components_utils.jl")
30+
include("plotly_base.jl")
2831

2932
@doc """
3033
module Dash
@@ -107,6 +110,8 @@ function __init__()
107110
end
108111

109112

113+
JSON3.StructTypes.StructType(::Type{DashBase.Component}) = JSON3.StructTypes.Struct()
114+
JSON3.StructTypes.excludes(::Type{DashBase.Component}) = (:name, :available_props, :wildcard_regex)
110115

111116

112-
end # module
117+
end # module

src/app/callbacks.jl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,11 @@ function check_callback(func, app::DashApp, deps::CallbackDeps)
165165
isempty(deps.output) && error("The callback method requires that one or more properly formatted outputs are passed.")
166166
isempty(deps.input) && error("The callback method requires that one or more properly formatted inputs are passed.")
167167

168-
!check_unique(deps.output) && error("One or more callback outputs have been duplicated; please confirm that all outputs are unique.")
169-
170-
for out in deps.output
171-
if any(x->out in x.dependencies.output, values(app.callbacks))
172-
error("output \"$(out)\" already registered")
173-
end
174-
end
175168

176169
args_count = length(deps.state) + length(deps.input)
177170

178171
check_callback_func(func, args_count)
179172

180-
for id_prop in deps.input
181-
id_prop in deps.output && error("Circular input and output arguments were found. Please verify that callback outputs are not also input arguments.")
182-
end
183173
end
184174

185175
function check_callback_func(func::Function, args_count)

src/app/supporttypes.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ struct Wildcard
22
type ::Symbol
33
end
44

5-
JSON2.write(io::IO, wild::Wildcard; kwargs...) = Base.write(io, "[\"", wild.type, "\"]")
5+
JSON3.StructTypes.StructType(::Type{Wildcard}) = JSON3.RawType()
6+
7+
JSON3.rawbytes(wild::Wildcard) = string("[\"", wild.type, "\"]")
68

79
const MATCH = Wildcard(:MATCH)
810
const ALL = Wildcard(:ALL)
@@ -30,6 +32,8 @@ const Input = Dependency{TraitInput}
3032
const State = Dependency{TraitState}
3133
const Output = Dependency{TraitOutput}
3234

35+
JSON3.StructTypes.StructType(::Type{<:Dependency}) = JSON3.StructTypes.Struct()
36+
3337
"""
3438
Base.==(a::Dependency, b::Dependency)
3539
@@ -97,6 +101,9 @@ struct ClientsideFunction
97101
namespace ::String
98102
function_name ::String
99103
end
104+
105+
JSON3.StructTypes.StructType(::Type{ClientsideFunction}) = JSON3.StructTypes.Struct()
106+
100107
struct Callback
101108
func ::Union{Function, ClientsideFunction}
102109
dependencies ::CallbackDeps
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include("express.jl")
2+
include("table_format.jl")

src/components_utils/express.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import Base64
2+
export dcc_send_file, dcc_send_string, dcc_send_bytes
3+
"""
4+
dbc_send_file(path::AbstractString, filename = nothing; type = nothing)
5+
6+
Convert a file into the format expected by the Download component.
7+
8+
# Arguments
9+
- `path` - path to the file to be sent
10+
- `filename` - name of the file, if not provided the original filename is used
11+
- `type` - type of the file (optional, passed to Blob in the javascript layer)
12+
"""
13+
function dcc_send_file(path, filename = nothing; type = nothing)
14+
filename = isnothing(filename) ? basename(path) : filename
15+
return dcc_send_bytes(read(path), filename, type = type)
16+
end
17+
18+
"""
19+
dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing)
20+
dcc_send_bytes(writer::Function, data, filename; type = nothing)
21+
22+
Convert vector of bytes into the format expected by the Download component.
23+
`writer` function must have signature `(io::IO, data)`
24+
25+
# Examples
26+
27+
Sending binary content
28+
```julia
29+
file_data = read("path/to/file")
30+
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
31+
return dcc_send_bytes(file_data, "filename.fl")
32+
end
33+
```
34+
35+
Sending `DataFrame` in `Arrow` format
36+
```julia
37+
using DataFrames, Arrow
38+
...
39+
df = DataFrame(...)
40+
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
41+
return dcc_send_bytes(Arrow.write, df, "df.arr")
42+
end
43+
```
44+
"""
45+
function dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing)
46+
47+
return Dict(
48+
:content => Base64.base64encode(src),
49+
:filename => filename,
50+
:type => type,
51+
:base64 => true
52+
)
53+
end
54+
55+
function dcc_send_bytes(writer::Function, data, filename; type = nothing)
56+
io = IOBuffer()
57+
writer(io, data)
58+
return dcc_send_bytes(take!(io), filename, type = type)
59+
end
60+
61+
"""
62+
dcc_send_data(src::AbstractString, filename; type = nothing)
63+
dcc_send_data(writer::Function, data, filename; type = nothing)
64+
65+
Convert string into the format expected by the Download component.
66+
`writer` function must have signature `(io::IO, data)`
67+
68+
# Examples
69+
70+
Sending string content
71+
```julia
72+
text_data = "this is the test"
73+
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
74+
return dcc_send_string(text_data, "text.txt")
75+
end
76+
```
77+
78+
Sending `DataFrame` in `CSV` format
79+
```julia
80+
using DataFrames, CSV
81+
...
82+
df = DataFrame(...)
83+
callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks
84+
return dcc_send_string(CSV.write, df, "df.csv")
85+
end
86+
```
87+
"""
88+
function dcc_send_string(src::AbstractString, filename; type = nothing)
89+
90+
return Dict(
91+
:content => src,
92+
:filename => filename,
93+
:type => type,
94+
:base64 => false
95+
)
96+
end
97+
98+
function dcc_send_string(writer::Function, data, filename; type = nothing)
99+
io = IOBuffer()
100+
writer(io, data)
101+
return dcc_send_string(String(take!(io)), filename, type = type)
102+
end

0 commit comments

Comments
 (0)