diff --git a/.circleci/config.yml b/.circleci/config.yml index 63e3db2308..48b3fa4495 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,7 +55,7 @@ jobs: - save_cache: key: dep-{{ checksum ".circleci/config.yml" }}-{{ checksum "ver.txt" }}-{{ checksum "requires-dev.txt" }}-{{ checksum "requires-install.txt" }}-{{ checksum "requires-testing.txt" }} paths: - - venv + - venv - run: name: 🌸 Python & JS Lint command: | @@ -71,26 +71,26 @@ jobs: npm run citest.unit lint-unit-36: - <<: *lint-unit - docker: - - image: circleci/python:3.6.12-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYLINTRC: .pylintrc - PYVERSION: python36 + <<: *lint-unit + docker: + - image: circleci/python:3.6.12-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYLINTRC: .pylintrc + PYVERSION: python36 lint-unit-27: - <<: *lint-unit - docker: - - image: circleci/python:2.7.18-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYLINTRC: .pylintrc - PYVERSION: python27 + <<: *lint-unit + docker: + - image: circleci/python:2.7.18-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYLINTRC: .pylintrc + PYVERSION: python27 build-core-37: &build-core working_directory: ~/dash @@ -107,7 +107,7 @@ jobs: - restore_cache: key: dep-{{ checksum ".circleci/config.yml" }}-{{ checksum "ver.txt" }}-{{ checksum "requires-dev.txt" }}-{{ checksum "requires-install.txt" }}-{{ checksum "requires-testing.txt" }} - run: - name: ️️🏗️ pip dev requirements + name: ️🏗️ pip dev requirements command: | sudo pip install --upgrade virtualenv python -m venv venv || virtualenv venv && . venv/bin/activate @@ -116,39 +116,39 @@ jobs: - save_cache: key: dep-{{ checksum ".circleci/config.yml" }}-{{ checksum "ver.txt" }}-{{ checksum "requires-dev.txt" }}-{{ checksum "requires-install.txt" }}-{{ checksum "requires-testing.txt" }} paths: - - venv + - venv - run: name: ️️🏗️ build core command: | - . venv/bin/activate && pip install --no-cache-dir --upgrade -e . --progress-bar off && mkdir packages - cd dash-renderer && renderer build && python setup.py sdist && mv dist/* ../packages/ && cd .. - git clone --depth 1 https://github.com/plotly/dash-core-components.git - cd dash-core-components && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. - ls -la packages + . venv/bin/activate && pip install --no-cache-dir --upgrade -e . --progress-bar off && mkdir packages + cd dash-renderer && renderer build && python setup.py sdist && mv dist/* ../packages/ && cd .. + git clone --depth 1 https://github.com/plotly/dash-core-components.git + cd dash-core-components && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. + ls -la packages - persist_to_workspace: root: ~/dash paths: - packages/*.tar.gz build-core-36: - <<: *build-core - docker: - - image: circleci/python:3.6.12-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYVERSION: python36 + <<: *build-core + docker: + - image: circleci/python:3.6.12-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python36 build-core-27: - <<: *build-core - docker: - - image: circleci/python:2.7.18-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYVERSION: python27 + <<: *build-core + docker: + - image: circleci/python:2.7.18-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python27 build-misc-37: &build-misc working_directory: ~/dash @@ -174,40 +174,40 @@ jobs: - save_cache: key: dep-{{ checksum ".circleci/config.yml" }}-{{ checksum "ver.txt" }}-{{ checksum "requires-dev.txt" }}-{{ checksum "requires-install.txt" }}-{{ checksum "requires-testing.txt" }} paths: - - venv + - venv - run: name: ️️🏗️ build misc command: | - . venv/bin/activate && pip install --no-cache-dir --upgrade -e . --progress-bar off && mkdir packages - git clone --depth 1 https://github.com/plotly/dash-table.git - cd dash-table && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. - git clone --depth 1 https://github.com/plotly/dash-html-components.git - cd dash-html-components && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. - ls -la packages + . venv/bin/activate && pip install --no-cache-dir --upgrade -e . --progress-bar off && mkdir packages + git clone --depth 1 https://github.com/plotly/dash-table.git + cd dash-table && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. + git clone --depth 1 https://github.com/plotly/dash-html-components.git + cd dash-html-components && npm ci && npm run build && python setup.py sdist && mv dist/* ../packages/ && cd .. + ls -la packages - persist_to_workspace: root: ~/dash paths: - packages/*.tar.gz build-misc-36: - <<: *build-misc - docker: - - image: circleci/python:3.6.12-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYVERSION: python36 + <<: *build-misc + docker: + - image: circleci/python:3.6.12-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python36 build-misc-27: - <<: *build-misc - docker: - - image: circleci/python:2.7.18-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PYVERSION: python27 + <<: *build-misc + docker: + - image: circleci/python:2.7.18-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python27 build-windows-37: working_directory: ~/dash @@ -222,10 +222,10 @@ jobs: - run: name: ️️🏗️ build core command: | - pip install --no-cache-dir --upgrade -e .[dev,testing] --progress-bar off - cd dash-renderer && renderer build && python setup.py sdist && mv dist/* ../packages/ && cd .. - git clone --depth 1 https://github.com/plotly/dash-core-components.git - cd dash-core-components && npm ci && npm run build && python setup.py sdist && cd .. + pip install --no-cache-dir --upgrade -e .[dev,testing] --progress-bar off + cd dash-renderer && renderer build && python setup.py sdist && mv dist/* ../packages/ && cd .. + git clone --depth 1 https://github.com/plotly/dash-core-components.git + cd dash-core-components && npm ci && npm run build && python setup.py sdist && cd .. build-dashr: working_directory: ~/dashr @@ -241,9 +241,8 @@ jobs: steps: - checkout - - run: - name: ️️🏭 clone and npm build core for R + name: 🏭 clone and npm build core for R command: | python -m venv venv . venv/bin/activate @@ -259,7 +258,7 @@ jobs: cd ../dash-table; npm ci && npm run build; rm -rf !(.|..|DESCRIPTION|LICENSE.txt|LICENSE|NAMESPACE|.Rbuildignore|R|man|inst|vignettes|build); cd .. - run: - name: 🔧fix up dash metadata + name: 🔧 fix up dash metadata command: | sudo Rscript dashR/tests/circleci/fixup_metadata.R @@ -282,7 +281,7 @@ jobs: >> ${BASH_ENV} - run: - name: ️️📋 run CRAN package checks + name: ️📋 run CRAN package checks command: | R CMD build dash-core-components R CMD build dash-html-components @@ -331,7 +330,6 @@ jobs: path: test-reports - store_artifacts: path: /tmp/dash_artifacts - - run: name: 🦔 percy finalize command: npx percy finalize --all @@ -358,7 +356,7 @@ jobs: - attach_workspace: at: ~/dash - run: - name: ️️🏗️ Install packages + name: ️️🏗️ Install packages command: | . venv/bin/activate npm install --production @@ -384,53 +382,45 @@ jobs: when: on_fail test-36: - <<: *test - docker: - - image: circleci/python:3.6.12-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PERCY_ENABLE: 0 - PYVERSION: python36 + <<: *test + docker: + - image: circleci/python:3.6.12-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PERCY_ENABLE: 0 + PYVERSION: python36 test-27: - <<: *test - docker: - - image: circleci/python:2.7.18-stretch-node-browsers - auth: - username: dashautomation - password: $DASH_PAT_DOCKERHUB - environment: - PERCY_ENABLE: 0 - PYVERSION: python27 + <<: *test + docker: + - image: circleci/python:2.7.18-stretch-node-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PERCY_ENABLE: 0 + PYVERSION: python27 workflows: version: 2 python3.7: jobs: - - lint-unit-37: - context: dash-docker-hub - - build-core-37: - context: dash-docker-hub - - build-windows-37: - context: dash-docker-hub - - build-misc-37: - context: dash-docker-hub - - build-dashr: - context: dash-docker-hub + - lint-unit-37 + - build-core-37 + - build-windows-37 + - build-misc-37 + - build-dashr - test-37: - context: dash-docker-hub requires: - build-core-37 - build-misc-37 - percy-finalize: - context: dash-docker-hub requires: - build-dashr - test-37 - artifacts: - context: dash-docker-hub requires: - percy-finalize filters: @@ -443,27 +433,19 @@ workflows: python3.6: jobs: - - lint-unit-36: - context: dash-docker-hub - - build-core-36: - context: dash-docker-hub - - build-misc-36: - context: dash-docker-hub + - lint-unit-36 + - build-core-36 + - build-misc-36 - test-36: - context: dash-docker-hub requires: - build-core-36 - build-misc-36 python2.7: jobs: - - lint-unit-27: - context: dash-docker-hub - - build-core-27: - context: dash-docker-hub - - build-misc-27: - context: dash-docker-hub + - lint-unit-27 + - build-core-27 + - build-misc-27 - test-27: - context: dash-docker-hub requires: - build-core-27 - build-misc-27 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 31b8ff29d0..8145fb95f9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -13,7 +13,7 @@ We do our best to catch bugs during the release process, but we rely on your hel **Describe your context** -Please provide us your environment so we can easily reproduce the issue. +Please provide us your environment, so we can easily reproduce the issue. - replace the result of `pip list | grep dash` below ``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c685706a69..47887292fa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,4 +13,4 @@ - [ ] I have added entry in the `CHANGELOG.md` - [ ] If this PR needs a follow-up in **dash docs**, **community thread**, I have mentioned the relevant URLS as follows - [ ] this GitHub [#PR number]() updates the dash docs - - [ ] here is the show and tell thread in Plotly Dash community + - [ ] here is the show and tell thread in Plotly Dash community diff --git a/@plotly/dash-component-plugins/LICENSE b/@plotly/dash-component-plugins/LICENSE index f8dd245665..ded2aed963 100644 --- a/@plotly/dash-component-plugins/LICENSE +++ b/@plotly/dash-component-plugins/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Plotly +Copyright (c) 2021 Plotly, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/@plotly/dash-component-plugins/package-lock.json b/@plotly/dash-component-plugins/package-lock.json index 6642ae98dd..b4f5505bd5 100644 --- a/@plotly/dash-component-plugins/package-lock.json +++ b/@plotly/dash-component-plugins/package-lock.json @@ -2012,18 +2012,26 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } } }, "emoji-regex": { @@ -2600,12 +2608,6 @@ "dev": true, "optional": true }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -3201,9 +3203,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { @@ -5300,9 +5302,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { @@ -5331,9 +5333,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/@plotly/dash-generator-test-component-nested/package-lock.json b/@plotly/dash-generator-test-component-nested/package-lock.json index 383b061ea3..3caae04112 100644 --- a/@plotly/dash-generator-test-component-nested/package-lock.json +++ b/@plotly/dash-generator-test-component-nested/package-lock.json @@ -2189,18 +2189,26 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } } }, "emoji-regex": { @@ -2834,12 +2842,6 @@ "dev": true, "optional": true }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -3458,9 +3460,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { @@ -5931,9 +5933,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { diff --git a/@plotly/dash-generator-test-component-standard/package-lock.json b/@plotly/dash-generator-test-component-standard/package-lock.json index 3bb77c4eeb..fb46afeb4a 100644 --- a/@plotly/dash-generator-test-component-standard/package-lock.json +++ b/@plotly/dash-generator-test-component-standard/package-lock.json @@ -2189,18 +2189,26 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } } }, "emoji-regex": { @@ -2834,12 +2842,6 @@ "dev": true, "optional": true }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -3458,9 +3460,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { @@ -5931,9 +5933,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { diff --git a/@plotly/dash-test-components/package-lock.json b/@plotly/dash-test-components/package-lock.json index b97ea9647d..0f3f0131f4 100644 --- a/@plotly/dash-test-components/package-lock.json +++ b/@plotly/dash-test-components/package-lock.json @@ -2368,24 +2368,24 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -3117,9 +3117,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { @@ -5540,9 +5540,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { diff --git a/@plotly/webpack-dash-dynamic-import/LICENSE b/@plotly/webpack-dash-dynamic-import/LICENSE index f8dd245665..ded2aed963 100644 --- a/@plotly/webpack-dash-dynamic-import/LICENSE +++ b/@plotly/webpack-dash-dynamic-import/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Plotly +Copyright (c) 2021 Plotly, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/@plotly/webpack-dash-dynamic-import/package.json b/@plotly/webpack-dash-dynamic-import/package.json index b2504683b2..f90e00d8dc 100644 --- a/@plotly/webpack-dash-dynamic-import/package.json +++ b/@plotly/webpack-dash-dynamic-import/package.json @@ -1,6 +1,6 @@ { "name": "@plotly/webpack-dash-dynamic-import", - "version": "1.1.5", + "version": "1.2.0", "description": "Webpack Plugin for Dynamic Import in Dash", "repository": { "type": "git", diff --git a/@plotly/webpack-dash-dynamic-import/src/index.js b/@plotly/webpack-dash-dynamic-import/src/index.js index ff7a14dad1..490e66b904 100644 --- a/@plotly/webpack-dash-dynamic-import/src/index.js +++ b/@plotly/webpack-dash-dynamic-import/src/index.js @@ -78,10 +78,7 @@ class WebpackDashDynamicImport { apply(compiler) { compiler.hooks.compilation.tap('WebpackDashDynamicImport', compilation => { compilation.mainTemplate.hooks.requireExtensions.tap('WebpackDashDynamicImport > RequireExtensions', (source, chunk, hash) => { - return [ - source, - resolveImportSource() - ] + return source + resolveImportSource(); }); }); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cba6cbe3a..e35f161292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [1.20.0] - 2021-04-08 + +## Dash and Dash Renderer +### Changed +- [#1531](https://github.com/plotly/dash/pull/1531) Update the format of the docstrings to make them easier to read in the reference pages of Dash Docs and in the console. This also addresses [#1205](https://github.com/plotly/dash/issues/1205) +- [#1553](https://github.com/plotly/dash/pull/1553) Increase the z-index of the Dash error menu from 1001 to 1100 in order to make sure it appears above Bootstrap components. + +### Fixed +- [#1546](https://github.com/plotly/dash/pull/1546) Validate callback request `outputs` vs `output` to avoid a perceived security issue. + +## Dash Core Components +### 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) + +### Fixed +- [#930](https://github.com/plotly/dash-core-components/pull/930) Fixed a bug [#867](https://github.com/plotly/dash-core-components/issues/867) with `DatePickerRange` that would sometimes shift the allowed dates by one day. +- [#934](https://github.com/plotly/dash-core-components/pull/934) Fixed a bug in `EnhancedTab` component that ignored `disabled_className` property + +## Dash HTML Components +### Fixed +- [#179](https://github.com/plotly/dash-html-components/pull/179) - Fixes [#77](https://github.com/plotly/dash-html-components/issues/77) Added `allow` and `referrerPolicy` properties to `html.Iframe` + +- [#178](https://github.com/plotly/dash-html-components/pull/178) - Fix [#161](https://github.com/plotly/dash-html-components/issues/161) `data` property, and fix [#129](https://github.com/plotly/dash-html-components/issues/129) obsolete, deprecated, and discouraged elements. No elements were removed, but comments were added to the documentation about these elements detailing their limitations. + +## Dash Table +### Changed +- [#862](https://github.com/plotly/dash-table/pull/862) - update docstrings per https://github.com/plotly/dash/issues/1205 +- [#878](https://github.com/plotly/dash-table/pull/878) - update build process to use Webpack 5 and other latest dependencies + ## [1.19.0] - 2021-01-19 ## Dash and Dash Renderer @@ -15,7 +52,7 @@ to close the error messages box. ### Fixed - [#1530](https://github.com/plotly/dash/pull/1530) Dedent error messages more carefully. -- [#1527](https://github.com/plotly/dash/issues/1527)🐛 `get_asset_url` now pulls from an external source if `assets_external_path` is set. +- [#1527](https://github.com/plotly/dash/issues/1527) 🐛 `get_asset_url` now pulls from an external source if `assets_external_path` is set. - updated `_add_assets_resource` to build asset urls the same way as `get_asset_url`. - updated doc string for `assets_external_path` Dash argument to be more clear that it will allways be joined with the `assets_url_path` argument when determining the url to an external asset. - [#1493](https://github.com/plotly/dash/pull/1493) Fix [#1143](https://github.com/plotly/dash/issues/1143), a bug where having a file with one of several common names (test.py, code.py, org.py, etc) that imports a dash component package would make `import dash` fail with a cryptic error message asking whether you have a file named "dash.py" @@ -350,7 +387,7 @@ clientside JavaScript callbacks via inline strings. - [#724](https://github.com/plotly/dash/pull/724), [renderer#175](https://github.com/plotly/dash-renderer/pull/175) Undo/redo toolbar is removed by default, you can enable it with `app=Dash(show_undo_redo=true)`. The CSS hack `._dash-undo-redo:{display:none;}` is no longer needed -- 💥[#709](https://github.com/plotly/dash/pull/709) Merge the `dash-renderer` project into the main dash repo to simplify feature dev workflow. We will keep the [deprecated one](https://github.com/plotly/dash-renderer) for archive purpose. +- 💥 [#709](https://github.com/plotly/dash/pull/709) Merge the `dash-renderer` project into the main dash repo to simplify feature dev workflow. We will keep the [deprecated one](https://github.com/plotly/dash-renderer) for archive purpose. ## [0.43.0] - 2019-05-15 ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66166e84c5..497fbf55cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ $ pip list | grep dash ### Dash-Renderer Beginner Guide -`Dash Renderer` began as a separate repository. It was merged into the main `Dash` repository as part of the 1.0 release. It is the common frontend for all Dash backends (**R** and **Python**), and manages React Component layout and backend event handling. +`Dash Renderer` began as a separate repository. It was merged into the main `Dash` repository as part of the 1.0 release. It is the common frontend for all Dash backends (**R** and **Python**), and manages React Component layout and backend event handling. If you want to contribute or simply dig deeper into Dash, we encourage you to play and taste it. This is the most efficient way to learn and understand everything under the hood. diff --git a/LICENSE b/LICENSE index c78d61d94c..1196ce9631 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 Plotly, Inc +Copyright (c) 2021 Plotly, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e28b47688f..58e26bc619 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ With Dash Open Source, Dash apps run on your local laptop or workstation, but ca Scale up with Dash Enterprise when your Dash app is ready for department or company-wide consumption. Or, launch your initiative with Dash Enterprise from the start to unlock developer productivity gains and hands-on acceleration from Plotly's team. -ML Ops Features: A one-stop shop for ML Ops: Horizontally scalable hosting, deployment, and authentication for your Dash apps. No IT or DevOps required. +ML Ops Features: A one-stop shop for ML Ops: Horizontally scalable hosting, deployment, and authentication for your Dash apps. No IT or DevOps required. - [**App manager**](https://plotly.com/dash/app-manager/) Deploy & manage Dash apps without needing IT or a DevOps team. App Manager gives you point & click control over all aspects of your Dash deployments. -- [**Kubernetes scaling**](https://plotly.com/dash/kubernetes/) Ensure high availability of Dash apps and scale horizontally with Dash Enterprise’s Kubernetes architecture. No IT or Helm required. +- [**Kubernetes scaling**](https://plotly.com/dash/kubernetes/) Ensure high availability of Dash apps and scale horizontally with Dash Enterprise’s Kubernetes architecture. No IT or Helm required. - [**No code auth**](https://plotly.com/dash/authentication/) Control Dash app access in a few clicks. Dash Enterprise supports LDAP, AD, PKI, Okta, SAML, OpenID Connect, OAuth, SSO, and simple email authentication. - [**Job Queue**](https://plotly.com/dash/job-queue/) The Job Queue is the key to building scalable Dash apps. Move heavy computation from synchronous Dash callbacks to the Job Queue for asynchronous background processing. @@ -53,7 +53,6 @@ Enterprise AI Features: Everything that your data science team needs to rapidly - [**GPU & Dask Acceleration**](https://plotly.com/dash/gpu-dask-acceleration/) Dash Enterprise puts Python’s most popular HPC stack for GPU and parallel CPU computing in the hands of business users. - [**Data Science Workspaces**](https://plotly.com/dash/workspaces/) Be productive from Day 1. Write and execute Python, R, & Julia code from Dash Enterprise's onboard code editor. - See [https://plotly.com/contact-us/](https://plotly.com/contact-us/) to get in touch. ![image](https://images.prismic.io/plotly-marketing-website/493eec39-8467-4610-b9d0-d6ad3ea61423_Dash+Open+source%2BDash+enterprise2-01.jpg?auto=compress,format) diff --git a/dash-renderer/dash_renderer/__init__.py b/dash-renderer/dash_renderer/__init__.py index dc9037b76a..88ccc7243d 100644 --- a/dash-renderer/dash_renderer/__init__.py +++ b/dash-renderer/dash_renderer/__init__.py @@ -1,7 +1,7 @@ import sys __file__ -__version__ = "1.9.0" +__version__ = "1.9.1" _js_dist_dependencies = [ { @@ -42,7 +42,7 @@ { "relative_package_path": "{}.min.js".format(__name__), "dev_package_path": "{}.dev.js".format(__name__), - "external_url": "https://unpkg.com/dash-renderer@1.9.0" + "external_url": "https://unpkg.com/dash-renderer@1.9.1" "/dash_renderer/dash_renderer.min.js", "namespace": "dash_renderer", }, diff --git a/dash-renderer/dash_renderer/dash_renderer.dev.js b/dash-renderer/dash_renderer/dash_renderer.dev.js index c75dd3a28a..3f46b50a3d 100644 --- a/dash-renderer/dash_renderer/dash_renderer.dev.js +++ b/dash-renderer/dash_renderer/dash_renderer.dev.js @@ -6457,7 +6457,7 @@ module.exports = exports; var ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js"); exports = ___CSS_LOADER_API_IMPORT___(false); // Module -exports.push([module.i, ".dash-error-menu {\n max-width: 50%;\n max-height: 60%;\n display: contents;\n font-family: monospace;\n font-size: 14px;\n font-variant-ligatures: common-ligatures;\n}\n\n.dash-error-card {\n box-sizing: border-box;\n background: #ffffff;\n display: inline-block;\n /* shadow-1 */\n box-shadow: 0px 6px 16px rgba(80, 103, 132, 0.165),\n 0px 2px 6px rgba(80, 103, 132, 0.12),\n 0px 0px 1px rgba(80, 103, 132, 0.32);\n border-radius: 4px;\n position: fixed;\n top: 16px;\n right: 16px;\n animation: dash-error-card-animation 0.5s;\n padding: 24px;\n text-align: left;\n background-color: white;\n\n}\n.dash-error-card--alerts-tray {\n position: absolute;\n top: -300px;\n left: -1px;\n animation: none;\n box-shadow: none;\n border: 1px solid #ececec;\n border-bottom: 0;\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n width: 422px;\n}\n.dash-error-card--container {\n padding: 10px 10px;\n width: 600px;\n max-width: 800px;\n max-height: calc(100vh - 50px);\n margin: 10px;\n overflow: auto;\n z-index: 1001; /* above the plotly.js toolbar */\n}\n\n.dash-error-card__topbar {\n width: 100%;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n position: relative;\n}\n.dash-error-card__message {\n font-size: 14px;\n}\n\n.dash-error-card__message > strong {\n color: #ff4500;\n}\n\n.dash-error-card__content {\n box-sizing: border-box;\n padding: 10px 10px;\n background-color: white;\n box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25),\n 0px 1px 3px rgba(162, 177, 198, 0.32);\n border-radius: 2px;\n margin-bottom: 8px;\n}\n\n.dash-error-card__list-item {\n background: #ffffff;\n box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25),\n 0px 1px 3px rgba(162, 177, 198, 0.32);\n border-radius: 2px;\n padding: 10px 10px;\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n}\n\n@keyframes dash-error-card-animation {\n from {\n opacity: 0;\n -webkit-transform: scale(1.1);\n -moz-transform: scale(1.1);\n -ms-transform: scale(1.1);\n transform: scale(1.1);\n }\n to {\n opacity: 1;\n -webkit-transform: scale(1);\n -moz-transform: scale(1);\n -ms-transform: scale(1);\n transform: scale(1);\n }\n}\n", ""]); +exports.push([module.i, ".dash-error-menu {\n max-width: 50%;\n max-height: 60%;\n display: contents;\n font-family: monospace;\n font-size: 14px;\n font-variant-ligatures: common-ligatures;\n}\n\n.dash-error-card {\n box-sizing: border-box;\n background: #ffffff;\n display: inline-block;\n /* shadow-1 */\n box-shadow: 0px 6px 16px rgba(80, 103, 132, 0.165),\n 0px 2px 6px rgba(80, 103, 132, 0.12),\n 0px 0px 1px rgba(80, 103, 132, 0.32);\n border-radius: 4px;\n position: fixed;\n top: 16px;\n right: 16px;\n animation: dash-error-card-animation 0.5s;\n padding: 24px;\n text-align: left;\n background-color: white;\n\n}\n.dash-error-card--alerts-tray {\n position: absolute;\n top: -300px;\n left: -1px;\n animation: none;\n box-shadow: none;\n border: 1px solid #ececec;\n border-bottom: 0;\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n width: 422px;\n}\n.dash-error-card--container {\n padding: 10px 10px;\n width: 600px;\n max-width: 800px;\n max-height: calc(100vh - 50px);\n margin: 10px;\n overflow: auto;\n z-index: 1100; /* above the plotly.js toolbar and Bootstrap components */\n}\n\n.dash-error-card__topbar {\n width: 100%;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n position: relative;\n}\n.dash-error-card__message {\n font-size: 14px;\n}\n\n.dash-error-card__message > strong {\n color: #ff4500;\n}\n\n.dash-error-card__content {\n box-sizing: border-box;\n padding: 10px 10px;\n background-color: white;\n box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25),\n 0px 1px 3px rgba(162, 177, 198, 0.32);\n border-radius: 2px;\n margin-bottom: 8px;\n}\n\n.dash-error-card__list-item {\n background: #ffffff;\n box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.25),\n 0px 1px 3px rgba(162, 177, 198, 0.32);\n border-radius: 2px;\n padding: 10px 10px;\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n}\n\n@keyframes dash-error-card-animation {\n from {\n opacity: 0;\n -webkit-transform: scale(1.1);\n -moz-transform: scale(1.1);\n -ms-transform: scale(1.1);\n transform: scale(1.1);\n }\n to {\n opacity: 1;\n -webkit-transform: scale(1);\n -moz-transform: scale(1);\n -ms-transform: scale(1);\n transform: scale(1);\n }\n}\n", ""]); // Exports module.exports = exports; @@ -97048,7 +97048,7 @@ function getEdgeTypes(node) { function updateSelectedNode(cy, id) { if (id) { // Find the subtree that the node belongs to. A subtree contains - // all all ancestors and descendents that are connected via Inputs + // all all ancestors and descendants that are connected via Inputs // or Outputs (but not State). // WARNING: No cycle detection! var ascend = function ascend(node, collection) { diff --git a/dash-renderer/digest.json b/dash-renderer/digest.json index fab2ccb9a1..7d40f9e30b 100644 --- a/dash-renderer/digest.json +++ b/dash-renderer/digest.json @@ -1,5 +1,5 @@ { - "MD5 (dash_renderer.dev.js)":"7c46c5a9e64c2d9e5e398317362b417d", + "MD5 (dash_renderer.dev.js)":"3755460c525e22187b36a2645e828737", "MD5 (dash_renderer.min.js)":"7b338826dbbc6ca44bf1ffecfea81558", "MD5 (polyfill@7.8.7.min.js)":"5090bae2c114802440412e301bdf5174", "MD5 (prop-types@15.7.2.js)":"85947944e396a28895fad5f553eee36f", @@ -8,5 +8,5 @@ "MD5 (react-dom@16.14.0.min.js)":"c5abc87541fe6bb0f43f22af475a8b20", "MD5 (react@16.14.0.js)":"d8f06b1eaf2ee5bc86a32f08fed00dc6", "MD5 (react@16.14.0.min.js)":"63d498e143f421cc44dfb64f22fef270", - "dash-renderer":"1.9.0" + "dash-renderer":"1.9.1" } \ No newline at end of file diff --git a/dash-renderer/lib/components/error/CallbackGraph/CallbackGraphEffects.js b/dash-renderer/lib/components/error/CallbackGraph/CallbackGraphEffects.js index 86e69c2291..6fd4aca08b 100644 --- a/dash-renderer/lib/components/error/CallbackGraph/CallbackGraphEffects.js +++ b/dash-renderer/lib/components/error/CallbackGraph/CallbackGraphEffects.js @@ -39,7 +39,7 @@ function getEdgeTypes(node) { function updateSelectedNode(cy, id) { if (id) { // Find the subtree that the node belongs to. A subtree contains - // all all ancestors and descendents that are connected via Inputs + // all all ancestors and descendants that are connected via Inputs // or Outputs (but not State). // WARNING: No cycle detection! var ascend = function ascend(node, collection) { diff --git a/dash-renderer/lib/components/error/GlobalErrorOverlay.css b/dash-renderer/lib/components/error/GlobalErrorOverlay.css index 955c97c160..023df8db50 100644 --- a/dash-renderer/lib/components/error/GlobalErrorOverlay.css +++ b/dash-renderer/lib/components/error/GlobalErrorOverlay.css @@ -44,7 +44,7 @@ max-height: calc(100vh - 50px); margin: 10px; overflow: auto; - z-index: 1001; /* above the plotly.js toolbar */ + z-index: 1100; /* above the plotly.js toolbar and Bootstrap components */ } .dash-error-card__topbar { diff --git a/dash-renderer/package-lock.json b/dash-renderer/package-lock.json index 6d74b4b15c..ae7adc4155 100644 --- a/dash-renderer/package-lock.json +++ b/dash-renderer/package-lock.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/dash-renderer/package.json b/dash-renderer/package.json index fb3b3a8d96..eddd79523f 100644 --- a/dash-renderer/package.json +++ b/dash-renderer/package.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "1.9.0", + "version": "1.9.1", "description": "render dash components in react", "main": "dash_renderer/dash_renderer.min.js", "scripts": { diff --git a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js index 3f42f51405..1e067bf24b 100644 --- a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js +++ b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js @@ -35,7 +35,7 @@ export function updateSelectedNode(cy, id) { node.addClass('selected-node'); // Find the subtree that the node belongs to. A subtree contains - // all all ancestors and descendents that are connected via Inputs + // all all ancestors and descendants that are connected via Inputs // or Outputs (but not State). // WARNING: No cycle detection! diff --git a/dash-renderer/src/components/error/GlobalErrorOverlay.css b/dash-renderer/src/components/error/GlobalErrorOverlay.css index 955c97c160..023df8db50 100644 --- a/dash-renderer/src/components/error/GlobalErrorOverlay.css +++ b/dash-renderer/src/components/error/GlobalErrorOverlay.css @@ -44,7 +44,7 @@ max-height: calc(100vh - 50px); margin: 10px; overflow: auto; - z-index: 1001; /* above the plotly.js toolbar */ + z-index: 1100; /* above the plotly.js toolbar and Bootstrap components */ } .dash-error-card__topbar { diff --git a/dash/_validate.py b/dash/_validate.py index d06856bc8c..71fef426c4 100644 --- a/dash/_validate.py +++ b/dash/_validate.py @@ -111,6 +111,26 @@ def validate_id_string(arg): ) +def validate_output_spec(output, output_spec, Output): + """ + This validation is for security and internal debugging, not for users, + so the messages are not intended to be clear. + `output` comes from the callback definition, `output_spec` from the request. + """ + if not isinstance(output, (list, tuple)): + output, output_spec = [output], [output_spec] + elif len(output) != len(output_spec): + raise exceptions.CallbackException("Wrong length output_spec") + + for outi, speci in zip(output, output_spec): + speci_list = speci if isinstance(speci, (list, tuple)) else [speci] + for specij in speci_list: + if not Output(specij["id"], specij["property"]) == outi: + raise exceptions.CallbackException( + "Output does not match callback definition" + ) + + def validate_multi_return(outputs_list, output_value, callback_id): if not isinstance(output_value, (list, tuple)): raise exceptions.InvalidCallbackReturnValue( diff --git a/dash/dash.py b/dash/dash.py index 34f6652bb7..16417345d5 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -27,7 +27,7 @@ from .fingerprint import build_fingerprint, check_fingerprint from .resources import Scripts, Css -from .dependencies import handle_callback_args +from .dependencies import handle_callback_args, Output from .development.base_component import ComponentRegistry from .exceptions import PreventUpdate, InvalidResourceError, ProxyError from .version import __version__ @@ -1004,6 +1004,7 @@ def wrap_func(func): @wraps(func) def add_context(*args, **kwargs): output_spec = kwargs.pop("outputs_list") + _validate.validate_output_spec(output, output_spec, Output) # don't touch the comment on the next line - used by debugger output_value = func(*args, **kwargs) # %% callback invoked %% diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 642221d678..750a0dcf89 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -172,7 +172,7 @@ def shape_or_exact(): ) ), # React's PropTypes.objectOf - objectOf=lambda: ("Dict with Strings as keys and values of type {}").format( + objectOf=lambda: "Dict with Strings as keys and values of type {}".format( get_jl_type(type_object["value"]) ), # React's PropTypes.shape @@ -265,7 +265,7 @@ def create_docstring_jl(component_name, props, description): # Ensure props are ordered with children first props = reorder_props(props=props) - return ("A{n} {name} component.\n{description}\nKeyword arguments:\n{args}").format( + return "A{n} {name} component.\n{description}\nKeyword arguments:\n{args}".format( n="n" if component_name[0].lower() in "aeiou" else "", name=component_name, description=description, diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 24928d1fcb..a9ade10760 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -1,6 +1,7 @@ from collections import OrderedDict import copy import os +from textwrap import fill from dash.development.base_component import _explicitize_args from dash.exceptions import NonExistentEventException @@ -12,16 +13,13 @@ def generate_class_string(typename, props, description, namespace): """Dynamically generate class strings to have nicely formatted docstrings, keyword arguments, and repr. - Inspired by http://jameso.be/2013/08/06/namedtuple.html - Parameters ---------- typename props description namespace - Returns ------- string @@ -53,12 +51,10 @@ def __init__(self, {default_argtext}): self.available_properties = {list_of_valid_keys} self.available_wildcard_properties =\ {list_of_valid_wildcard_attr_prefixes} - _explicit_args = kwargs.pop('_explicit_args') _locals = locals() _locals.update(kwargs) # For wildcard attrs args = {{k: _locals[k] for k in _explicit_args if k != 'children'}} - for k in {required_props}: if k not in args: raise TypeError( @@ -112,14 +108,12 @@ def __init__(self, {default_argtext}): def generate_class_file(typename, props, description, namespace): """Generate a Python class file (.py) given a class string. - Parameters ---------- typename props description namespace - Returns ------- """ @@ -170,14 +164,12 @@ def generate_classes_files(project_shortname, metadata, *component_generators): def generate_class(typename, props, description, namespace): """Generate a Python class object given a class string. - Parameters ---------- typename props description namespace - Returns ------- """ @@ -191,11 +183,9 @@ def generate_class(typename, props, description, namespace): def required_props(props): """Pull names of required props from the props object. - Parameters ---------- props: dict - Returns ------- list @@ -206,7 +196,6 @@ def required_props(props): def create_docstring(component_name, props, description): """Create the Dash component docstring. - Parameters ---------- component_name: str @@ -215,7 +204,6 @@ def create_docstring(component_name, props, description): Dictionary with {propName: propMetadata} structure description: str Component description - Returns ------- str @@ -248,12 +236,10 @@ def create_docstring(component_name, props, description): def prohibit_events(props): """Events have been removed. Raise an error if we see dashEvents or fireEvents. - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Raises ------- ? @@ -267,12 +253,10 @@ def prohibit_events(props): def parse_wildcards(props): """Pull out the wildcard attributes from the Component props. - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- list @@ -287,43 +271,37 @@ def parse_wildcards(props): def reorder_props(props): """If "children" is in props, then move it to the front to respect dash - convention. - + convention, then 'id', then the remaining props sorted by prop name Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- dict Dictionary with {propName: propMetadata} structure """ - if "children" in props: - # Constructing an OrderedDict with duplicate keys, you get the order - # from the first one but the value from the last. - # Doing this to avoid mutating props, which can cause confusion. - props = OrderedDict([("children", "")] + list(props.items())) - return props + # Constructing an OrderedDict with duplicate keys, you get the order + # from the first one but the value from the last. + # Doing this to avoid mutating props, which can cause confusion. + props1 = [("children", "")] if "children" in props else [] + props2 = [("id", "")] if "id" in props else [] + return OrderedDict(props1 + props2 + sorted(list(props.items()))) def filter_props(props): """Filter props from the Component arguments to exclude: - - Those without a "type" or a "flowType" field - Those with arg.type.name in {'func', 'symbol', 'instanceOf'} - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- dict Filtered dictionary with {propName: propMetadata} structure - Examples -------- ```python @@ -380,7 +358,18 @@ def filter_props(props): return filtered_props +def fix_keywords(txt): + """ + replaces javascript keywords true, false, null with Python keywords + """ + fix_word = {"true": "True", "false": "False", "null": "None"} + for js_keyword, python_keyword in fix_word.items(): + txt = txt.replace(js_keyword, python_keyword) + return txt + + # pylint: disable=too-many-arguments +# pylint: disable=too-many-locals def create_prop_docstring( prop_name, type_object, @@ -391,7 +380,6 @@ def create_prop_docstring( is_flow_type=False, ): """Create the Dash component prop docstring. - Parameters ---------- prop_name: str @@ -411,71 +399,110 @@ def create_prop_docstring( (creates 2 spaces for every indent) is_flow_type: bool Does the prop use Flow types? Otherwise, uses PropTypes - Returns ------- str Dash component prop docstring """ py_type_name = js_to_py_type( - type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num + 1 + type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num ) indent_spacing = " " * indent_num - if default is None: - default = "" - else: - default = default["value"] - - if default in ["true", "false"]: - default = default.title() + default = default["value"] if default else "" + default = fix_keywords(default) is_required = "optional" if required: is_required = "required" - elif default and default not in ["null", "{}", "[]"]: - is_required = "default {}".format(default.replace("\n", "\n" + indent_spacing)) + elif default and default not in ["None", "{}", "[]"]: + is_required = "default {}".format(default.replace("\n", "")) + + # formats description + period = "." if description else "" + description = description.strip().strip(".").replace('"', r"\"") + period + desc_indent = indent_spacing + " " + description = fill( + description, + initial_indent=desc_indent, + subsequent_indent=desc_indent, + break_long_words=False, + break_on_hyphens=False, + ) + description = "\n{}".format(description) if description else "" + colon = ":" if description else "" + description = fix_keywords(description) if "\n" in py_type_name: + # corrects the type + dict_or_list = "list of dicts" if py_type_name.startswith("list") else "dict" + + # format and rewrite the intro to the nested dicts + intro1, intro2, dict_descr = py_type_name.partition("with keys:") + intro = "".join(["`{}`".format(prop_name), " is a ", intro1, intro2]) + intro = fill( + intro, + initial_indent=desc_indent, + subsequent_indent=desc_indent, + break_long_words=False, + break_on_hyphens=False, + ) + + # captures optional nested dict description and puts the "or" condition on a new line + if "| dict with keys:" in dict_descr: + dict_part1, dict_part2 = dict_descr.split(" |", 1) + dict_part2 = "".join([desc_indent, "Or", dict_part2]) + dict_descr = "{}\n\n {}".format(dict_part1, dict_part2) + + # ensures indent is correct if there is a second nested list of dicts + current_indent = dict_descr.lstrip("\n").find("-") + if current_indent == len(indent_spacing): + dict_descr = "".join( + "\n\n " + line for line in dict_descr.splitlines() if line != "" + ) + return ( - "{indent_spacing}- {name} (dict; {is_required}): " - "{description}{period}" - "{name} has the following type: {type}".format( + "\n{indent_spacing}- {name} ({dict_or_list}; {is_required}){colon}" + "{description}" + "\n\n{intro}{dict_descr}".format( indent_spacing=indent_spacing, name=prop_name, - type=py_type_name, - description=description.strip().strip("."), - period=". " if description else "", + colon=colon, + description=description, + intro=intro, + dict_descr=dict_descr, + dict_or_list=dict_or_list, is_required=is_required, ) ) - return "{indent_spacing}- {name} ({type}{is_required}){description}".format( - indent_spacing=indent_spacing, - name=prop_name, - type="{}; ".format(py_type_name) if py_type_name else "", - description=(": {}".format(description) if description != "" else ""), - is_required=is_required, + return ( + "\n{indent_spacing}- {name} ({type}{is_required}){colon}" + "{description}".format( + indent_spacing=indent_spacing, + name=prop_name, + type="{}; ".format(py_type_name) if py_type_name else "", + colon=colon, + description=description, + is_required=is_required, + ) ) -def map_js_to_py_types_prop_types(type_object): +def map_js_to_py_types_prop_types(type_object, indent_num): """Mapping from the PropTypes js type object to the Python type.""" def shape_or_exact(): - return "dict containing keys {}.\n{}".format( - ", ".join("'{}'".format(t) for t in list(type_object["value"].keys())), - "Those keys have the following types:\n{}".format( - "\n".join( - create_prop_docstring( - prop_name=prop_name, - type_object=prop, - required=prop["required"], - description=prop.get("description", ""), - default=prop.get("defaultValue"), - indent_num=1, - ) - for prop_name, prop in list(type_object["value"].items()) + return "dict with keys:\n{}".format( + "\n".join( + create_prop_docstring( + prop_name=prop_name, + type_object=prop, + required=prop["required"], + description=prop.get("description", ""), + default=prop.get("defaultValue"), + indent_num=indent_num + 2, ) + for prop_name, prop in sorted(list(type_object["value"].items())) ), ) @@ -526,7 +553,6 @@ def shape_or_exact(): def map_js_to_py_types_flow_types(type_object): """Mapping from the Flow js types to the Python type.""" - return dict( array=lambda: "list", boolean=lambda: "boolean", @@ -551,24 +577,18 @@ def map_js_to_py_types_flow_types(type_object): else "" ), # React's PropTypes.shape - signature=lambda indent_num: "dict containing keys {}.\n{}".format( - ", ".join( - "'{}'".format(d["key"]) for d in type_object["signature"]["properties"] - ), - "{}Those keys have the following types:\n{}".format( - " " * indent_num, - "\n".join( - create_prop_docstring( - prop_name=prop["key"], - type_object=prop["value"], - required=prop["value"]["required"], - description=prop["value"].get("description", ""), - default=prop.get("defaultValue"), - indent_num=indent_num, - is_flow_type=True, - ) - for prop in type_object["signature"]["properties"] - ), + signature=lambda indent_num: "dict with keys:\n{}".format( + "\n".join( + create_prop_docstring( + prop_name=prop["key"], + type_object=prop["value"], + required=prop["value"]["required"], + description=prop["value"].get("description", ""), + default=prop.get("defaultValue"), + indent_num=indent_num + 2, + is_flow_type=True, + ) + for prop in type_object["signature"]["properties"] ), ), ) @@ -576,7 +596,6 @@ def map_js_to_py_types_flow_types(type_object): def js_to_py_type(type_object, is_flow_type=False, indent_num=0): """Convert JS types to Python types for the component definition. - Parameters ---------- type_object: dict @@ -585,17 +604,19 @@ def js_to_py_type(type_object, is_flow_type=False, indent_num=0): Does the prop use Flow types? Otherwise, uses PropTypes indent_num: int Number of indents to use for the docstring for the prop - Returns ------- str Python type string """ + js_type_name = type_object["name"] js_to_py_types = ( map_js_to_py_types_flow_types(type_object=type_object) if is_flow_type - else map_js_to_py_types_prop_types(type_object=type_object) + else map_js_to_py_types_prop_types( + type_object=type_object, indent_num=indent_num + ) ) if ( diff --git a/dash/favicon.ico b/dash/favicon.ico old mode 100644 new mode 100755 index 465bf58751..44954a594a Binary files a/dash/favicon.ico and b/dash/favicon.ico differ diff --git a/dash/renovate.json b/dash/renovate.json index c1f20f8e9f..7c31c26082 100644 --- a/dash/renovate.json +++ b/dash/renovate.json @@ -7,17 +7,17 @@ "enabled": true }, "pip_requirements": { - "fileMatch": ["requirements.txt", "dev-requirements.txt", "dev-requirements-py37.txt"] + "fileMatch": ["requirements.txt", "dev-requirements.txt", "dev-requirements-py37.txt"] }, "pip_setup": { - "enabled": true + "enabled": true }, "packageRules": [{ "packageNames": [ "pylint" ], "paths": [ - "dev-requirements.txt" + "dev-requirements.txt" ], "allowedVersions": "<2.0.0" }] diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 4a09d2a19b..7296481094 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -77,10 +77,10 @@ def __init__( ) self.percy_runner.initialize_build() - logger.info("initialize browser with arguments") - logger.info(" headless => %s", self._headless) - logger.info(" download_path => %s", self._download_path) - logger.info(" percy asset root => %s", os.path.abspath(percy_assets_root)) + logger.debug("initialize browser with arguments") + logger.debug(" headless => %s", self._headless) + logger.debug(" download_path => %s", self._download_path) + logger.debug(" percy asset root => %s", os.path.abspath(percy_assets_root)) def __enter__(self): return self diff --git a/dash/version.py b/dash/version.py index d84d79d43f..0dcddbc87b 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = "1.19.0" +__version__ = "1.20.0" diff --git a/requires-install.txt b/requires-install.txt index 141271a8d8..86c7817990 100644 --- a/requires-install.txt +++ b/requires-install.txt @@ -1,8 +1,8 @@ Flask>=1.0.4 flask-compress plotly -dash_renderer==1.9.0 -dash-core-components==1.15.0 -dash-html-components==1.1.2 -dash-table==4.11.2 +dash_renderer==1.9.1 +dash-core-components==1.16.0 +dash-html-components==1.1.3 +dash-table==4.11.3 future diff --git a/requires-testing.txt b/requires-testing.txt index 8634ae4e8e..eeec35eb0e 100644 --- a/requires-testing.txt +++ b/requires-testing.txt @@ -6,6 +6,7 @@ pytest-mock>=2.0.0,<3;python_version=="2.7" lxml>=4.6.2 selenium>=3.141.0 percy>=2.0.2 +cryptography<3.4;python_version<"3.7" requests[security]>=2.21.0 beautifulsoup4>=4.8.2,<=4.9.3;python_version=="2.7" beautifulsoup4>=4.8.2;python_version>="3.0" diff --git a/tests/integration/callbacks/test_malformed_request.py b/tests/integration/callbacks/test_malformed_request.py new file mode 100644 index 0000000000..c815fc1b9d --- /dev/null +++ b/tests/integration/callbacks/test_malformed_request.py @@ -0,0 +1,59 @@ +import requests + + +import dash_core_components as dcc +import dash_html_components as html +import dash +from dash.dependencies import Input, Output + + +def test_cbmf001_bad_output_outputs(dash_thread_server): + app = dash.Dash(__name__) + app.layout = html.Div( + [ + dcc.Input(id="i", value="initial value"), + html.Div(html.Div([1.5, None, "string", html.Div(id="o1")])), + ] + ) + + @app.callback(Output("o1", "children"), [Input("i", "value")]) + def update_output(value): + return value + + dash_thread_server(app) + + # first a good request + response = requests.post( + dash_thread_server.url + "/_dash-update-component", + json=dict( + output="o1.children", + outputs={"id": "o1", "property": "children"}, + inputs=[{"id": "i", "property": "value", "value": 9}], + changedPropIds=["i.value"], + ), + ) + assert response.status_code == 200 + assert '"o1": {"children": 9}' in response.text + + # now some bad ones + outspecs = [ + {"output": "o1.nope", "outputs": {"id": "o1", "property": "nope"}}, + {"output": "o1.children", "outputs": {"id": "o1", "property": "nope"}}, + {"output": "o1.nope", "outputs": {"id": "o1", "property": "children"}}, + {"output": "o1.children", "outputs": {"id": "nope", "property": "children"}}, + {"output": "nope.children", "outputs": {"id": "nope", "property": "children"}}, + ] + for outspeci in outspecs: + response = requests.post( + dash_thread_server.url + "/_dash-update-component", + json=dict( + inputs=[{"id": "i", "property": "value", "value": 9}], + changedPropIds=["i.value"], + **outspeci + ), + ) + assert response.status_code == 500 + assert "o1" not in response.text + assert "children" not in response.text + assert "nope" not in response.text + assert "500 Internal Server Error" in response.text diff --git a/tests/integration/callbacks/test_prevent_update.py b/tests/integration/callbacks/test_prevent_update.py index e5f122f602..6b4183efbb 100644 --- a/tests/integration/callbacks/test_prevent_update.py +++ b/tests/integration/callbacks/test_prevent_update.py @@ -87,7 +87,7 @@ def test_cbpu002_multi_output_no_update(dash_duo): [Input("btn", "n_clicks")], ) def show_clicks(n): - # partial or complete cancelation of updates via no_update + # partial or complete cancellation of updates via no_update return [ no_update if n and n > 4 else n, no_update if n and n > 2 else n, diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index 189209f4a1..22ed40bb0a 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -1,5 +1,3 @@ -from multiprocessing import Value - import dash from dash.dependencies import Input, Output from dash.exceptions import PreventUpdate @@ -9,7 +7,6 @@ def test_rdif001_sandbox_allow_scripts(dash_duo): app = dash.Dash(__name__) - call_count = Value("i") N_OUTPUTS = 50 @@ -26,7 +23,6 @@ def update_output(n_clicks): if n_clicks is None: raise PreventUpdate - call_count.value += 1 return ["{}={}".format(i, i + n_clicks) for i in range(N_OUTPUTS)] @app.server.after_request @@ -39,6 +35,11 @@ def apply_cors(response): dash_duo.start_server(app) + dash_duo.find_element("#btn").click() + dash_duo.wait_for_element("#output-0").text == "0=1" + + assert dash_duo.get_logs() == [] + iframe = """ @@ -53,8 +54,9 @@ def apply_cors(response): dash_duo.driver.switch_to.frame(0) - dash_duo.wait_for_element("#output-0") - dash_duo.wait_for_element_by_id("btn").click() - dash_duo.wait_for_element("#output-0").text == "0=1" + assert dash_duo.get_logs() == [] + + dash_duo.find_element("#btn").click() + dash_duo.wait_for_text_to_equal("#output-0", "0=1") assert dash_duo.get_logs() == [] diff --git a/tests/unit/development/__init__.py b/tests/unit/development/__init__.py index b8d2f0b34c..9ec687e80d 100644 --- a/tests/unit/development/__init__.py +++ b/tests/unit/development/__init__.py @@ -13,51 +13,81 @@ def has_trailing_space(s): "It's multiple lines long.", "", "Keyword arguments:", + "", "- children (a list of or a singular dash component, string or number; optional)", - "- optionalArray (list; optional): Description of optionalArray", + "", + "- id (string; optional)", + "", + "- aria-* (string; optional)", + "", + "- customArrayProp (list; optional)", + "", + "- customProp (optional)", + "", + "- data-* (string; optional)", + "", + "- in (string; optional)", + "", + "- optionalAny (boolean | number | string | dict | list; optional)", + "", + "- optionalArray (list; optional):", + " Description of optionalArray.", + "", + "- optionalArrayOf (list of numbers; optional)", + "", "- optionalBool (boolean; optional)", + "", + "- optionalElement (dash component; optional)", + "", + "- optionalEnum (a value equal to: 'News'," " 'Photos'; optional)", + "", + "- optionalNode (a list of or a singular dash component, string or number; optional)", + "", "- optionalNumber (number; default 42)", + "", "- optionalObject (dict; optional)", + "", + "- optionalObjectOf (dict with strings as keys and values of type number; optional)", + "", + "- optionalObjectWithExactAndNestedDescription (dict; optional)", + "", + " `optionalObjectWithExactAndNestedDescription` is a dict with keys:", + "", + " - color (string; optional)", + "", + " - figure (dict; optional):", + " Figure is a plotly graph object.", + "", + " `figure` is a dict with keys:", + "", + " - data (list of dicts; optional):", + " data is a collection of traces.", + "", + " - layout (dict; optional):", + " layout describes the rest of the figure.", + "", + " - fontSize (number; optional)", + "", + "- optionalObjectWithShapeAndNestedDescription (dict; optional)", + "", + " `optionalObjectWithShapeAndNestedDescription` is a dict with keys:", + "", + " - color (string; optional)", + "", + " - figure (dict; optional):", + " Figure is a plotly graph object.", + "", + " `figure` is a dict with keys:", + "", + " - data (list of dicts; optional):", + " data is a collection of traces.", + "", + " - layout (dict; optional):", + " layout describes the rest of the figure.", + "", + " - fontSize (number; optional)", + "", "- optionalString (string; default 'hello world')", - "- optionalNode (a list of or a singular dash component, " - "string or number; optional)", - "- optionalElement (dash component; optional)", - "- optionalEnum (a value equal to: 'News', 'Photos'; optional)", + "", "- optionalUnion (string | number; optional)", - "- optionalArrayOf (list of numbers; optional)", - "- optionalObjectOf (dict with strings as keys and values " - "of type number; optional)", - "- optionalObjectWithExactAndNestedDescription (dict; optional): " - "optionalObjectWithExactAndNestedDescription has the " - "following type: dict containing keys " - "'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. " - "figure has the following type: dict containing " - "keys 'data', 'layout'.", - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes " "the rest of the figure", - "- optionalObjectWithShapeAndNestedDescription (dict; optional): " - "optionalObjectWithShapeAndNestedDescription has the " - "following type: dict containing keys " - "'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. " - "figure has the following type: dict containing " - "keys 'data', 'layout'.", - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes " "the rest of the figure", - "- optionalAny (boolean | number | string | dict | " "list; optional)", - "- customProp (optional)", - "- customArrayProp (list; optional)", - "- data-* (string; optional)", - "- aria-* (string; optional)", - "- in (string; optional)", - "- id (string; optional)", ] diff --git a/tests/unit/development/metadata_test.py b/tests/unit/development/metadata_test.py index 042b443851..c7440d2090 100644 --- a/tests/unit/development/metadata_test.py +++ b/tests/unit/development/metadata_test.py @@ -9,57 +9,97 @@ class Table(Component): It's multiple lines long. Keyword arguments: + - children (a list of or a singular dash component, string or number; optional) -- optionalArray (list; optional): Description of optionalArray + +- id (string; optional) + +- aria-* (string; optional) + +- customArrayProp (list; optional) + +- customProp (optional) + +- data-* (string; optional) + +- in (string; optional) + +- optionalAny (boolean | number | string | dict | list; optional) + +- optionalArray (list; optional): + Description of optionalArray. + +- optionalArrayOf (list of numbers; optional) + - optionalBool (boolean; optional) -- optionalNumber (number; default 42) -- optionalObject (dict; optional) -- optionalString (string; default 'hello world') -- optionalNode (a list of or a singular dash component, string or number; optional) + - optionalElement (dash component; optional) + - optionalEnum (a value equal to: 'News', 'Photos'; optional) -- optionalUnion (string | number; optional) -- optionalArrayOf (list of numbers; optional) + +- optionalNode (a list of or a singular dash component, string or number; optional) + +- optionalNumber (number; default 42) + +- optionalObject (dict; optional) + - optionalObjectOf (dict with strings as keys and values of type number; optional) -- optionalObjectWithExactAndNestedDescription (dict; optional): optionalObjectWithExactAndNestedDescription has the following type: dict containing keys 'color', 'fontSize', 'figure'. -Those keys have the following types: - - color (string; optional) - - fontSize (number; optional) - - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'. -Those keys have the following types: - - data (list of dicts; optional): data is a collection of traces - - layout (dict; optional): layout describes the rest of the figure -- optionalObjectWithShapeAndNestedDescription (dict; optional): optionalObjectWithShapeAndNestedDescription has the following type: dict containing keys 'color', 'fontSize', 'figure'. -Those keys have the following types: - - color (string; optional) - - fontSize (number; optional) - - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'. -Those keys have the following types: - - data (list of dicts; optional): data is a collection of traces - - layout (dict; optional): layout describes the rest of the figure -- optionalAny (boolean | number | string | dict | list; optional) -- customProp (optional) -- customArrayProp (list; optional) -- data-* (string; optional) -- aria-* (string; optional) -- in (string; optional) -- id (string; optional)""" + +- optionalObjectWithExactAndNestedDescription (dict; optional) + + `optionalObjectWithExactAndNestedDescription` is a dict with keys: + + - color (string; optional) + + - figure (dict; optional): + Figure is a plotly graph object. + + `figure` is a dict with keys: + + - data (list of dicts; optional): + data is a collection of traces. + + - layout (dict; optional): + layout describes the rest of the figure. + + - fontSize (number; optional) + +- optionalObjectWithShapeAndNestedDescription (dict; optional) + + `optionalObjectWithShapeAndNestedDescription` is a dict with keys: + + - color (string; optional) + + - figure (dict; optional): + Figure is a plotly graph object. + + `figure` is a dict with keys: + + - data (list of dicts; optional): + data is a collection of traces. + + - layout (dict; optional): + layout describes the rest of the figure. + + - fontSize (number; optional) + +- optionalString (string; default 'hello world') + +- optionalUnion (string | number; optional)""" @_explicitize_args def __init__(self, children=None, optionalArray=Component.UNDEFINED, optionalBool=Component.UNDEFINED, optionalFunc=Component.UNDEFINED, optionalNumber=Component.UNDEFINED, optionalObject=Component.UNDEFINED, optionalString=Component.UNDEFINED, optionalSymbol=Component.UNDEFINED, optionalNode=Component.UNDEFINED, optionalElement=Component.UNDEFINED, optionalMessage=Component.UNDEFINED, optionalEnum=Component.UNDEFINED, optionalUnion=Component.UNDEFINED, optionalArrayOf=Component.UNDEFINED, optionalObjectOf=Component.UNDEFINED, optionalObjectWithExactAndNestedDescription=Component.UNDEFINED, optionalObjectWithShapeAndNestedDescription=Component.UNDEFINED, optionalAny=Component.UNDEFINED, customProp=Component.UNDEFINED, customArrayProp=Component.UNDEFINED, id=Component.UNDEFINED, **kwargs): - self._prop_names = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id'] + self._prop_names = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion'] self._type = 'Table' self._namespace = 'TableComponents' self._valid_wildcard_attributes = ['data-', 'aria-'] - self.available_properties = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id'] + self.available_properties = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion'] self.available_wildcard_properties = ['data-', 'aria-'] - _explicit_args = kwargs.pop('_explicit_args') _locals = locals() _locals.update(kwargs) # For wildcard attrs args = {k: _locals[k] for k in _explicit_args if k != 'children'} - for k in []: if k not in args: raise TypeError( 'Required argument `' + k + '` was not specified.') - super(Table, self).__init__(children=children, **args) \ No newline at end of file + super(Table, self).__init__(children=children, **args) diff --git a/tests/unit/development/test_component_loader.py b/tests/unit/development/test_component_loader.py index 4c525fdf6d..aa30ed1c3d 100644 --- a/tests/unit/development/test_component_loader.py +++ b/tests/unit/development/test_component_loader.py @@ -103,7 +103,7 @@ @pytest.fixture -def write_metada_file(): +def write_metadata_file(): with open(METADATA_PATH, "w") as f: f.write(METADATA_STRING) yield @@ -120,7 +120,7 @@ def make_namespace(): shutil.rmtree("default_namespace") -def test_loadcomponents(write_metada_file): +def test_loadcomponents(write_metadata_file): my_component = generate_class( "MyComponent", METADATA["MyComponent.react.js"]["props"], @@ -156,7 +156,7 @@ def test_loadcomponents(write_metada_file): assert repr(a_component(**a_kwargs)) == repr(c[1](**a_kwargs)) -def test_loadcomponents_from_generated_class(write_metada_file, make_namespace): +def test_loadcomponents_from_generated_class(write_metadata_file, make_namespace): my_component_runtime = generate_class( "MyComponent", METADATA["MyComponent.react.js"]["props"], diff --git a/tests/unit/development/test_flow_metadata_conversions.py b/tests/unit/development/test_flow_metadata_conversions.py index 822df417e9..cf2b10d9e7 100644 --- a/tests/unit/development/test_flow_metadata_conversions.py +++ b/tests/unit/development/test_flow_metadata_conversions.py @@ -1,3 +1,4 @@ +# flake8: ignore=E501 import json import os from collections import OrderedDict @@ -27,17 +28,18 @@ "optionalSignature(shape)", "\n".join( [ - "dict containing keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', 'style', 'value'.", - "Those keys have the following types:", - "- checked (boolean; optional)", - "- children (a list of or a singular dash component, string or number; optional)", - "- customData (bool | number | str | dict | list; required): A test description", - "- disabled (boolean; optional)", - "- label (string; optional)", - "- primaryText (string; required): Another test description", - "- secondaryText (string; optional)", - "- style (dict; optional)", - "- value (bool | number | str | dict | list; required)", + "dict with keys:\n", + " - checked (boolean; optional)\n", + " - children (a list of or a singular dash component, string or number; optional)\n", + " - customData (bool | number | str | dict | list; required):\n" + " A test description.\n", + " - disabled (boolean; optional)\n", + " - label (string; optional)\n", + " - primaryText (string; required):\n" + " Another test description.\n", + " - secondaryText (string; optional)\n", + " - style (dict; optional)\n", + " - value (bool | number | str | dict | list; required)", ] ), ], @@ -45,20 +47,19 @@ "requiredNested", "\n".join( [ - "dict containing keys 'customData', 'value'.", - "Those keys have the following types:", - "- customData (dict; required): customData has the following type: dict containing keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', 'style', 'value'.", - " Those keys have the following types:", - " - checked (boolean; optional)", - " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required)", - " - disabled (boolean; optional)", - " - label (string; optional)", - " - primaryText (string; required)", - " - secondaryText (string; optional)", - " - style (dict; optional)", - " - value (bool | number | str | dict | list; required)", - "- value (bool | number | str | dict | list; required)", + "dict with keys:\n", + " - customData (dict; required)\n\n" + " `customData` is a dict with keys:\n", + " - checked (boolean; optional)\n", + " - children (a list of or a singular dash component, string or number; optional)\n", + " - customData (bool | number | str | dict | list; required)\n", + " - disabled (boolean; optional)\n", + " - label (string; optional)\n", + " - primaryText (string; required)\n", + " - secondaryText (string; optional)\n", + " - style (dict; optional)\n", + " - value (bool | number | str | dict | list; required)\n", + " - value (bool | number | str | dict | list; required)", ] ), ], @@ -71,46 +72,78 @@ "It's multiple lines long.", "", "Keyword arguments:", - "- requiredString (string; required): A required string", - "- optionalString (string; default ''): A string that isn't required.", - "- optionalBoolean (boolean; default False): A boolean test", - "- optionalNode (a list of or a singular dash component, string or number; optional): " - "A node test", - "- optionalArray (list; optional): An array test with a particularly ", - "long description that covers several lines. It includes the newline character ", - "and should span 3 lines in total.", - "- requiredUnion (string | number; required)", - "- optionalSignature(shape) (dict; optional): This is a test of an object's shape. " - "optionalSignature(shape) has the following type: dict containing keys 'checked', " - "'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', " - "'style', 'value'.", - " Those keys have the following types:", - " - checked (boolean; optional)", - " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required): A test description", - " - disabled (boolean; optional)", - " - label (string; optional)", - " - primaryText (string; required): Another test description", - " - secondaryText (string; optional)", - " - style (dict; optional)", - " - value (bool | number | str | dict | list; required)", - "- requiredNested (dict; required): requiredNested has the following type: dict containing " - "keys 'customData', 'value'.", - " Those keys have the following types:", - " - customData (dict; required): customData has the following type: dict containing " - "keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', " - "'secondaryText', 'style', 'value'.", - " Those keys have the following types:", + "", + "- optionalArray (list; optional):", + " An array test with a particularly long description that covers", + " several lines. It includes the newline character and should span", + " 3 lines in total.", + "", + "- optionalBoolean (boolean; default False):", + " A boolean test.", + "", + "- optionalNode (a list of or a singular dash component, string or number; optional):", + " A node test.", + "", + "- optionalSignature(shape) (dict; optional):", + " This is a test of an object's shape.", + "", + " `optionalSignature(shape)` is a dict with keys:", + "", " - checked (boolean; optional)", + "", " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required)", + "", + " - customData (bool | number | str | dict | list; required):", + " A test description.", + "", " - disabled (boolean; optional)", + "", " - label (string; optional)", - " - primaryText (string; required)", + "", + " - primaryText (string; required):", + " Another test description.", + "", " - secondaryText (string; optional)", + "", " - style (dict; optional)", + "", " - value (bool | number | str | dict | list; required)", - " - value (bool | number | str | dict | list; required)", + "", + "- optionalString (string; default ''):", + " A string that isn't required.", + "", + "- requiredNested (dict; required)", + "", + " `requiredNested` is a dict with keys:", + "", + " - customData (dict; required)", + "", + " `customData` is a dict with keys:", + "", + " - checked (boolean; optional)", + "", + " - children (a list of or a singular dash component, string or number; optional)", + "", + " - customData (bool | number | str | dict | list; required)", + "", + " - disabled (boolean; optional)", + "", + " - label (string; optional)", + "", + " - primaryText (string; required)", + "", + " - secondaryText (string; optional)", + "", + " - style (dict; optional)", + "", + " - value (bool | number | str | dict | list; required)", + "", + " - value (bool | number | str | dict | list; required)", + "", + "- requiredString (string; required):", + " A required string.", + "", + "- requiredUnion (string | number; required)", ] @@ -129,13 +162,13 @@ def test_docstring(load_test_flow_metadata_json): load_test_flow_metadata_json["props"], load_test_flow_metadata_json["description"], ) + print(docstring.splitlines()) prohibit_events(load_test_flow_metadata_json["props"]), assert not list(unified_diff(expected_doc, docstring.splitlines())) def test_docgen_to_python_args(load_test_flow_metadata_json): props = load_test_flow_metadata_json["props"] - for prop_name, prop in list(props.items()): assert ( js_to_py_type(prop["flowType"], is_flow_type=True) diff --git a/tests/unit/development/test_generate_class.py b/tests/unit/development/test_generate_class.py index 49ba7d5684..2c7c0de494 100644 --- a/tests/unit/development/test_generate_class.py +++ b/tests/unit/development/test_generate_class.py @@ -83,7 +83,7 @@ def test_repr_multiple_arguments(component_class): # Note how the order in which keyword arguments are supplied is # not always equal to the order in the repr of the component c = component_class(id="my id", optionalArray=[1, 2, 3]) - assert repr(c) == "Table(optionalArray=[1, 2, 3], id='my id')" + assert repr(c) == "Table(id='my id', optionalArray=[1, 2, 3])" def test_repr_nested_arguments(component_class): diff --git a/tests/unit/development/test_generate_class_file.py b/tests/unit/development/test_generate_class_file.py index eecc2ba34e..b3ba34109b 100644 --- a/tests/unit/development/test_generate_class_file.py +++ b/tests/unit/development/test_generate_class_file.py @@ -67,7 +67,6 @@ def test_class_string(expected_class_string, component_class_string): expected_class_string.splitlines(), component_class_string.splitlines() ) ) - assert not has_trailing_space(component_class_string) diff --git a/tests/unit/development/test_metadata_conversions.py b/tests/unit/development/test_metadata_conversions.py index 008efebe66..23203821bf 100644 --- a/tests/unit/development/test_metadata_conversions.py +++ b/tests/unit/development/test_metadata_conversions.py @@ -29,15 +29,18 @@ "optionalObjectWithExactAndNestedDescription", "\n".join( [ - "dict containing keys 'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.", + "dict with keys:\n", + " - color (string; optional)\n", + " - figure (dict; optional):", + " Figure is a plotly graph object.\n", + " `figure` is a dict with keys:\n", # noqa: E501 - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes the rest of the figure", # noqa: E501 + " - data (list of dicts; optional):", + " data is a collection of traces.\n", + " - layout (dict; optional):", + " layout describes the rest of the figure.\n", + # noqa: E501 + " - fontSize (number; optional)", ] ), ], @@ -45,16 +48,19 @@ "optionalObjectWithShapeAndNestedDescription", "\n".join( [ - "dict containing keys 'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.", + "dict with keys:\n", + " - color (string; optional)\n", + " - figure (dict; optional):", + " Figure is a plotly graph object.\n", + " `figure` is a dict with keys:\n", # noqa: E501 - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes the rest of the figure", # noqa: E501 - ] + " - data (list of dicts; optional):", + " data is a collection of traces.\n", + " - layout (dict; optional):", + " layout describes the rest of the figure.\n", + # noqa: E501 + " - fontSize (number; optional)", + ], ), ], ["optionalAny", "boolean | number | string | dict | list"],