Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.
Open
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
35 changes: 22 additions & 13 deletions dash_uploader/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,23 @@
import dash_uploader.settings as settings


def create_dash_callback(callback, settings): # pylint: disable=redefined-outer-name
"""Wrap the dash callback with the du.settings.
def query_app_and_root(component_id):
"""Query the app and the root folder by the given component id.
This is a private method, and should not be exposed to users.
"""
app_idx = settings.user_configs_query.get(component_id, None)
if app_idx is None:
app_idx = settings.user_configs_default
app_item = settings.user_configs[app_idx]
if not app_item["is_dash"]:
raise TypeError("The du.configure_upload must be called with a dash.Dash instance before the @du.callback can be used! Please, configure the dash-uploader.")
app = app_item["app"]
upload_folder_root = app_item["upload_folder_root"]
return app, upload_folder_root


def create_dash_callback(callback, app_root_folder): # pylint: disable=redefined-outer-name
"""Wrap the dash callback with the upload_folder_root.
This function could be used as a wrapper. It will add the
configurations of dash-uploader to the callback.
This is a private method, and should not be exposed to users.
Expand All @@ -19,9 +34,9 @@ def wrapper(iscompleted, filenames, upload_id):
out = []
if filenames is not None:
if upload_id:
root_folder = Path(settings.UPLOAD_FOLDER_ROOT) / upload_id
root_folder = Path(app_root_folder) / upload_id
else:
root_folder = Path(settings.UPLOAD_FOLDER_ROOT)
root_folder = Path(app_root_folder)

for filename in filenames:
file = root_folder / filename
Expand Down Expand Up @@ -56,9 +71,8 @@ def callback(
)
def get_a_list(filenames):
return html.Ul([html.Li(filenames)])


"""
app, upload_folder_root = query_app_and_root(id)

def add_callback(function):
"""
Expand All @@ -77,15 +91,10 @@ def add_callback(function):
"""
dash_callback = create_dash_callback(
function,
settings,
upload_folder_root,
)

if not hasattr(settings, "app"):
raise Exception(
"The du.configure_upload must be called before the @du.callback can be used! Please, configure the dash-uploader."
)

dash_callback = settings.app.callback(
dash_callback = app.callback(
output,
[Input(id, "isCompleted")],
[State(id, "fileNames"), State(id, "upload_id")],
Expand Down
138 changes: 118 additions & 20 deletions dash_uploader/configure_upload.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,96 @@
import logging

import dash
import flask

import dash_uploader.settings as settings
from dash_uploader.upload import update_upload_api
from dash_uploader.httprequesthandler import HttpRequestHandler


logger = logging.getLogger("dash_uploader")


def update_upload_api(requests_pathname_prefix, upload_api):
"""Path join for the API path name.
This is a private method, and should not be exposed to users.
"""
if requests_pathname_prefix == "/":
return upload_api
return "/".join(
[
requests_pathname_prefix.rstrip("/"),
upload_api.lstrip("/"),
]
)


def check_app(app):
"""Check the validity of the provided app.
The app requires to be a dash.Dash instance or a flask.Flask
instance. It should not be repeated in the user configurations.
This is a private method, and should not be exposed to users.
"""
is_dash = isinstance(app, dash.Dash)
if not is_dash and not isinstance(app, flask.Flask):
raise TypeError(
'The argument "app" requires to be a dash.Dash instance or a flask.Flask instance.'
)
return is_dash


def check_upload_component_ids(upload_component_ids):
"""Check the validity of the component ids.
This function is used for checking the validity of provided component
ids. A valid id should be a non-empty str and not repeated in the
configurations.
This is a private method, and should not be exposed to users.
"""
# When None, check the default configs.
if upload_component_ids is None:
if settings.user_configs_default is not None:
raise ValueError(
"The default app has been configured before. A repeated configuration is not allowed."
)
return None
# When not None, check the ids first.
valid_ids = None
if isinstance(upload_component_ids, str) and upload_component_ids != "":
valid_ids = (upload_component_ids,)
if isinstance(upload_component_ids, (list, tuple)):
if all(
map(lambda uid: isinstance(uid, str) and uid != "", upload_component_ids)
):
valid_ids = tuple(upload_component_ids)
if valid_ids is None:
raise TypeError(
'The argument "upload_component_ids" should be None, str or [str].'
)
# Then, check the repetition of the provided ids.
for uid in valid_ids:
if uid in settings.user_configs_query:
raise ValueError(
'The component id "{0}" has been configured before. A repeated configuration is not allowed.'
)
return valid_ids


def configure_upload(
app, folder, use_upload_id=True, upload_api=None, http_request_handler=None
app,
folder,
use_upload_id=True,
upload_api=None,
http_request_handler=None,
upload_component_ids=None,
):
r"""
Configure the upload APIs for dash app.
This function is required to be called before using du.callback.

Parameters
---------
app: dash.Dash
The application instance
app: dash.Dash or flask.Flask
The application instance. It is required to be a dash.Dash
for using du.callback.
folder: str
The folder where to upload files.
Can be relative ("uploads") or
Expand All @@ -44,35 +116,60 @@ def configure_upload(
If you provide a class, use a subclass of HttpRequestHandler.
See the documentation of dash_uploader.HttpRequestHandler for
more details.
upload_component_ids: None or str or [str]
A list of du.Upload component ids. If set None, this configuration would be
regarded as default configurations. If not, the registered app would be
configured for the provided components.
"""
settings.UPLOAD_FOLDER_ROOT = folder
settings.app = app
# Check the validity of arguments.
is_dash = check_app(app)
upload_component_ids = check_upload_component_ids(upload_component_ids)

# Configure the API. Extra configs are needed if using a proxy for dash app.
if upload_api is None:
upload_api = settings.upload_api
if is_dash and upload_api is not None:
routes_pathname_prefix = app.config.get("routes_pathname_prefix", "/")
requests_pathname_prefix = app.config.get("requests_pathname_prefix", "/")
service = update_upload_api(requests_pathname_prefix, upload_api)
full_upload_api = update_upload_api(routes_pathname_prefix, upload_api)
else:
# Set the upload api since du.Upload components
# that are created after du.configure_upload
# need to be able to read the api endpoint.
settings.upload_api = upload_api

# Needed if using a proxy
settings.requests_pathname_prefix = app.config.get("requests_pathname_prefix", "/")
settings.routes_pathname_prefix = app.config.get("routes_pathname_prefix", "/")

upload_api = update_upload_api(settings.routes_pathname_prefix, upload_api)
service = upload_api
full_upload_api = upload_api

# Set the request handler.
if http_request_handler is None:
http_request_handler = HttpRequestHandler

server = app.server if is_dash else app
decorate_server(
app.server,
server,
folder,
upload_api,
full_upload_api,
http_request_handler=http_request_handler,
use_upload_id=use_upload_id,
)

# If no bugs are triggered, would update the user configs.
# Set the upload api since du.Upload components
# that are created after du.configure_upload
# need to be able to read the api endpoint.
app_idx = len(settings.user_configs)
settings.user_configs.append({
"app": app,
"service": service,
"upload_api": upload_api,
"upload_folder_root": folder,
"is_dash": is_dash,
"upload_component_ids": upload_component_ids,
})
# Set the query.
if upload_component_ids is not None:
for uid in upload_component_ids:
settings.user_configs_query[uid] = app_idx
else:
settings.user_configs_default = app_idx


def decorate_server(
server,
Expand Down Expand Up @@ -106,5 +203,6 @@ def decorate_server(
server, upload_folder=temp_base, use_upload_id=use_upload_id
)

server.add_url_rule(upload_api, None, handler.get, methods=["GET"])
server.add_url_rule(upload_api, None, handler.post, methods=["POST"])
end_point = upload_api.lstrip("/").replace("/", ".")
server.add_url_rule(upload_api, end_point + ".get", handler.get, methods=["GET"])
server.add_url_rule(upload_api, end_point + ".post", handler.post, methods=["POST"])
48 changes: 33 additions & 15 deletions dash_uploader/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,37 @@
# The du.configure_upload can change this
upload_api = "/API/resumable"

# Needed if using a proxy; when dash.Dash is used
# with a `requests_pathname_prefix`.
# The front-end will prefix this string to the requests
# that are made to the proxy server
requests_pathname_prefix = '/'
# User configurations:
# The configuration list is used for storing user-defined configurations.
# Each item is set by an independent du.configure_upload. The list is
# formatted as
# user_configs = {
# {
# 'app': dash.Dash() or flask.Flask(),
# 'service': str,
# 'upload_api': str,
# 'routes_pathname_prefix': str,
# 'requests_pathname_prefix': str,
# 'upload_folder_root': str,
# 'is_dash': bool
# 'upload_component_ids': [str]
# },
# ...
# }
# It is not recommended to change this dict manually. It should be
# automatically set by du.configure_upload.
user_configs = list()

# From dash source code:
# Note that `requests_pathname_prefix` is the prefix for the AJAX calls that
# originate from the client (the web browser) and `routes_pathname_prefix` is
# the prefix for the API routes on the backend (this flask server).
# `url_base_pathname` will set `requests_pathname_prefix` and
# `routes_pathname_prefix` to the same value.
# If you need these to be different values then you should set
# `requests_pathname_prefix` and `routes_pathname_prefix`,
# not `url_base_pathname`.
routes_pathname_prefix = '/'
# Backward query dict:
# This dictionary is used for fast querying the items in user_configs. It
# is formatted as
# user_configs_query = {
# 'upload_id_1': list_index_1,
# 'upload_id_2': list_index_2,
# ...
# }
# user_configs_query is a str (The default name of the configs.)
# It is not recommended to change this dict manually. It should be
# automatically set by du.configure_upload.
user_configs_query = {}
user_configs_default = None
20 changes: 9 additions & 11 deletions dash_uploader/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@
}


def update_upload_api(requests_pathname_prefix, upload_api):
'''Path join for the API path name.
def query_service_addr(component_id):
"""Query the service address by the given component id.
This is a private method, and should not be exposed to users.
'''
if requests_pathname_prefix == '/':
return upload_api
return '/'.join([
requests_pathname_prefix.rstrip('/'),
upload_api.lstrip('/'),
])
"""
app_idx = settings.user_configs_query.get(component_id, None)
if app_idx is None:
app_idx = settings.user_configs_default
service_addr = settings.user_configs[app_idx]["service"]
return service_addr


def combine(overiding_dict, base_dict):
Expand Down Expand Up @@ -125,8 +124,7 @@ def Upload(
if upload_id is None:
upload_id = uuid.uuid1()

service = update_upload_api(settings.requests_pathname_prefix,
settings.upload_api)
service = query_service_addr(id)

arguments = dict(
id=id,
Expand Down