diff --git a/.gitignore b/.gitignore index d46df1068e..7de426474d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ gradio/templates/frontend/static *.db *.sqlite3 gradio/launches.json -flagged +flagged/* # Tests .coverage diff --git a/frontend/package-lock.json b/frontend/package-lock.json index de1b020218..2d0ac6fa61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,7 +1,7 @@ { "name": "gradio", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, "packages": { "": { @@ -25990,13 +25990,13 @@ "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" }, "acorn-node": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, "requires": { "acorn": "^7.0.0", "acorn-walk": "^7.0.0", @@ -26054,14 +26054,12 @@ "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "requires": {} + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "requires": {} + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, "alphanum-sort": { "version": "1.0.2", @@ -26152,7 +26150,8 @@ "arg": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", - "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==" + "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", + "dev": true }, "argparse": { "version": "1.0.10", @@ -26530,8 +26529,7 @@ "babel-plugin-named-asset-import": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", - "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", - "requires": {} + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==" }, "babel-plugin-polyfill-corejs2": { "version": "0.2.0", @@ -26915,6 +26913,15 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -27257,7 +27264,8 @@ "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true }, "camelcase-keys": { "version": "6.2.2", @@ -28114,7 +28122,8 @@ "css-unit-converter": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", + "dev": true }, "css-what": { "version": "3.4.2", @@ -28498,7 +28507,8 @@ "defined": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true }, "del": { "version": "4.1.1", @@ -28630,6 +28640,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, "requires": { "acorn-node": "^1.6.1", "defined": "^1.0.0", @@ -28639,7 +28650,8 @@ "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true }, "diff-sequences": { "version": "26.6.2", @@ -29213,8 +29225,7 @@ "eslint-config-prettier": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "requires": {} + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==" }, "eslint-config-react-app": { "version": "6.0.0", @@ -29400,8 +29411,7 @@ "eslint-plugin-react-hooks": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "requires": {} + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==" }, "eslint-plugin-testing-library": { "version": "3.10.2", @@ -30223,6 +30233,12 @@ } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "filelist": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", @@ -31159,7 +31175,8 @@ "html-tags": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "dev": true }, "html-webpack-plugin": { "version": "4.5.0", @@ -32435,8 +32452,7 @@ "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "requires": {} + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" }, "jest-regex-util": { "version": "26.0.0", @@ -33018,7 +33034,8 @@ "lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==" + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true }, "lines-and-columns": { "version": "1.1.6", @@ -33150,7 +33167,8 @@ "lodash.topath": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", - "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=" + "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=", + "dev": true }, "lodash.truncate": { "version": "4.4.2", @@ -33616,7 +33634,8 @@ "modern-normalize": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz", - "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==" + "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", + "dev": true }, "move-concurrently": { "version": "1.0.1", @@ -33740,6 +33759,7 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, "requires": { "lodash": "^4.17.21" } @@ -34009,7 +34029,8 @@ "object-hash": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true }, "object-inspect": { "version": "1.9.0", @@ -34413,7 +34434,8 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "picomatch": { "version": "2.2.2", @@ -34913,6 +34935,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz", "integrity": "sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=", + "dev": true, "requires": { "glob": "^7.1.2", "object-assign": "^4.1.1", @@ -34924,6 +34947,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -34932,6 +34956,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -34942,6 +34967,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -34949,22 +34975,26 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "postcss": { "version": "6.0.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, "requires": { "chalk": "^2.4.1", "source-map": "^0.6.1", @@ -34974,12 +35004,14 @@ "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -35016,6 +35048,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-2.0.3.tgz", "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==", + "dev": true, "requires": { "camelcase-css": "^2.0.1", "postcss": "^7.0.18" @@ -35294,6 +35327,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", + "dev": true, "requires": { "postcss": "^7.0.32", "postcss-selector-parser": "^6.0.2" @@ -36485,7 +36519,8 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true }, "process": { "version": "0.11.10", @@ -36619,6 +36654,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", "integrity": "sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==", + "dev": true, "requires": { "commander": "^8.0.0", "glob": "^7.1.7", @@ -36629,12 +36665,14 @@ "commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -36647,12 +36685,14 @@ "nanoid": { "version": "3.1.30", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==" + "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "dev": true }, "postcss": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "dev": true, "requires": { "nanoid": "^3.1.30", "picocolors": "^1.0.0", @@ -36663,6 +36703,7 @@ "version": "6.0.7", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz", "integrity": "sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA==", + "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -36712,7 +36753,8 @@ "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true }, "raf": { "version": "3.4.1", @@ -36800,8 +36842,7 @@ "react-chartjs-2": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz", - "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==", - "requires": {} + "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==" }, "react-cropper": { "version": "2.1.8", @@ -37073,8 +37114,7 @@ "react-webcam": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/react-webcam/-/react-webcam-5.2.3.tgz", - "integrity": "sha512-7rBh4Veeumpy45ObkZCsG6zezaamkwK5Iqxc0AFhX2ZzWQZXZ+f61yJ1yIb9Y62U4d7KkfP/xEduBCRzzcN4Dw==", - "requires": {} + "integrity": "sha512-7rBh4Veeumpy45ObkZCsG6zezaamkwK5Iqxc0AFhX2ZzWQZXZ+f61yJ1yIb9Y62U4d7KkfP/xEduBCRzzcN4Dw==" }, "read-pkg": { "version": "2.0.0", @@ -37202,6 +37242,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "dev": true, "requires": { "css-unit-converter": "^1.1.1", "postcss-value-parser": "^3.3.0" @@ -37210,7 +37251,8 @@ "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true } } }, @@ -38691,7 +38733,8 @@ "source-map-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz", - "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==" + "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==", + "dev": true }, "source-map-resolve": { "version": "0.6.0", @@ -38997,21 +39040,6 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -39075,6 +39103,21 @@ "define-properties": "^1.1.3" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -39294,6 +39337,7 @@ "version": "npm:@tailwindcss/postcss7-compat@2.2.17", "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.2.17.tgz", "integrity": "sha512-3h2svqQAqYHxRZ1KjsJjZOVTQ04m29LjfrLjXyZZEJuvUuJN+BCIF9GI8vhE1s0plS0mogd6E6YLg6mu4Wv/Vw==", + "dev": true, "requires": { "arg": "^5.0.1", "autoprefixer": "^9", @@ -39336,6 +39380,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -39345,6 +39390,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -39360,6 +39406,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -39370,6 +39417,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/color/-/color-4.1.0.tgz", "integrity": "sha512-o2rkkxyLGgYoeUy1OodXpbPAQNmlNBrirQ8ODO8QutzDiDMNdezSOZLNnusQ6pUpCQJUsaJIo9DZJKqa2HgH7A==", + "dev": true, "requires": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -39379,6 +39427,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -39388,6 +39437,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -39400,6 +39450,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -39412,6 +39463,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -39422,6 +39474,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -39432,6 +39485,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "requires": { "is-glob": "^4.0.3" }, @@ -39440,6 +39494,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -39450,6 +39505,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, "requires": { "import-from": "^3.0.0" } @@ -39458,6 +39514,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, "requires": { "resolve-from": "^5.0.0" } @@ -39466,6 +39523,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -39474,7 +39532,8 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true } } }, @@ -39482,6 +39541,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "dev": true, "requires": { "import-cwd": "^3.0.0", "lilconfig": "^2.0.3", @@ -39492,6 +39552,7 @@ "version": "6.0.7", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz", "integrity": "sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA==", + "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -39501,6 +39562,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -39509,6 +39571,7 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -39777,6 +39840,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, "requires": { "rimraf": "^3.0.0" } @@ -41311,14 +41375,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -41344,6 +41400,14 @@ } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -41869,8 +41933,7 @@ "ws": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", - "requires": {} + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/gradio/__init__.py b/gradio/__init__.py index b06f46981b..159d7a4d57 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -1,6 +1,7 @@ from gradio.interface import * # This makes it possible to import `Interface` as `gradio.Interface`. from gradio.networking import get_state, set_state from gradio.mix import * +from gradio.flagging import * import pkg_resources current_pkg_version = pkg_resources.require("gradio")[0].version diff --git a/gradio/flagging.py b/gradio/flagging.py new file mode 100644 index 0000000000..b01d68f1af --- /dev/null +++ b/gradio/flagging.py @@ -0,0 +1,248 @@ +import gradio as gr +import os +import datetime +from gradio import encryptor +import csv +import io +from abc import ABC, abstractmethod + + +class FlaggingCallback(ABC): + """ + An abstract class for defining the methods that any FlaggingCallback should have. + """ + + @abstractmethod + def setup(self, flagging_dir): + """ + This method should be overridden and ensure that everything is set up correctly for flag(). + This method gets called once at the beginning of the Interface.launch() method. + Parameters: + flagging_dir: A string, typically containing the path to the directory where the flagging file should be storied (provided as an argument to Interface.__init__()). + """ + pass + + @abstractmethod + def flag(self, interface, input_data, output_data, flag_option=None, flag_index=None, username=None): + """ + This method should be overridden by the FlaggingCallback subclass and may contain optional additional arguments. + This gets called every time the button is pressed. + Parameters: + interface: The Interface object that is being used to launch the flagging interface. + input_data: The input data to be flagged. + output_data: The output data to be flagged. + flag_option (optional): In the case that flagging_options are provided, the flag option that is being used. + flag_index (optional): The index of the sample that is being flagged. + username (optional): The username of the user that is flagging the data, if logged in. + Returns: + (int) The total number of samples that have been flagged. + """ + pass + + +class SimpleCSVLogger(FlaggingCallback): + """ + A simple example implementation of the FlaggingCallback abstract class + provided for illustrative purposes. + """ + def setup(self, flagging_dir): + self.flagging_dir = flagging_dir + os.makedirs(flagging_dir, exist_ok=True) + + def flag(self, interface, input_data, output_data, flag_option=None, flag_index=None, username=None): + flagging_dir = self.flagging_dir + log_filepath = "{}/log.csv".format(flagging_dir) + + csv_data = [] + for i, input in enumerate(interface.input_components): + csv_data.append(input.save_flagged( + flagging_dir, interface.config["input_components"][i]["label"], input_data[i], None)) + for i, output in enumerate(interface.output_components): + csv_data.append(output.save_flagged( + flagging_dir, interface.config["output_components"][i]["label"], output_data[i], None) if + output_data[i] is not None else "") + + with open(log_filepath, "a", newline="") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(csv_data) + + with open(log_filepath, "r") as csvfile: + line_count = len([None for row in csv.reader(csvfile)]) - 1 + return line_count + + +class CSVLogger(FlaggingCallback): + """ + The default implementation of the FlaggingCallback abstract class. + Logs the input and output data to a CSV file. + """ + def setup(self, flagging_dir): + self.flagging_dir = flagging_dir + os.makedirs(flagging_dir, exist_ok=True) + + def flag(self, interface, input_data, output_data, flag_option=None, flag_index=None, username=None): + flagging_dir = self.flagging_dir + log_fp = "{}/log.csv".format(flagging_dir) + encryption_key = interface.encryption_key if interface.encrypt else None + is_new = not os.path.exists(log_fp) + + if flag_index is None: + csv_data = [] + for i, input in enumerate(interface.input_components): + csv_data.append(input.save_flagged( + flagging_dir, interface.config["input_components"][i]["label"], input_data[i], encryption_key)) + for i, output in enumerate(interface.output_components): + csv_data.append(output.save_flagged( + flagging_dir, interface.config["output_components"][i]["label"], output_data[i], encryption_key) if + output_data[i] is not None else "") + if flag_option is not None: + csv_data.append(flag_option) + if username is not None: + csv_data.append(username) + csv_data.append(str(datetime.datetime.now())) + if is_new: + headers = [interface["label"] + for interface in interface.config["input_components"]] + headers += [interface["label"] + for interface in interface.config["output_components"]] + if interface.flagging_options is not None: + headers.append("flag") + if username is not None: + headers.append("username") + headers.append("timestamp") + + def replace_flag_at_index(file_content): + file_content = io.StringIO(file_content) + content = list(csv.reader(file_content)) + header = content[0] + flag_col_index = header.index("flag") + content[flag_index][flag_col_index] = flag_option + output = io.StringIO() + writer = csv.writer(output) + writer.writerows(content) + return output.getvalue() + + if interface.encrypt: + output = io.StringIO() + if not is_new: + with open(log_fp, "rb") as csvfile: + encrypted_csv = csvfile.read() + decrypted_csv = encryptor.decrypt( + interface.encryption_key, encrypted_csv) + file_content = decrypted_csv.decode() + if flag_index is not None: + file_content = replace_flag_at_index(file_content) + output.write(file_content) + writer = csv.writer(output) + if flag_index is None: + if is_new: + writer.writerow(headers) + writer.writerow(csv_data) + with open(log_fp, "wb") as csvfile: + csvfile.write(encryptor.encrypt( + interface.encryption_key, output.getvalue().encode())) + else: + if flag_index is None: + with open(log_fp, "a", newline="") as csvfile: + writer = csv.writer(csvfile) + if is_new: + writer.writerow(headers) + writer.writerow(csv_data) + else: + with open(log_fp) as csvfile: + file_content = csvfile.read() + file_content = replace_flag_at_index(file_content) + with open(log_fp, "w", newline="") as csvfile: # newline parameter needed for Windows + csvfile.write(file_content) + with open(log_fp, "r") as csvfile: + line_count = len([None for row in csv.reader(csvfile)]) - 1 + return line_count + + +class HuggingFaceDatasetSaver(FlaggingCallback): + """ + A FlaggingCallback that saves flagged data to a HuggingFace dataset. + """ + def __init__(self, hf_foken, dataset_name, organization=None, + private=False, verbose=True): + """ + Params: + hf_token (str): The token to use to access the huggingface API. + dataset_name (str): The name of the dataset to save the data to, e.g. + "image-classifier-1" + organization (str): The name of the organization to which to attach + the datasets. If None, the dataset attaches to the user only. + private (bool): If the dataset does not already exist, whether it + should be created as a private dataset or public. Private datasets + may require paid huggingface.co accounts + verbose (bool): Whether to print out the status of the dataset + creation. + """ + self.hf_foken = hf_foken + self.dataset_name = dataset_name + self.organization_name = organization + self.dataset_private = private + self.verbose = verbose + + def setup(self, flagging_dir): + """ + Params: + flagging_dir (str): local directory where the dataset is cloned, + updated, and pushed from. + """ + try: + import huggingface_hub + except (ImportError, ModuleNotFoundError): + raise ImportError("Package `huggingface_hub` not found is needed " + "for HuggingFaceDatasetSaver. Try 'pip install huggingface_hub'.") + path_to_dataset_repo = huggingface_hub.create_repo( + name=self.dataset_name, token=self.hf_foken, + private=self.dataset_private, repo_type="dataset", exist_ok=True) + self.flagging_dir = flagging_dir + self.dataset_dir = os.path.join(flagging_dir, self.dataset_name) + self.repo = huggingface_hub.Repository( + local_dir=self.dataset_dir, clone_from=path_to_dataset_repo, + use_auth_token=self.hf_foken) + self.repo.git_pull() + + #Should filename be user-specified? + self.log_file = os.path.join(self.dataset_dir, "data.csv") + + def flag(self, interface, input_data, output_data, flag_option=None, + flag_index=None, username=None, path=None): + # Note flag_index, username, path are not currently used + is_new = not os.path.exists(self.log_file) + with open(self.log_file, "a", newline="") as csvfile: + writer = csv.writer(csvfile) + + # Generate the headers + if is_new: + headers = [interface["label"] for interface in interface.config["input_components"]] + headers += [interface["label"] for interface in interface.config["output_components"]] + if interface.flagging_options is not None: + headers.append("flag") + writer.writerow(headers) + + # Generate the row corresponding to the flagged sample + csv_data = [] + for i, input in enumerate(interface.input_components): + csv_data.append(input.save_flagged(self.dataset_dir, interface.config["input_components"][i]["label"], input_data[i], None)) + for i, output in enumerate(interface.output_components): + csv_data.append(output.save_flagged(self.dataset_dir, interface.config["output_components"][i]["label"], output_data[i], None) if + output_data[i] is not None else "") + if flag_option is not None: + csv_data.append(flag_option) + + # Write the rows + writer.writerow(csv_data) + + # return number of samples in dataset + with open(self.log_file, "r") as csvfile: + line_count = len([None for row in csv.reader(csvfile)]) - 1 + + # push the repo + self.repo.push_to_hub( + commit_message="Flagged sample #{}".format(line_count)) + + return line_count + diff --git a/gradio/interface.py b/gradio/interface.py index d5e8f49eae..ca4245e7e0 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -18,19 +18,24 @@ import time import warnings import webbrowser import weakref + from gradio import networking, strings, utils, encryptor, queue -from gradio.inputs import get_input_instance -from gradio.outputs import get_output_instance -from gradio.interpretation import quantify_difference_in_label, get_regression_or_classification_value from gradio.external import load_interface, load_from_pipeline +from gradio.flagging import FlaggingCallback, CSVLogger +from gradio.inputs import get_input_instance +from gradio.interpretation import quantify_difference_in_label, get_regression_or_classification_value +from gradio.outputs import get_output_instance class Interface: """ - Interfaces are created with Gradio by constructing a `gradio.Interface()` object or by calling `gradio.Interface.load()`. + Gradio interfaces are created by constructing a `Interface` object + with a locally-defined function, or with `Interface.load()` with the path + to a repo or by `Interface.from_pipeline()` with a Transformers Pipeline. """ - instances = weakref.WeakSet() # stores references to all currently existing Interface instances + # stores references to all currently existing Interface instances + instances = weakref.WeakSet() @classmethod def get_instances(cls): @@ -77,7 +82,8 @@ class Interface: capture_session=None, interpretation=None, num_shap=2.0, theme=None, repeat_outputs_per_model=True, title=None, description=None, article=None, thumbnail=None, css=None, height=500, width=900, allow_screenshot=True, allow_flagging=None, flagging_options=None, - encrypt=False, show_tips=None, flagging_dir="flagged", analytics_enabled=None, enable_queue=None, api_mode=None): + encrypt=False, show_tips=None, flagging_dir="flagged", analytics_enabled=None, enable_queue=None, api_mode=None, + flagging_callback=CSVLogger()): """ Parameters: fn (Callable): the function to wrap an interface around. @@ -178,10 +184,13 @@ class Interface: self.simple_server = None self.allow_screenshot = allow_screenshot # For allow_flagging and analytics_enabled: (1) first check for parameter, (2) check for environment variable, (3) default to True - self.allow_flagging = allow_flagging if allow_flagging is not None else os.getenv("GRADIO_ALLOW_FLAGGING", "True")=="True" self.analytics_enabled = analytics_enabled if analytics_enabled is not None else os.getenv("GRADIO_ANALYTICS_ENABLED", "True")=="True" + self.allow_flagging = allow_flagging if allow_flagging is not None else os.getenv("GRADIO_ALLOW_FLAGGING", "True")=="True" + self.flagging_options = flagging_options + self.flagging_callback: FlaggingCallback = flagging_callback self.flagging_dir = flagging_dir + self.encrypt = encrypt self.save_to = None self.share = None @@ -213,9 +222,6 @@ class Interface: # just ignore this. pass - if self.allow_flagging: - os.makedirs(self.flagging_dir, exist_ok=True) - data = {'fn': fn, 'inputs': inputs, 'outputs': outputs, @@ -598,6 +604,10 @@ class Interface: if self.enable_queue is None: self.enable_queue = enable_queue + # Setup flagging + if self.allow_flagging: + self.flagging_callback.setup(self.flagging_dir) + # Launch local flask server server_port, path_to_local_server, app, thread, server = networking.start_server( self, server_name, server_port, self.auth) diff --git a/gradio/networking.py b/gradio/networking.py index 8c46bebe14..07f3ec3270 100644 --- a/gradio/networking.py +++ b/gradio/networking.py @@ -25,8 +25,8 @@ import urllib.parse import urllib.request from werkzeug.security import safe_join from werkzeug.serving import make_server -from gradio import encryptor -from gradio import queue + +from gradio import encryptor, queue from gradio.tunneling import create_tunnel # By default, the http server will try to open on port 7860. If not available, 7861, 7862, etc. @@ -209,9 +209,10 @@ def predict(): output = {"data": prediction, "durations": durations, "avg_durations": avg_durations} if app.interface.allow_flagging == "auto": try: - flag_index = flag_data(raw_input, prediction, + flag_index = app.interface.flagging_handler.flag(raw_input, prediction, flag_option=(None if app.interface.flagging_options is None else ""), - username=current_user.id if current_user.is_authenticated else None) + username=current_user.id if current_user.is_authenticated else None, + flag_path=os.path.join(app.cwd, app.interface.flagging_dir)) output["flag_index"] = flag_index except Exception as e: print(str(e)) @@ -281,90 +282,12 @@ def log_feature_analytics(feature): pass # do not push analytics if no network -def flag_data(input_data, output_data, flag_option=None, flag_index=None, username=None, flag_path=None): - if flag_path is None: - flag_path = os.path.join(app.cwd, app.interface.flagging_dir) - log_fp = "{}/log.csv".format(flag_path) - encryption_key = app.interface.encryption_key if app.interface.encrypt else None - is_new = not os.path.exists(log_fp) - - if flag_index is None: - csv_data = [] - for i, interface in enumerate(app.interface.input_components): - csv_data.append(interface.save_flagged( - flag_path, app.interface.config["input_components"][i]["label"], input_data[i], encryption_key)) - for i, interface in enumerate(app.interface.output_components): - csv_data.append(interface.save_flagged( - flag_path, app.interface.config["output_components"][i]["label"], output_data[i], encryption_key) if output_data[i] is not None else "") - if flag_option is not None: - csv_data.append(flag_option) - if username is not None: - csv_data.append(username) - csv_data.append(str(datetime.datetime.now())) - if is_new: - headers = [interface["label"] - for interface in app.interface.config["input_components"]] - headers += [interface["label"] - for interface in app.interface.config["output_components"]] - if app.interface.flagging_options is not None: - headers.append("flag") - if username is not None: - headers.append("username") - headers.append("timestamp") - - def replace_flag_at_index(file_content): - file_content = io.StringIO(file_content) - content = list(csv.reader(file_content)) - header = content[0] - flag_col_index = header.index("flag") - content[flag_index][flag_col_index] = flag_option - output = io.StringIO() - writer = csv.writer(output) - writer.writerows(content) - return output.getvalue() - - if app.interface.encrypt: - output = io.StringIO() - if not is_new: - with open(log_fp, "rb") as csvfile: - encrypted_csv = csvfile.read() - decrypted_csv = encryptor.decrypt( - app.interface.encryption_key, encrypted_csv) - file_content = decrypted_csv.decode() - if flag_index is not None: - file_content = replace_flag_at_index(file_content) - output.write(file_content) - writer = csv.writer(output) - if flag_index is None: - if is_new: - writer.writerow(headers) - writer.writerow(csv_data) - with open(log_fp, "wb") as csvfile: - csvfile.write(encryptor.encrypt( - app.interface.encryption_key, output.getvalue().encode())) - else: - if flag_index is None: - with open(log_fp, "a", newline="") as csvfile: - writer = csv.writer(csvfile) - if is_new: - writer.writerow(headers) - writer.writerow(csv_data) - else: - with open(log_fp) as csvfile: - file_content = csvfile.read() - file_content = replace_flag_at_index(file_content) - with open(log_fp, "w", newline="") as csvfile: # newline parameter needed for Windows - csvfile.write(file_content) - with open(log_fp, "r") as csvfile: - line_count = len([None for row in csv.reader(csvfile)]) - 1 - return line_count - @app.route("/api/flag/", methods=["POST"]) @login_check def flag(): log_feature_analytics('flag') data = request.json['data'] - flag_data(data['input_data'], data['output_data'], data.get("flag_option"), data.get("flag_index"), + app.interface.flagging_callback.flag(app.interface, data['input_data'], data['output_data'], data.get("flag_option"), data.get("flag_index"), current_user.id if current_user.is_authenticated else None) return jsonify(success=True) diff --git a/test/test_external.py b/test/test_external.py index 6113171638..fba02c7426 100644 --- a/test/test_external.py +++ b/test/test_external.py @@ -9,7 +9,7 @@ import transformers WARNING: These tests have an external dependency: namely that Hugging Face's Hub and Space APIs do not change, and they keep their most famous models up. So if, e.g. Spaces is down, then these test will not pass. """ -os.environ["GRADIO_ANALYTICS_ENABLED"] = "" # Disables analytics +os.environ["GRADIO_ANALYTICS_ENABLED"] = "False" class TestHuggingFaceModelAPI(unittest.TestCase): diff --git a/test/test_flagging.py b/test/test_flagging.py new file mode 100644 index 0000000000..62fdd2e4ca --- /dev/null +++ b/test/test_flagging.py @@ -0,0 +1,31 @@ +import gradio as gr +from gradio import flagging +import tempfile +import unittest +import unittest.mock as mock + + +class TestFlagging(unittest.TestCase): + def test_default_flagging_handler(self): + with tempfile.TemporaryDirectory() as tmpdirname: + io = gr.Interface(lambda x: x, "text", "text", flagging_dir=tmpdirname) + io.launch(prevent_thread_lock=True) + row_count = io.flagging_callback.flag(io, ["test"], ["test"]) + self.assertEqual(row_count, 1) # 2 rows written including header + row_count = io.flagging_callback.flag(io, ["test"], ["test"]) + self.assertEqual(row_count, 2) # 3 rows written including header + io.close() + + def test_simple_csv_flagging_handler(self): + with tempfile.TemporaryDirectory() as tmpdirname: + io = gr.Interface(lambda x: x, "text", "text", flagging_dir=tmpdirname, flagging_callback=flagging.SimpleCSVLogger()) + io.launch(prevent_thread_lock=True) + row_count = io.flagging_callback.flag(io, ["test"], ["test"]) + self.assertEqual(row_count, 0) # no header + row_count = io.flagging_callback.flag(io, ["test"], ["test"]) + self.assertEqual(row_count, 1) # no header + io.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_inputs.py b/test/test_inputs.py index 3cbd036300..38d2a2fd34 100644 --- a/test/test_inputs.py +++ b/test/test_inputs.py @@ -10,7 +10,7 @@ import tempfile import json -os.environ["GRADIO_ANALYTICS_ENABLED"] = "" # Disables analytics +os.environ["GRADIO_ANALYTICS_ENABLED"] = "False" class InputComponent(unittest.TestCase): diff --git a/test/test_networking.py b/test/test_networking.py index 0990e31de9..7c65f689df 100644 --- a/test/test_networking.py +++ b/test/test_networking.py @@ -1,15 +1,14 @@ -from gradio import networking -import gradio as gr +from gradio import networking, Interface, reset_all, flagging import unittest import unittest.mock as mock import ipaddress import requests import warnings -import tempfile -from unittest.mock import ANY +from unittest.mock import ANY, MagicMock import urllib.request import os + os.environ["GRADIO_ANALYTICS_ENABLED"] = "False" @@ -59,7 +58,7 @@ class TestPort(unittest.TestCase): class TestFlaskRoutes(unittest.TestCase): def setUp(self) -> None: - self.io = gr.Interface(lambda x: x, "text", "text") + self.io = Interface(lambda x: x, "text", "text") self.app, _, _ = self.io.launch(prevent_thread_lock=True) self.client = self.app.test_client() @@ -106,12 +105,12 @@ class TestFlaskRoutes(unittest.TestCase): def tearDown(self) -> None: self.io.close() - gr.reset_all() + reset_all() class TestAuthenticatedFlaskRoutes(unittest.TestCase): def setUp(self) -> None: - self.io = gr.Interface(lambda x: x, "text", "text") + self.io = Interface(lambda x: x, "text", "text") self.app, _, _ = self.io.launch(auth=("test", "correct_password"), prevent_thread_lock=True) self.client = self.app.test_client() @@ -127,11 +126,12 @@ class TestAuthenticatedFlaskRoutes(unittest.TestCase): def tearDown(self) -> None: self.io.close() - gr.reset_all() + reset_all() + class TestInterfaceCustomParameters(unittest.TestCase): def test_show_error(self): - io = gr.Interface(lambda x: 1/x, "number", "number") + io = Interface(lambda x: 1/x, "number", "number") app, _, _ = io.launch(show_error=True, prevent_thread_lock=True) client = app.test_client() response = client.post('/api/predict/', json={"data": [0]}) @@ -141,47 +141,39 @@ class TestInterfaceCustomParameters(unittest.TestCase): def test_feature_logging(self): with mock.patch('requests.post') as mock_post: - io = gr.Interface(lambda x: 1/x, "number", "number", analytics_enabled=True) + io = Interface(lambda x: 1/x, "number", "number", analytics_enabled=True) io.launch(show_error=True, prevent_thread_lock=True) networking.log_feature_analytics("test_feature") mock_post.assert_called_with(networking.GRADIO_FEATURE_ANALYTICS_URL, data=ANY, timeout=ANY) io.close() - io = gr.Interface(lambda x: 1/x, "number", "number") - print(io.analytics_enabled) + io = Interface(lambda x: 1/x, "number", "number") io.launch(show_error=True, prevent_thread_lock=True) with mock.patch('requests.post') as mock_post: networking.log_feature_analytics("test_feature") mock_post.assert_not_called() io.close() -class TestFlagging(unittest.TestCase): - def test_num_rows_written(self): - io = gr.Interface(lambda x: x, "text", "text") - io.launch(prevent_thread_lock=True) - with tempfile.TemporaryDirectory() as tmpdirname: - row_count = networking.flag_data(["test"], ["test"], flag_path=tmpdirname) - self.assertEquals(row_count, 1) # 2 rows written including header - row_count = networking.flag_data("test", "test", flag_path=tmpdirname) - self.assertEquals(row_count, 2) # 3 rows written including header - io.close() +class TestFlagging(unittest.TestCase): @mock.patch("requests.post") - @mock.patch("gradio.networking.flag_data") - def test_flagging_analytics(self, mock_flag, mock_post): - io = gr.Interface(lambda x: x, "text", "text", analytics_enabled=True) + def test_flagging_analytics(self, mock_post): + callback = flagging.CSVLogger() + callback.flag = mock.MagicMock() + io = Interface(lambda x: x, "text", "text", analytics_enabled=True, flagging_callback=callback) app, _, _ = io.launch(show_error=True, prevent_thread_lock=True) client = app.test_client() response = client.post('/api/flag/', json={"data": {"input_data": ["test"], "output_data": ["test"]}}) mock_post.assert_any_call(networking.GRADIO_FEATURE_ANALYTICS_URL, data=ANY, timeout=ANY) - mock_flag.assert_called_once() + callback.flag.assert_called_once() self.assertEqual(response.status_code, 200) io.close() + @mock.patch("requests.post") class TestInterpretation(unittest.TestCase): def test_interpretation(self, mock_post): - io = gr.Interface(lambda x: len(x), "text", "label", interpretation="default", analytics_enabled=True) + io = Interface(lambda x: len(x), "text", "label", interpretation="default", analytics_enabled=True) app, _, _ = io.launch(prevent_thread_lock=True) client = app.test_client() io.interpret = mock.MagicMock(return_value=(None, None)) @@ -192,30 +184,30 @@ class TestInterpretation(unittest.TestCase): class TestState(unittest.TestCase): def test_state_initialization(self): - io = gr.Interface(lambda x: len(x), "text", "label") + io = Interface(lambda x: len(x), "text", "label") app, _, _ = io.launch(prevent_thread_lock=True) with app.test_request_context(): self.assertIsNone(networking.get_state()) def test_state_value(self): - io = gr.Interface(lambda x: len(x), "text", "label") + io = Interface(lambda x: len(x), "text", "label") app, _, _ = io.launch(prevent_thread_lock=True) with app.test_request_context(): networking.set_state("test") client = app.test_client() client.post('/api/predict/', json={"data": [0]}) - self.assertEquals(networking.get_state(), "test") + self.assertEqual(networking.get_state(), "test") class TestURLs(unittest.TestCase): def test_url_ok(self): urllib.request.urlopen = mock.MagicMock(return_value="test") res = networking.url_request("http://www.gradio.app") - self.assertEquals(res, "test") + self.assertEqual(res, "test") def test_setup_tunnel(self): networking.create_tunnel = mock.MagicMock(return_value="test") res = networking.setup_tunnel(None, None) - self.assertEquals(res, "test") + self.assertEqual(res, "test") def test_url_ok(self): res = networking.url_ok("https://www.gradio.app") @@ -224,7 +216,7 @@ class TestURLs(unittest.TestCase): class TestQueuing(unittest.TestCase): def test_queueing(self): - io = gr.Interface(lambda x: x, "text", "text") + io = Interface(lambda x: x, "text", "text") app, _, _ = io.launch(prevent_thread_lock=True) client = app.test_client() # mock queue methods and post method