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

Refactor Dash.jl to support same parameters as Dash for Python & R #12

Merged
merged 6 commits into from
Apr 26, 2020
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
11 changes: 11 additions & 0 deletions src/ComponentPackages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ module ComponentPackages
)
end

function components_js_sources(prefix = ""; debug=false)
type = debug ? :dev : :prod
result = []

for p in values(_components_packages)
for script in p.scripts[type]
push!(result, "$(prefix)_dash-component-suites/$(p.package_name)/$(script)")
rpkyle marked this conversation as resolved.
Show resolved Hide resolved
end
end
return result
end



Expand Down
5 changes: 4 additions & 1 deletion src/Dash.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ export dash, Component, Front, @use, <|, @callid_str, CallbackId, callback!,
run_server, PreventUpdate, no_update, @wildprop

ComponentPackages.@reg_components()
include("utils.jl")
include("config.jl")
include("app.jl")
include("index_page.jl")
include("handlers.jl")
include("utils.jl")


@doc """
Expand Down Expand Up @@ -91,4 +93,5 @@ function run_server(app::DashApp, host = HTTP.Sockets.localhost, port = 8080; de
HTTP.serve(handler, host, port)
end


end # module
269 changes: 215 additions & 54 deletions src/app.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
const IdProp = Tuple{Symbol, Symbol}

const default_index = """<!DOCTYPE html>
rpkyle marked this conversation as resolved.
Show resolved Hide resolved
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
</head>
<body>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>"""



struct CallbackId
state ::Vector{IdProp}
input ::Vector{IdProp}
Expand Down Expand Up @@ -34,33 +54,43 @@ mutable struct Layout
component::Union{Nothing, Component}
end

const ExternalSrcType = Union{String, Dict{String, String}}

struct DashConfig
external_stylesheets ::Vector{ExternalSrcType}
external_scripts ::Vector{ExternalSrcType}
url_base_pathname ::Union{String, Nothing} #TODO This looks unused
requests_pathname_prefix ::String
routes_pathname_prefix ::String
assets_folder ::String
assets_url_path ::String
assets_ignore ::String
serve_locally ::Bool
suppress_callback_exceptions ::Bool
eager_loading ::Bool
meta_tags ::Vector{Dict{String, String}}
index_string ::Union{String, Nothing}
assets_external_path ::Union{String, Nothing}
include_assets_files ::Bool
show_undo_redo ::Bool
end

"""
struct DashApp <: Any

Representation of Dash application
Representation of Dash application.

Not meant to be constructed directly, use `dash` function instead.
"""
struct DashApp
name ::String
config ::DashConfig
layout ::Layout
callbacks ::Dict{Symbol, Callback}
external_stylesheets ::Vector{String}
external_scripts ::Vector{String}
url_base_pathname ::String
assets_folder ::String
callable_components ::Dict{Symbol, Component}
function DashApp(name::String;
external_stylesheets ::Vector{String} = Vector{String}(),
external_scripts ::Vector{String} = Vector{String}(),
url_base_pathname="/",
assets_folder::String = "assets")

new(name, Layout(nothing), Dict{Symbol, Callback}(),
external_stylesheets, external_scripts,
url_base_pathname, assets_folder,
Dict{Symbol, Component}()

)
end
callable_components ::Dict{Symbol, Component}

DashApp(name::String, config::DashConfig) = new(name, config, Layout(nothing), Dict{Symbol, Callback}(), Dict{Symbol, Component}())

end

function layout!(app::DashApp, component::Component)
Copy link
Contributor

Choose a reason for hiding this comment

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

@waralex I imagine this function updates the layout within app to include the tree of components that are passed? If that's the case, does layout support passing a function that returns a component?

i.e. in both Python and R, I believe, the layout can be passed a tree of components or a single function. for example:

    def _layout_value(self):
        return self._layout() if self._layout_is_function else self._layout

or

    layout = function(...) {
      private$layout_ <- if (is.function(..1)) ..1 else list(...)
      invisible(private$layout_render())
    },

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rpkyle function is not supported yet, so you can add it to issues list so I don't forget it later

Copy link
Contributor

Choose a reason for hiding this comment

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

@waralex Added in #15.

Expand All @@ -78,59 +108,190 @@ function Base.getproperty(app::DashApp, name::Symbol)
name == :layout ? getlayout(app) : Base.getfield(app, name)
end





"""
dash(name::String; external_stylesheets ::Vector{String} = Vector{String}(), url_base_pathname::String="/")
dash(layout_maker::Function, name::String; external_stylesheets ::Vector{String} = Vector{String}(), url_base_pathname::String="/")
dash(name::String;
external_stylesheets,
external_scripts,
url_base_pathname,
requests_pathname_prefix,
routes_pathname_prefix,
assets_folder,
assets_url_path,
assets_ignore,
serve_locally,
suppress_callback_exceptions,
eager_loading ,
meta_tags,
index_string,
assets_external_path,
include_assets_files,
show_undo_redo
)

Construct a dash app using callback for layout creation
Construct a dash app

# Arguments
- `layout_maker::Function` - function for layout creation. Must has signature ()::Component
- `name::String` - Dashboard name
- `external_stylesheets::Vector{String} = Vector{String}()` - vector of external css urls
- `external_scripts::Vector{String} = Vector{String}()` - vector of external js scripts urls
- `url_base_pathname::String="/"` - base url path for dashboard, default "/"
- `name::String` - The name of your application
- `assets_folder::String` - a path, relative to the current working directory,
for extra files to be used in the browser. Default `"assets"`
for extra files to be used in the browser. Default ``'assets'``.

# Examples
```jldoctest
julia> app = dash("Test") do
html_div() do
html_h1("Test Dashboard")
end
end
```
"""
- `assets_url_path::String` - The local urls for assets will be:
``requests_pathname_prefix * assets_url_path * "/" * asset_path``
where ``asset_path`` is the path to a file inside ``assets_folder``.
Default ``'assets'`.


- `assets_ignore::String` - [IN DEVELOPMENT] A regex, as a string to pass to ``Regex``, for
assets to omit from immediate loading. Ignored files will still be
served if specifically requested. You cannot use this to prevent access
to sensitive files.
:type assets_ignore: string

- `assets_external_path::String` - [IN DEVELOPMENT] an absolute URL from which to load assets.
Use with ``serve_locally=false``. Dash can still find js and css to
automatically load if you also keep local copies in your assets
folder that Dash can index, but external serving can improve
performance and reduce load on the Dash server.


- `include_assets_files::Bool` - [IN DEVELOPMENT] Default ``true``, set to ``False`` to prevent
immediate loading of any assets. Assets will still be served if
specifically requested. You cannot use this to prevent access
to sensitive files.


- `url_base_pathname::String`: A local URL prefix to use app-wide.
Default ``nothing``. Both `requests_pathname_prefix` and
`routes_pathname_prefix` default to `url_base_pathname`.


- `requests_pathname_prefix::String`: A local URL prefix for file requests.
Defaults to `url_base_pathname`, and must end with
`routes_pathname_prefix`


- `routes_pathname_prefix::String`: A local URL prefix for JSON requests.
Defaults to ``url_base_pathname``, and must start and end
with ``'/'``.

- `serve_locally`: [IN DEVELOPMENT] If ``true`` (default), assets and dependencies
(Dash and Component js and css) will be served from local URLs.
If ``false`` we will use CDN links where available.

- `meta_tags::Vector{Dict{String, String}}`: html <meta> tags to be added to the index page.
Each dict should have the attributes and values for one tag, eg:
``Dict("name"=>"description", "content" => "My App")``


- `index_string::String`: Override the standard Dash index page.
Must contain the correct insertion markers to interpolate various
content into it depending on the app config and components used.
See https://dash.plotly.com/external-resources for details.


- `external_scripts::Vector`: Additional JS files to load with the page.
Each entry can be a String (the URL) or a Dict{String, String} with ``src`` (the URL)
and optionally other ``<script>`` tag attributes such as ``integrity``
and ``crossorigin``.

- `external_stylesheets::Vector`: Additional CSS files to load with the page.
Each entry can be a String (the URL) or a Dict{String, String} with ``href`` (the URL)
and optionally other ``<link>`` tag attributes such as ``rel``,
``integrity`` and ``crossorigin``.

- `suppress_callback_exceptions::Bool`: Default ``false``: check callbacks to
ensure referenced IDs exist and props are valid. Set to ``true``
if your layout is dynamic, to bypass these checks.

- `show_undo_redo::Bool`: Default ``false``, set to ``true`` to enable undo
and redo buttons for stepping through the history of the app state.

"""
function dash(name::String;
rpkyle marked this conversation as resolved.
Show resolved Hide resolved
external_stylesheets ::Vector{String} = Vector{String}(),
external_scripts ::Vector{String} = Vector{String}(),
url_base_pathname="/",
assets_folder::String = "assets")
result = DashApp(name,
external_stylesheets=external_stylesheets,
external_scripts=external_scripts,
url_base_pathname=url_base_pathname,
assets_folder = assets_folder
)
external_stylesheets = ExternalSrcType[],
external_scripts = ExternalSrcType[],
url_base_pathname = nothing,
Copy link
Contributor

Choose a reason for hiding this comment

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

@waralex I'm not sure why we changed the default for url_base_pathname from "/" to None, but I'll investigate. The Python docstring still reflects "/", I suspect that this was never updated, so what you have here (nothing) is likely correct.

Copy link
Collaborator Author

@waralex waralex Apr 24, 2020

Choose a reason for hiding this comment

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

@rpkyle See the comment to the previous issue
Also

I suspect that this was never updated

This is why I don't like to write expanded docstrings in the initial stages of development. Because it need to be completely rewritten before release, or it will accumulate similar inconsistencies. With frequent changes to an api that has not yet stabilized it is very difficult to track that there are no inconsistencies in the code and documentation

requests_pathname_prefix = nothing,
routes_pathname_prefix = nothing,
Comment on lines +218 to +219
Copy link
Contributor

Choose a reason for hiding this comment

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

@waralex For these, specifying nothing as the default seems right, and then internally we want to make sure to resolve in the following order

  • check if the corresponding environment variable exists, use the value if present, otherwise:
  • check to see if the DASH_APP_NAME environment variable exists, use `//' if present, otherwise:
  • check to see if the DASH_URL_BASE_PATHNAME environment variable exists, use if present, otherwise:
  • return the base pathname, aka url_base_pathname, which defaults to "/" when undefined

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rpkyle I removed the environment variables mention from docstring for a reason. Working with environment variables is one of the components of a future PR.
url_base_pathname not defaults to "/" - this is a documentation error in python. routes_pathname_prefix and requests_pathname_prefix defaults to "/" when url_base_pathname is None, but if you set url_base_pathname to default "/" and requests_pathname_prefix to some value then you will get the error "You supplied url_base_pathname and requests_pathname_prefix. This is ambiguous ..."

assets_folder = "assets",
assets_url_path = "assets",
assets_ignore = "",
serve_locally = true,
suppress_callback_exceptions = false,
eager_loading = false,
meta_tags = Dict{Symbol, String}[],
index_string = default_index,
assets_external_path = nothing,
include_assets_files = true,
show_undo_redo = false

)


config = DashConfig(
external_stylesheets,
external_scripts,
pathname_configs(
url_base_pathname,
requests_pathname_prefix,
routes_pathname_prefix
)...,
absolute_assets_path(assets_folder),
lstrip(assets_url_path, '/'),
assets_ignore,
serve_locally,
suppress_callback_exceptions,
eager_loading,
meta_tags,
index_string,
assets_external_path,
include_assets_files,
show_undo_redo
)

result = DashApp(name, config)
return result
end

function dash(layout_maker::Function, name::String;
external_stylesheets ::Vector{String} = Vector{String}(),
external_scripts ::Vector{String} = Vector{String}(),
url_base_pathname="/",
assets_folder::String = "assets")
function dash(layout_maker ::Function, name;
external_stylesheets = ExternalSrcType[],
external_scripts = ExternalSrcType[],
url_base_pathname = nothing,
requests_pathname_prefix = nothing,
routes_pathname_prefix = nothing,
assets_folder = "assets",
assets_url_path = "assets",
assets_ignore = "",
serve_locally = true,
suppress_callback_exceptions = false,
eager_loading = false,
meta_tags = Dict{Symbol, String}[],
index_string = default_index,
assets_external_path = nothing,
include_assets_files = true,
show_undo_redo = false
)
result = dash(name,
external_stylesheets=external_stylesheets,
external_scripts=external_scripts,
url_base_pathname=url_base_pathname,
assets_folder = assets_folder
requests_pathname_prefix = requests_pathname_prefix,
routes_pathname_prefix = routes_pathname_prefix,
assets_folder = assets_folder,
assets_url_path = assets_url_path,
assets_ignore = assets_ignore,
serve_locally = serve_locally,
suppress_callback_exceptions = suppress_callback_exceptions,
eager_loading = eager_loading,
meta_tags = meta_tags,
index_string = index_string,
assets_external_path = assets_external_path,
include_assets_files = include_assets_files,
show_undo_redo = show_undo_redo
)
layout!(result, layout_maker())
return result
Expand Down
Loading