Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #926 from plotly/DownloadComponent
Browse files Browse the repository at this point in the history
Download component
  • Loading branch information
alexcjohnson authored Mar 4, 2021
2 parents d323542 + 73059a3 commit 495097b
Show file tree
Hide file tree
Showing 15 changed files with 459 additions and 105 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## UNRELEASED

### Added
- [#863](https://github.com/plotly/dash-core-components/pull/863) Adds a new `Download` component. Along with this several utility functions are added to help construct the appropriate data format:
- `dcc.send_file` - send a file from disk
- `dcc.send_data_frame` - send a `DataFrame`, using one of its writer methods
- `dcc.send_bytes` - send a bytestring or the result of a bytestring writer
- `dcc.send_string` - send a string or the result of a string writer

### Changed
- [#923](https://github.com/plotly/dash-core-components/pull/923)
Set autoComplete to off in `dcc.Dropdown`. This fixes [#808](https://github.com/plotly/dash-core-components/issues/808)
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export(dccConfirmDialog)
export(dccConfirmDialogProvider)
export(dccDatePickerRange)
export(dccDatePickerSingle)
export(dccDownload)
export(dccDropdown)
export(dccGraph)
export(dccInput)
Expand Down
204 changes: 110 additions & 94 deletions dash_core_components_base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,122 +6,138 @@
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, '__plotly_dash') and 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)
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)

from ._imports_ import * # noqa: F401, F403, E402
from ._imports_ import __all__ # noqa: E402
from .express import send_bytes, send_data_frame, send_file, send_string # noqa: F401, 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)
115 changes: 115 additions & 0 deletions dash_core_components_base/express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import io
import ntpath
import base64
from functools import partial

# Py2 StringIO.StringIO handles unicode but io.StringIO doesn't
try:
from StringIO import StringIO as _StringIO
py2 = True
except ImportError:
_StringIO = io.StringIO
py2 = False

# 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(_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_pickle, "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": partial(send_bytes, compression=None) if py2 else send_bytes,
}

# endregion
3 changes: 3 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
numpy
pandas
pyarrow<3;python_version<"3.7"
pyarrow;python_version>="3.7"
xlrd<2
mimesis;python_version>="3.6"
virtualenv;python_version=="2.7"
Loading

0 comments on commit 495097b

Please sign in to comment.