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

Feat/callback state #130

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open

Conversation

mapix
Copy link
Collaborator

@mapix mapix commented Nov 27, 2023

  • Added state parameter to du.callback support more state supplies such as token or current session
  • Added usage example for it

Ref: #104

@mapix mapix requested a review from fohrloop November 27, 2023 06:33
@mapix
Copy link
Collaborator Author

mapix commented Nov 27, 2023

@fohrloop
The reason I didn't add more tests here is because the current tests are a bit difficult to run on my local machine. If feasible, I will concentrate on adding more tests after more issues are resolved.

@mapix
Copy link
Collaborator Author

mapix commented Nov 27, 2023

The current modification has been running well in my scene for quite a long time, for example:

@du.callback(
    output=[
        PREVIEW_IMAGE_ID.get_output("children"),
        UPLOAD_IMAGE_LOADING_ID.get_output("children"),
        LOGIN_MODAL_ID.get_output("is_open"),
    ],
    state=[
        SESSION_STORE_ID.get_state("data"),
        TOKEN_STORE_ID.get_state("data"),
    ],
    id=UPLOAD_IMAGE_ID.get_identifier(),
)
def callback_on_completion(status: du.UploadStatus, session_id, token):
    if session_id != status.upload_id:
        return no_update, status_alert("Session mismatch.", color="danger"), no_update

    if (not token) or (not is_valid_token(token)):
        return no_update, status_alert("Login required.", color="danger"), True

    session = Session.load(session_id)
    if not session:
        return no_update, status_alert("Session not found.", color="danger"), no_update

    if status.is_completed:
        return (
            render_images(session),
            status_alert(
                f"Successfully uploaded {status.n_uploaded}/{status.n_total}.",
                color="green",
            ),
            no_update,
        )
    return (
        render_images(session),
        status_alert(f"Uploading {status.n_uploaded}...", color="green"),
        no_update,
    )

@fohrloop
Copy link
Owner

Hi @mapix ,

Glad to see this one getting a fix. Tests would be nice though; what could be done to make testing easier on your local machine?

I don't think I have too much time to contribute on this project, but could we get some user to comment this change? Or someone from your team?

@salvocamiolo / @salvo-camiolo ?

Niko

@mapix
Copy link
Collaborator Author

mapix commented Nov 27, 2023

@fohrloop

The reason for the difficulty with tests is that I always have a critical Case failure on my Mac, but I cannot determine for a while whether it is due to differences in the logic of handling unlink files between Windows and Mac. This error is not related to the current changes. I wonder if other contributors have encountered this issue as well.

Regarding more contributors, @Sisyphus235 from my team will collaborate more on this project.

================================================================================================== test session starts ==================================================================================================
platform darwin -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0 -- /Users/wfluo/miniconda3/envs/dash/bin/python
cachedir: .pytest_cache
rootdir: /Users/wfluo/workspaces/dash-uploader
configfile: pytest.ini
testpaths: tests/
plugins: anyio-3.7.1, dash-2.13.0
collected 8 items                                                                                                                                                                                                       

tests/test_chromedriver.py::test_chromedriver_version_okay PASSED                                                                                                                                                 [ 12%]
tests/test_usage.py::test_render01_render_component PASSED                                                                                                                                                        [ 25%]
tests/test_usage.py::test_upload01_upload_a_file PASSED                                                                                                                                                           [ 37%]
tests/test_disabled.py::test_disabled01_check_disabled_property_update PASSED                                                                                                                                     [ 50%]
tests/test_disabled.py::test_disabled02_check_disabled_effect PASSED                                                                                                                                              [ 62%]
tests/test_uploading_same_file_twice_with_errors.py::test_uploadtwice01_upload_a_file_twice_and_reserve_it PASSED                                                                                                 [ 75%]
tests/test_uploading_same_file_twice_with_errors.py::test_uploadtwice02_upload_a_file_twice_with_error FAILED                                                                                                     [ 87%]
tests/test_uploadstatus.py::test_uploadstatus_creation_and_serialization PASSED                                                                                                                                   [100%]

======================================================================================================= FAILURES ========================================================================================================
___________________________________________________________________________________ test_uploadtwice02_upload_a_file_twice_with_error ___________________________________________________________________________________

dash_duo = <dash.testing.composite.DashComposite object at 0x10775ca30>, testfile10kb_csv = PosixPath('/Users/wfluo/workspaces/dash-uploader/tests/testfile_for_reupload.csv')
testfile10kb_2_csv = PosixPath('/Users/wfluo/workspaces/dash-uploader/tests/testfile2_for_reupload.csv')

    def test_uploadtwice02_upload_a_file_twice_with_error(
        dash_duo, testfile10kb_csv, testfile10kb_2_csv
    ):
        # Same as test_uploadtwice01_upload_a_file_twice_and_reserve_it
        # but force an error
    
        # This app does not have retries on the "remove_file"
        # function, and therefore the error alert will appear
        # to the user instantly. (to make tests faster)
        app = import_app("tests.apps.testapp_noretry_remove_file")
        dash_duo.start_server(app)
    
        def upload_file(file_to_upload):
            # User sees the component
            upload = dash_duo.find_element("#dash-uploader")
    
            # Upload the file.
            # Clicking the upload component would open a file dialog and
            # this would require the tests to use OS specific GUI tools
            # to select the file. This could be added in the future but it's
            # probably very this would be broken
            upload_input = upload.find_element(
                By.XPATH, "//input[@name='dash-uploader-upload']"
            )
            upload_input.send_keys(str(file_to_upload))
    
        upload_file(testfile10kb_csv)
    
        # Wait for "Completed: testfile_for_reupload.csv" text, with 10 second timeout
        WebDriverWait(dash_duo._driver, 10).until(
            EC.text_to_be_present_in_element(
                (By.XPATH, "//div[@id='dash-uploader']/*/label"), testfile10kb_csv.name
            )
        )
    
        # Get the div with the output values
        callback_output = dash_duo.find_element("#callback-output")
        # Get the name of the uploaded file
        uploaded_file = callback_output.find_element(By.XPATH, "//ul").text
        uploaded_file = Path(uploaded_file)
    
        assert uploaded_file.name == testfile10kb_csv.name
        assert uploaded_file.exists()
        assert uploaded_file.stat().st_size == testfile10kb_csv.stat().st_size
    
        # Upload another file to change the labels.
        upload_file(testfile10kb_2_csv)
        # Wait for "Completed: testfile2_for_reupload.csv" text, with 10 second timeout
        WebDriverWait(dash_duo._driver, 10).until(
            EC.text_to_be_present_in_element(
                (By.XPATH, "//div[@id='dash-uploader']/*/label"), testfile10kb_2_csv.name
            )
        )
        # Wait that the callback for the 'testfile2_for_reupload.csv' has been fired.
        WebDriverWait(dash_duo._driver, 10).until(
            EC.text_to_be_present_in_element(
                (By.XPATH, '//*[@id="callback-output"]/ul/li'), testfile10kb_2_csv.name
            )
        )
    
        # Reserve file & make it impossible to upload testfile10kb_csv
        # Hold the file for 4 seconds
        file_reserve_thread = threading.Thread(
            target=reserve_file_for_while, args=(uploaded_file, HOLD_TIME_FOR_FILE)
        )
        file_reserve_thread.start()
    
        # Reupload file again with same filename
        upload_file(testfile10kb_csv)
    
        # Expect to see an error alert text for the user
        # in the following seconds
>       WebDriverWait(dash_duo._driver, 4).until(EC.alert_is_present())

tests/test_uploading_same_file_twice_with_errors.py:214: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <selenium.webdriver.support.wait.WebDriverWait (session="b1af585958442773e1def274d4b51b67")>, method = <function alert_is_present.<locals>._predicate at 0x107780160>, message = ''

    def until(self, method, message=''):
        """Calls the method provided with the driver as an argument until the \
        return value does not evaluate to ``False``.
    
        :param method: callable(WebDriver)
        :param message: optional message for :exc:`TimeoutException`
        :returns: the result of the last call to `method`
        :raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
        """
        screen = None
        stacktrace = None
    
        end_time = time.monotonic() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, 'screen', None)
                stacktrace = getattr(exc, 'stacktrace', None)
            time.sleep(self._poll)
            if time.monotonic() > end_time:
                break
>       raise TimeoutException(message, screen, stacktrace)
E       selenium.common.exceptions.TimeoutException: Message:

../../miniconda3/envs/dash/lib/python3.10/site-packages/selenium/webdriver/support/wait.py:87: TimeoutException
------------------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------------------
Dash is running on http://127.0.0.1:58055/

 * Serving Flask app 'tests.apps.testapp_noretry_remove_file'
 * Debug mode: off
<UploadStatus: latest_file = C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile_for_reupload.csv, uploaded_files = [C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile_for_reupload.csv], is_completed = True, n_uploaded = 1, n_total = 1, uploaded_size_mb = 0.009766578674316406, total_size_mb = 0.009766578674316406, progress = 1.0, upload_id = 680cec64-8cf4-11ee-984b-bef0c687ed75>
<UploadStatus: latest_file = C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile2_for_reupload.csv, uploaded_files = [C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile2_for_reupload.csv], is_completed = True, n_uploaded = 1, n_total = 1, uploaded_size_mb = 0.009766578674316406, total_size_mb = 0.009766578674316406, progress = 1.0, upload_id = 680cec64-8cf4-11ee-984b-bef0c687ed75>
<UploadStatus: latest_file = C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile_for_reupload.csv, uploaded_files = [C:\tmp\Uploads/680cec64-8cf4-11ee-984b-bef0c687ed75/testfile_for_reupload.csv], is_completed = True, n_uploaded = 1, n_total = 1, uploaded_size_mb = 0.009766578674316406, total_size_mb = 0.009766578674316406, progress = 1.0, upload_id = 680cec64-8cf4-11ee-984b-bef0c687ed75>
--------------------------------------------------------------------------------------------------- Captured log call ---------------------------------------------------------------------------------------------------
15:13:03 | INFO | dash.dash:2040 | Dash is running on http://127.0.0.1:58055/

15:13:03 | INFO | werkzeug:224 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:58055
15:13:03 | INFO | werkzeug:224 | Press CTRL+C to quit
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET / HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET / HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/deps/[email protected]_13_0m1697123470.12.1.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/deps/[email protected]_13_0m1697123470.14.0.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/deps/[email protected]_13_0m1697123470.8.1.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash_uploader/_build/dash_uploader.v0_7_0-a2m1698038156.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/deps/[email protected]_13_0m1697123470.14.0.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1697123469.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/dcc/dash_core_components.v2_12_0m1697123469.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/dcc/dash_core_components-shared.v2_12_0m1697123469.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/html/dash_html_components.v2_0_14m1697123470.min.js HTTP/1.1" 200 -
15:13:03 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:03] "GET /_dash-component-suites/dash/dash_table/bundle.v5_2_7m1697123469.js HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "GET /_dash-layout HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "GET /_dash-dependencies HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "GET /_favicon.ico?v=2.13.0 HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /API/dash-uploader HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /_dash-update-component HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /_dash-update-component HTTP/1.1" 204 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /API/dash-uploader HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /_dash-update-component HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /_dash-update-component HTTP/1.1" 204 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /API/dash-uploader HTTP/1.1" 200 -
15:13:04 | INFO | werkzeug:224 | 127.0.0.1 - - [27/Nov/2023 15:13:04] "POST /_dash-update-component HTTP/1.1" 200 -
=================================================================================================== warnings summary ====================================================================================================
tests/test_chromedriver.py::test_chromedriver_version_okay
  /Users/wfluo/miniconda3/envs/dash/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:390: DeprecationWarning: HTTPResponse.getheader() is deprecated and will be removed in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).
    if response.getheader('Content-Type'):

tests/test_chromedriver.py::test_chromedriver_version_okay
  /Users/wfluo/miniconda3/envs/dash/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:391: DeprecationWarning: HTTPResponse.getheader() is deprecated and will be removed in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).
    content_type = response.getheader('Content-Type').split(';')

tests/test_usage.py: 25 warnings
tests/test_disabled.py: 175 warnings
tests/test_uploading_same_file_twice_with_errors.py: 56 warnings
  /Users/wfluo/miniconda3/envs/dash/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:390: DeprecationWarning:
  
  HTTPResponse.getheader() is deprecated and will be removed in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).

tests/test_usage.py: 25 warnings
tests/test_disabled.py: 175 warnings
tests/test_uploading_same_file_twice_with_errors.py: 56 warnings
  /Users/wfluo/miniconda3/envs/dash/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py:391: DeprecationWarning:
  
  HTTPResponse.getheader() is deprecated and will be removed in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
====================================================================================== 1 failed, 7 passed, 514 warnings in 47.66s =======================================================================================

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants