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

Download component #863

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#854](https://github.com/plotly/dash-core-components/pull/854) Used `persistenceTransforms` to strip the time part of the datetime in the persited props of DatePickerSingle (date) and DatePickerRange (end_date, start_date), fixing [dcc#700](https://github.com/plotly/dash-core-components/issues/700).

### Added
- [#863](https://github.com/plotly/dash-core-components/pull/863) Added new `Download` component.
- [#850](https://github.com/plotly/dash-core-components/pull/850) Add property `prependData` to `Graph` to support `Plotly.prependTraces`
+ refactored the existing `extendTraces` API to be a single `mergeTraces` API that can handle both `prepend` as well as `extend`.

Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Expand Up @@ -5,6 +5,7 @@ export(dccConfirmDialog)
export(dccConfirmDialogProvider)
export(dccDatePickerRange)
export(dccDatePickerSingle)
export(dccDownload)
export(dccDropdown)
export(dccGraph)
export(dccInput)
Expand Down
223 changes: 120 additions & 103 deletions dash_core_components_base/__init__.py
@@ -1,135 +1,152 @@
from __future__ import print_function as _
from ._imports_ import * # noqa: F401, F403
from ._imports_ import __all__ # noqa: E402
from .express import * # noqa: F401, F403

import json
import os as _os
import sys as _sys
import dash as _dash

_basepath = _os.path.dirname(__file__)
_filepath = _os.path.abspath(_os.path.join(_basepath, 'package-info.json'))
_filepath = _os.path.abspath(_os.path.join(_basepath, "package-info.json"))
with open(_filepath) as f:
package = json.load(f)

package_name = package['name'].replace(' ', '_').replace('-', '_')
__version__ = package['version']
package_name = package["name"].replace(" ", "_").replace("-", "_")
__version__ = package["version"]

# Module imports trigger a dash.development import, need to check this first
if not hasattr(_dash, 'development'):
print("Dash was not successfully imported. Make sure you don't have a file "
"named \n'dash.py' in your current directory.", file=_sys.stderr)
if not hasattr(_dash, "development"):
print(
"Dash was not successfully imported. Make sure you don't have a file "
"named \n'dash.py' in your current directory.",
file=_sys.stderr,
)
_sys.exit(1)

# Must update to dash>=0.23.1 to use this version of dash-core-components
if not hasattr(_dash.development.base_component, '_explicitize_args'):
print("Please update the `dash` module to >= 0.23.1 to use this "
"version of dash_core_components.\n"
"You are using version {:s}".format(_dash.version.__version__),
file=_sys.stderr)
if not hasattr(_dash.development.base_component, "_explicitize_args"):
print(
"Please update the `dash` module to >= 0.23.1 to use this "
"version of dash_core_components.\n"
"You are using version {:s}".format(_dash.version.__version__),
file=_sys.stderr,
)
_sys.exit(1)

from ._imports_ import * # noqa: F401, F403
from ._imports_ import __all__ # noqa: E402

_current_path = _os.path.dirname(_os.path.abspath(__file__))


_this_module = _sys.modules[__name__]

async_resources = [
'datepicker',
'dropdown',
'graph',
'highlight',
'markdown',
'slider',
'upload'
"datepicker",
"dropdown",
"graph",
"highlight",
"markdown",
"slider",
"upload",
]

_js_dist = []

_js_dist.extend([{
'relative_package_path': 'async-{}.js'.format(async_resource),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/async-{}.js'
).format(__version__, async_resource),
'namespace': 'dash_core_components',
'async': True
} for async_resource in async_resources])
_js_dist.extend(
[
{
"relative_package_path": "async-{}.js".format(async_resource),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/async-{}.js"
).format(__version__, async_resource),
"namespace": "dash_core_components",
"async": True,
}
for async_resource in async_resources
]
)

_js_dist.extend([{
'relative_package_path': 'async-{}.js.map'.format(async_resource),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/async-{}.js.map'
).format(__version__, async_resource),
'namespace': 'dash_core_components',
'dynamic': True
} for async_resource in async_resources])
_js_dist.extend(
[
{
"relative_package_path": "async-{}.js.map".format(async_resource),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/async-{}.js.map"
).format(__version__, async_resource),
"namespace": "dash_core_components",
"dynamic": True,
}
for async_resource in async_resources
]
)

_js_dist.extend([
{
'relative_package_path': '{}.min.js'.format(__name__),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/dash_core_components.min.js'
).format(__version__),
'namespace': 'dash_core_components'
},
{
'relative_package_path': '{}.min.js.map'.format(__name__),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/dash_core_components.min.js.map'
).format(__version__),
'namespace': 'dash_core_components',
'dynamic': True
},
{
'relative_package_path': '{}-shared.js'.format(__name__),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/dash_core_components-shared.js'
).format(__version__),
'namespace': 'dash_core_components'
},
{
'relative_package_path': '{}-shared.js.map'.format(__name__),
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/dash_core_components-shared.js.map'
).format(__version__),
'namespace': 'dash_core_components',
'dynamic': True
},
{
'relative_package_path': 'plotly.min.js',
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/plotly.min.js'
).format(__version__),
'namespace': 'dash_core_components',
'async': 'eager'
},
{
'relative_package_path': 'async-plotlyjs.js',
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/async-plotlyjs.js'
).format(__version__),
'namespace': 'dash_core_components',
'async': 'lazy'
},
{
'relative_package_path': 'async-plotlyjs.js.map',
'external_url': (
'https://unpkg.com/dash-core-components@{}'
'/dash_core_components/async-plotlyjs.js.map'
).format(__version__),
'namespace': 'dash_core_components',
'dynamic': True
},
])
_js_dist.extend(
[
{
"relative_package_path": "{}.min.js".format(__name__),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/dash_core_components.min.js"
).format(__version__),
"namespace": "dash_core_components",
},
{
"relative_package_path": "{}.min.js.map".format(__name__),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/dash_core_components.min.js.map"
).format(__version__),
"namespace": "dash_core_components",
"dynamic": True,
},
{
"relative_package_path": "{}-shared.js".format(__name__),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/dash_core_components-shared.js"
).format(__version__),
"namespace": "dash_core_components",
},
{
"relative_package_path": "{}-shared.js.map".format(__name__),
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/dash_core_components-shared.js.map"
).format(__version__),
"namespace": "dash_core_components",
"dynamic": True,
},
{
"relative_package_path": "plotly.min.js",
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/plotly.min.js"
).format(__version__),
"namespace": "dash_core_components",
"async": "eager",
},
{
"relative_package_path": "async-plotlyjs.js",
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/async-plotlyjs.js"
).format(__version__),
"namespace": "dash_core_components",
"async": "lazy",
},
{
"relative_package_path": "async-plotlyjs.js.map",
"external_url": (
"https://unpkg.com/dash-core-components@{}"
"/dash_core_components/async-plotlyjs.js.map"
).format(__version__),
"namespace": "dash_core_components",
"dynamic": True,
},
]
)

for _component in __all__:
setattr(locals()[_component], '_js_dist', _js_dist)
setattr(locals()[_component], "_js_dist", _js_dist)
107 changes: 107 additions & 0 deletions dash_core_components_base/express.py
@@ -0,0 +1,107 @@
import io
import ntpath
import base64


# region Utils for Download component


def send_file(path, filename=None, type=None):
"""
Convert a file into the format expected by the Download component.
:param path: path to the file to be sent
:param filename: name of the file, if not provided the original filename is used
:param type: type of the file (optional, passed to Blob in the javascript layer)
:return: dict of file content (base64 encoded) and meta data used by the Download component
"""
# If filename is not set, read it from the path.
if filename is None:
filename = ntpath.basename(path)
# Read the file contents and send it.
with open(path, "rb") as f:
return send_bytes(f.read(), filename, type)


def send_bytes(src, filename, type=None, **kwargs):
"""
Convert data written to BytesIO into the format expected by the Download component.
:param src: array of bytes or a writer that can write to BytesIO
:param filename: the name of the file
:param type: type of the file (optional, passed to Blob in the javascript layer)
:return: dict of data frame content (base64 encoded) and meta data used by the Download component
"""
content = src if isinstance(src, bytes) else _io_to_str(io.BytesIO(), src, **kwargs)
return dict(
content=base64.b64encode(content).decode(),
filename=filename,
type=type,
base64=True,
)


def send_string(src, filename, type=None, **kwargs):
"""
Convert data written to StringIO into the format expected by the Download component.
:param src: a string or a writer that can write to StringIO
:param filename: the name of the file
:param type: type of the file (optional, passed to Blob in the javascript layer)
:return: dict of data frame content (NOT base64 encoded) and meta data used by the Download component
"""
content = src if isinstance(src, str) else _io_to_str(io.StringIO(), src, **kwargs)
return dict(content=content, filename=filename, type=type, base64=False)


def _io_to_str(data_io, writer, **kwargs):
# Some pandas writers try to close the IO, we do not want that.
data_io_close = data_io.close
data_io.close = lambda: None
# Write data content.
writer(data_io, **kwargs)
data_value = data_io.getvalue()
data_io_close()
return data_value


def send_data_frame(writer, filename, type=None, **kwargs):
"""
Convert data frame into the format expected by the Download component.
:param writer: a data frame writer
:param filename: the name of the file
:param type: type of the file (optional, passed to Blob in the javascript layer)
:return: dict of data frame content (base64 encoded) and meta data used by the Download component

Examples
--------

>>> df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [2, 1, 5, 6], 'c': ['x', 'x', 'y', 'y']})
...
>>> send_data_frame(df.to_csv, "mydf.csv") # download as csv
>>> send_data_frame(df.to_json, "mydf.json") # download as json
>>> send_data_frame(df.to_excel, "mydf.xls", index=False) # download as excel
>>> send_data_frame(df.to_pkl, "mydf.pkl") # download as pickle

"""
name = writer.__name__
# Check if the provided writer is known.
if name not in _data_frame_senders.keys():
raise ValueError(
"The provided writer ({}) is not supported, "
"try calling send_string or send_bytes directly.".format(name)
)
# Send data frame using the appropriate send function.
return _data_frame_senders[name](writer, filename, type, **kwargs)


_data_frame_senders = {
"to_csv": send_string,
"to_json": send_string,
"to_html": send_string,
"to_excel": send_bytes,
"to_feather": send_bytes,
"to_parquet": send_bytes,
"to_msgpack": send_bytes,
"to_stata": send_bytes,
"to_pickle": send_bytes,
}

# endregion
5 changes: 3 additions & 2 deletions dev-requirements.txt
@@ -1,4 +1,5 @@
numpy
pandas
xlrd
pyarrow
mimesis;python_version>="3.6"
virtualenv;python_version=="2.7"
virtualenv;python_version=="2.7"