mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-01 11:45:36 +08:00
* enter pre-release mode * Remove deprecated parameters and classes for the 5.0 release (#8797) * 5.0 * add changeset * deprecate more * add changeset * lint * Update rotten-bears-bathe.md * Update icy-clocks-juggle.md * changes * Delete .changeset/icy-clocks-juggle.md * every * more deprecation * deprecate inits * fix * fix func * fix some tests * format * fix more tests * fixes --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix (#8830) * fix * Prevent invalid values from being submitted to dropdown, etc. (#8810) * prevent invalid values * error * add changeset * component * add tests * fix tests * spec ts * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fixes * Remove manual ip address check and launch counter (#8884) * changes * add changeset * hash * changes * remove * changes * rename * internal * changes * remove json path * merge * fix tests --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Remove deprecated documentation (#8940) * remove logoutbutton page * remove huggingfacedatasetsaver * Use HTTP Livestreaming for audio/video streaming out (#8906) * HTTP live streaming * type check * fix code * Fix code * add code * Video demo * Fix tests * Update notebook * Add guide * Fix demo * Allow downloading * revert * Fix download filename * lint * notebooks * fix video demo * Fix config * Fix audio repeated play bug * Improve guide * fix audio? * Use cantina * Code * type check * add code * Use runtimeerror * Add code * Adds `strict_cors` parameter to `launch()` (#8959) * prevent null origin requests by default * changes * add changeset * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fi * Streaming out tweaks (#8976) * Tweaks * Better * typo * lint * Improve url downloads for file objects (#8978) * changes * changes * add changeset * add changeset * Ci security tweaks (#9010) * asd * asd * asd * asd * asd * asd * asd * asd * asd * asd * asd * asd * asd * asd * change * changes * changes * changes * changes * changes * changes --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: pngwn <hello@pngwn.io> * merge main (#9050) * lint * Have gr.on set value at start as well (#9065) * changes * changes * changes --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> * No token passed by default in `gr.load()` (#9069) * changes * add changeset * docstring * change * client changess --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * changes * Set default `format` in `gr.Audio` to be `None` to avoid unnecessary preprocessing (#9073) * audio format * add changeset * lint * docstring * format * fix tests * tweaks * refactor * fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Streaming inputs for 5.0 (#8941) * Fix code * Add code * Add code * working demo * hacky video * Add code for video * Fixing some code * clean queieing * low streaming mode audio * reworking * remove console * Pretty good spot * Delete unused * consolidate * Add progress bar * Set time limit null * delete * Fix then issue * merge out * Add code * clean up * Remove base64 * Add code * minor bugs * End stream * Fix rerender * remove unwanted * Address streaming comments * Commit file lol * ;int * lint backend * lint * Fix queue status. Stream_every defined in event * Fix types * Add code * Add code * Add code * queue time * docstring wording * Fix typo * add changeset --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 3.10 (#9133) * Deprecate passing a tuple for gr.Code value (#9132) * Add code * add changeset * lint * type check --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Postprocess hardening (#9122) * hardenning * Fix code * add changeset * Fix tests * add test fuzzer * Clean up * revert * Fix * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Drop python 3.8 and 3.9 (#9140) * drop support for python 3.8 and 3.9 * add changeset * format * changes * add changeset * changes * add changeset * changes * 3.10 * string * tweak * tweak * changes * changes * format * more tweaks * update actions * website docs build * fix func tests * rev req * test fix * remove ruff rule for zip strict --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Raise WasmUnsupportedError for ffmpeg usage on Lite (#9130) * Raise WasmUnsupportedError for ffmpeg usage on Lite * add changeset * add changeset * Add WasmUnsupportedError in Audio._convert_to_adts * Add WasmUnsupportedError in processing_utils.audio_to_file * Fix * Raise WasmUnsupportedError from processing_utils.audio_from_file * empty commit --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Video gallery (#9052) * video support * tests and backend changes * undo main merge * upload fix * Revert "undo main merge" This reverts commite2a26e6d28
. * type fixes * format * pr fixes * Update gradio/components/gallery.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Update gradio/components/gallery.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * type fix * thumbnails * thumbnail type * remove thumbnail generation * add changeset * test fixes * test fixes * python test fix * python test fixc * fix * fix * story fix --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Open audio/image input stream only when queue is ready (#9149) * fix * submit logic happens in Blocks * add changeset * trigger ci * trigger ci * Add code * Add code * Fix retrigger refactor * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * trigger ci * update (#9176) * File access security guide (#9156) * first draft Add code Add code Add code emphasis * suggestions * redirects * add changeset * trigger ci * typos --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix guide * Fix notebook (#9181) * DNS resolver on ip check (#9150) * changes * changes * add changeset * chaanges * changes * changes * changes * changes * add caching and whitelist * remove hf.space --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Change dark mode color theme from `gray` to `zinc` (#9175) * use zinc as neutral colour * add changeset * fix test * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Streaming Guides (#9173) * Fix unified case * commit * Add code * add changeset * notebook * Lint * delete * Fix code * fix tests * File access security guide (#9156) * first draft Add code Add code Add code emphasis * suggestions * redirects * add changeset * trigger ci * typos --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * redirect * typos * link * fix * See what the problem is * less time * fix * try again with busted cache * try again * Code * Demo and code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: pngwn <hello@pngwn.io> * Fix (#9215) * Deprecate type='tuples for chatbot and focus chatbot docs on 'messages' type (#9194) * Remove grey background behind all components (#9213) * remove panel padding and border * add changeset * revert radius change * add changeset * format * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * changes * changes * Revert "changes" This reverts commit9e2ae43330
. * Revert "changes" This reverts commit9f4c3eec0f
. * Redesign `gr.Slider()` (#9197) * redesign slider * add changeset * fix test * update slider design * slider tweaks --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 🔡 Update default core Gradio font (#9204) * change sans font from Source Sans Pro to Asap * change misc refs to Source Sans Pro * add changeset * revert old changes * add changeset * replace asap with IBM Plex Sans * add changeset * repalce asaps with ibm plex * tweak --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Redesign `gr.Button()` (#9167) * *add new button styling *add origin theme class with old button styling * add changeset * remove new colour * add changeset * color and radius tweaks * remove neutral hue change * *update button demo *style tweaks * format * fix test * use white text on primary btn * adjust primary orange * tweak colour * disabled fixes * refactor * refactor * refactor * refactor * remove non-button changes * test * revert test * make cancel btn darker in light mode * change button stories to interactive * fix slider test * fix test * tweak * tweak secondary colour to work with gr.group() * add changeset * tweak * tweak button hover grey --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: pngwn <hello@pngwn.io> * Minor changes to flagging for 5.0 (#9166) * init * add changeset * rename * flagging * flagging * changes * update * changes * more * more * changes * add changeset * fix test * changes * update demos --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Adds TLS to FRP tunnel (#9218) * tls tunnel * add changeset * add changeset * arm64 * checksums * changes * tweaks * tweak --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Move buttons from chat_interface into Chatbot (#9201) * First draft * type check * test * add changeset * Fix e2e styling and tests * fix lint * Add code * add changeset * Remove shadow of copy button, make a box * lint * add changeset * fix padding + lint * make buttons a bit smaller. use different icon * Add code * add changeset * tunneling * fix * Add code * fix + lint * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Improve button consistency across light/dark mode (#9236) * ensure btn borders are consistent in light and dark mode * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Redesign `gr.Tabs()` (#9199) * Decrease component radii and remove input shadows (#9216) * fix py chatbot test * Lighten secondary button grey fill (#9245) * lighten secondary button grey * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Stop using `multiprocessing` in `flagging.CSVLogger` on Lite v5 (#9246) * Fix the default demo code for the dev * Use ClassicCSVLogger for Lite * add changeset * add changeset * Revert "Use ClassicCSVLogger for Lite" This reverts commita89fcb1134
. * Avoid using multiprocessing.Lock on Lite * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * prefix api routes (#9200) * Built-in submit and stop buttons in `gr.ChatInterface(multimodal=False)`, adding `submit_btn` and `stop_btn` props to `gr.Textbox()` and `gr.MultimodalText()` (#9235) * Add submit_btn prop to the Textbox component and use it in ChatInterface for a consistent design with multimodal=True * Change the default value of MultimodalTextbox.submit_btn to False for consistency with Textbox.submit_btn * add changeset * Set the default value of Textbox.submit_btn as False for consistency * add changeset * Add stop_btn prop to Textbox and MultimodalTextbox and use it in ChatInterface for a built-in stop button * add changeset * add changeset * Fix Python tests * Fix JS tests * nit fix * Make the submit and stop buttons not exclusive for simplicity * Replace the Pause icon with the Square icon * add changeset * Update the docstring * Preserve the original values of textbox.submit_btn and .stop_btn after running a generator * Show the stop button only when the submit_btn is enabled from the beginning * Respect the user-specified values of submit_btn and stop_btn * Add ChatInterface.submit_btn and .stop_btn params * Fix Textbox.svelte style with string values of submit_btn and stop_btn * Fix Python tests * Fix Python code * fix test * Apply suggestions from code review Co-authored-by: Abubakar Abid <abubakar@huggingface.co> --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Adds a "huggingface" button variant, and makes it the default for `gr.LoginButton` and `gr.DuplicateButton` (#9254) * add clear variant * add changeset * duplicate button * add changeset * tweak * tweak * format * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#8829) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix cs * chore: update versions (beta) (#9262) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Improve Icon Button consistency (#9250) * * update icon buttons * add image editor specific icon button * tweak hover * margin tweak * add changeset * improve gr.Video button UI * radius tweak * ensure even spacing * fix typechecks * add changeset * revert irrelevant changes * typefix * fix image editor buttons * fix download link icon * disable undo if no change events dispatched in model3d and video * add changeset * add iconbuttonwrapper around gallery share btn --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * format * Fix reload mode and streaming in 5.0 dev (#9269) * Fix reload mode + streaming * use api_prefix for reload * add changeset * comments --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Raise error instead of warning if checksums for binary do not match (#9268) * tunneling * add changeset * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix stop recording button colors (#9270) * Add code * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Match style of textbox stop button to submit button (#9280) * Change styles * styling * add changeset * add changeset * consistent width --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Raise ChecksumMismatchError (#9300) * raise mismatch * add changeset * changes * format backend --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Further tweak to is_in_or_equal (#9282) * Add code * add changeset * add changeset * is_launching tweak * no resolve symlink * Use has_launched --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * changes (#9301) * Fixes race condition in `update_root_in_config` (#9306) * test * lint * tests * add changeset * change * lint * reduce num attempts --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * docstring * Adds ability to block event trigger when file is uploading (#9253) * input_ready * add changeset * update value * block event when input waiting * format * add changeset * dep index --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 5.0 merge (#9318) * merge * pythong format * fix typecheck * fix json scroll * fix test --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Hannah <hannahblair@users.noreply.github.com> * Proposal: remove `gr.make_waveform` and remove `matplotlib` as a dependency (#9312) * remove matplotlib * add changeset * remove tests, demo * Fix the Lite worker to set the matplotlib backend engine only when the matplotlib package is installed * add changeset * Fix comment --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> * Dont move files to cache automatically in chatbot postprocess (#9303) * Fix * add changeset * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Remove two dependencies: `importlib_resources` and `urllib3` (if not in Wasm) (#9321) * remove-importlib_resources * add changeset * urllib only on wasm * add changeset * format * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Make `gr.Image` preprocessing more efficient (#9314) * image preprocess * add changeset * changes * fix tests * docstring * docstring * image * fix * format * changes * fix test * changes * handle svg files --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 5.0 merge take 2 (#9326) * chore: update versions (#9168) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * chore: update error.svx (#9291) * chore: update error.svx occured -> occurred * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Update docs to use new Image init (#9304) * Fix scrollbars everywhere (#9276) * changes * add changeset * scroll fix * remove .json css, adjust scroll height to account for label --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Hannah <hannahblair@users.noreply.github.com> * Separate starlette.Request from PredictBody. Only set in new PredictBodyInternal object (#9279) * use custom pydantic type annotatio * add changeset * Add code * add changeset * rework * dont use arbitrary_types_allowed * add changeset * fix test * revert path change --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Add root_url to components created by gr.render (#9267) * Fix bug * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fileformat whitelist (#9302) * changes * add changeset * Update routes.py --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix multiple trigger bug when function has js (#9188) * add code * add changeset * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (#9298) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix json * harden test * harden test * clean * format * add changeset * notebooks * fix old conflicts --------- Co-authored-by: Gradio PR Bot <121576822+gradio-pr-bot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Tayfun Sen <tayfun.sen@gmail.com> Co-authored-by: aliabid94 <aabid94@gmail.com> Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: Hannah <hannahblair@users.noreply.github.com> Co-authored-by: Freddy Boulton <alfonsoboulton@gmail.com> * Add `matplotlib` requirements to several demos (#9327) * add matplotlib req to demos * add matplotlib req to demos * more * update reqs * clean * format * Standardize `height` across components and add `max_height` and `min_height` parameters where appropriate (#9313) * height * changelog * height * add changeset * add changeset * add changeset * revert clog * more changes * add changeset * chatbot * restore * filexplorer * add changeset * json * add changeset * markdown * add changeset * row * add changeset * height * format frontend * revert * max height * fix * fix docstrings * fix py tests * add story --------- Co-authored-by: Dawood <dawoodkhan82@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix typo in `tunneling.py` (#9338) * tunneling fix * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Set the color of placeholder in a disabled textbox to gray instead of black, and disable typing while a response is generating in `gr.ChatInterface`, allow `gr.MultimodalTextbox` to accept string values (#9328) * textbox * add changeset * changes' * revert demos * add changeset * add changeset * changes * multimodal * add changeset * changes * format * revert demo * fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Improve is_in_or_equal and fuzzer (#9341) * improve fuzzer * test case * add changeset * verify * Update gradio/utils.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Add info about Powershell client (#9343) * clients * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Remove lite/theme.css from the Git-managed file tree (#9335) * Delete js/lite/src/theme.css from the Git managed file tree as it's dynamically generated * Remove lite-related npm scripts from spa/package.json * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 9227 chatinterface retry bug (#9316) * first draft * add code * tip * add changeset * delete dead code * Type check notebook * consolidate like section with guide * Add comments * add value * Lint * lint * guide --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Move icons into `IconButtonWrapper` (#9261) * * update icon buttons * add image editor specific icon button * tweak hover * margin tweak * add changeset * improve gr.Video button UI * radius tweak * ensure even spacing * fix typechecks * add changeset * revert irrelevant changes * typefix * fix image editor buttons * fix download link icon * disable undo if no change events dispatched in model3d and video * use icons with iconbuttonwrapper * add iconbuttonwrapper around gallery share btn * Revert "add iconbuttonwrapper around gallery share btn" This reverts commit4605302df4
. * add changeset * design fixes * add changeset * move status tracker progress to bottom of component * add changeset * use iconbutton for like/dislike * fix lint error * fix type errors * type errors * fix test * revert undo icon change * btn spacing --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Added gradio-in-r (#9340) * Added gradio-in-r * add changeset * section * remove * tweaks * delete changeset * R * Updated using-gradio-in-other-programming-languages.md --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Enhance Lite E2E tests and fix a networking problem on Lite (#9333) * Add Lite E2E test to check a matplotlib problem which was fixed in https://github.com/gradio-app/gradio/pull/9312 * Restore js/app/test/image_remote_url.spec.ts, which was deleted in https://github.com/gradio-app/gradio/pull/8716 * Fix tootils import * Format * Fix processing_utils.resolve_with_google_dns to use the HTTPX client instead of urllib so it works on Lite * add changeset * add changeset * Move js/app/test/image_remote_url.spec.ts -> js/spa/test/image_remote_url.spec.ts * Use pyodide.http in resolve_with_google_dns on Lite --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Do not attach `content_disposition_type = "attachment"` headers for files explicitly allowed by developer (#9348) * changes * add changeset * format * fix type * type * add test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix overflowing markdown in Chatbot (#9260) * fix markdown overflowing table * add changeset * revert undo icon * add changeset * Revert "revert undo icon" This reverts commit855b012a20
. * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Small tweak to how thoughts are shown in `gr.Chatbot` (#9359) * thiknk chat * add changeset * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Use `container` param in `gr.Markdown` (#9356) * * add param * add story * add changeset * Use IconButton for copy btn * fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * run format * Fixes website build in 5.0-dev (#9382) * changes * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Small tweaks to improve the DX for the "tuples"/"messages" argument in `gr.Chatbot` (#9358) * change format * format * add changeset * revert * revert --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Update babylon.js to `v7` for `gr.Model3D` (#9377) * update package.json * add changeset * add changeset * update pnpm lock * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix `gr.ImageEditor` toolbar cutoff (#9371) * fix wrap alignment * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Adds LLM to the Playground (#9233) * simple system prompt * faster model and streaming and better system prompt * changes * changes * add changeset * formatting * add placeholder wheel * changes * save to db finally working * clean * fix open in playground button * better fix for open in playground * changes * format * fix * try * remove * remove make waveform * fix * using fallback mode and other changes * add show_error * fix lite refresh issue * fix css * add demo * format * lite using latest wheel * cleanup * formatting * hack fix for b vs betta * formatting --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Refactor lazy caching (#9361) * changes * lazy * redo lazy * add changeset * changes * helpers * docstrings' * lint * Update guides/04_additional-features/09_environment-variables.md Co-authored-by: Charles <charles@huggingface.co> * Update gradio/chat_interface.py Co-authored-by: Dawood Khan <dawoodkhan82@gmail.com> * Update gradio/chat_interface.py Co-authored-by: Dawood Khan <dawoodkhan82@gmail.com> * tolerant --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Charles <charles@huggingface.co> Co-authored-by: Dawood Khan <dawoodkhan82@gmail.com> * Added max lines and overflow scrollbar for `gr.Code` (#9311) * add max lines for gr.Code * add changeset * revert default lines to 5 * fix tests * lint --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix File Types for MultimodalTextbox (#9393) * fix file_types * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Prevent HTML and Markdown height changing when status is hidden (#9363) * fix markdown height changing * * add min_height param to html * prevent height from changing based on status * add changeset * add changeset * param desc change * fix test * format * * add max height to html * share css_units func * add changeset * fix backend test * fe --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Object Detection From Webcam Stream Guide (#9336) * guides * Add demo * guide * Add info about Powershell client (#9343) * clients * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Remove lite/theme.css from the Git-managed file tree (#9335) * Delete js/lite/src/theme.css from the Git managed file tree as it's dynamically generated * Remove lite-related npm scripts from spa/package.json * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * 9227 chatinterface retry bug (#9316) * first draft * add code * tip * add changeset * delete dead code * Type check notebook * consolidate like section with guide * Add comments * add value * Lint * lint * guide --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Move icons into `IconButtonWrapper` (#9261) * * update icon buttons * add image editor specific icon button * tweak hover * margin tweak * add changeset * improve gr.Video button UI * radius tweak * ensure even spacing * fix typechecks * add changeset * revert irrelevant changes * typefix * fix image editor buttons * fix download link icon * disable undo if no change events dispatched in model3d and video * use icons with iconbuttonwrapper * add iconbuttonwrapper around gallery share btn * Revert "add iconbuttonwrapper around gallery share btn" This reverts commit4605302df4
. * add changeset * design fixes * add changeset * move status tracker progress to bottom of component * add changeset * use iconbutton for like/dislike * fix lint error * fix type errors * type errors * fix test * revert undo icon change * btn spacing --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Added gradio-in-r (#9340) * Added gradio-in-r * add changeset * section * remove * tweaks * delete changeset * R * Updated using-gradio-in-other-programming-languages.md --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Enhance Lite E2E tests and fix a networking problem on Lite (#9333) * Add Lite E2E test to check a matplotlib problem which was fixed in https://github.com/gradio-app/gradio/pull/9312 * Restore js/app/test/image_remote_url.spec.ts, which was deleted in https://github.com/gradio-app/gradio/pull/8716 * Fix tootils import * Format * Fix processing_utils.resolve_with_google_dns to use the HTTPX client instead of urllib so it works on Lite * add changeset * add changeset * Move js/app/test/image_remote_url.spec.ts -> js/spa/test/image_remote_url.spec.ts * Use pyodide.http in resolve_with_google_dns on Lite --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Do not attach `content_disposition_type = "attachment"` headers for files explicitly allowed by developer (#9348) * changes * add changeset * format * fix type * type * add test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix overflowing markdown in Chatbot (#9260) * fix markdown overflowing table * add changeset * revert undo icon * add changeset * Revert "revert undo icon" This reverts commit855b012a20
. * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * demo name * Guide on Streaming Video for Object Detection (#9365) * Add code * notebooks * Suggestions * Add gif * Small tweak to how thoughts are shown in `gr.Chatbot` (#9359) * thiknk chat * add changeset * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Use `container` param in `gr.Markdown` (#9356) * * add param * add story * add changeset * Use IconButton for copy btn * fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * small fixes (#9347) * Updated Guide: Real Time Speech Recognition (#9349) * Update real-time-speech-recognition.md added necessary dependency * Update run.py updated code to handle cases with stereo microphone * Update real-time-speech-recognition.md improved english * Update run.py updated code for streaming * Update run.py * chunk space uploads (#9360) * chunk space uploads * Update upload_demo_to_space.py Co-authored-by: Lucain <lucainp@gmail.com> * address comments + tweak CI --------- Co-authored-by: Lucain <lucainp@gmail.com> * add find (#9368) * New branch (#9369) * add find * fix syntax * New branch (#9370) * add find * fix syntax * add hidden files * run format * Testing CI (#9379) * remove unnecessary redirects * add changeset * fix * formatting --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fixes website build in 5.0-dev (#9382) * changes * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Small tweaks to improve the DX for the "tuples"/"messages" argument in `gr.Chatbot` (#9358) * change format * format * add changeset * revert * revert --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Update babylon.js to `v7` for `gr.Model3D` (#9377) * update package.json * add changeset * add changeset * update pnpm lock * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix `gr.ImageEditor` toolbar cutoff (#9371) * fix wrap alignment * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * add lite upload (#9385) * fix sha (#9386) * Fix lite ci (#9387) * fix sha * fix name * fix name * Add code * feedback * link * add changeset * code * check * Update guides/04_additional-features/02_streaming-outputs.md * Update guides/07_streaming/02_object-detection-from-webcam.md --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> Co-authored-by: Hannah <hannahblair@users.noreply.github.com> Co-authored-by: Ifeanyi Idiaye <72707830+Ifeanyi55@users.noreply.github.com> Co-authored-by: Julien Chaumond <julien@huggingface.co> Co-authored-by: Nikita Krasnytskyi <nikita.kras.kyiv@gmail.com> Co-authored-by: pngwn <hello@pngwn.io> Co-authored-by: Lucain <lucainp@gmail.com> Co-authored-by: Ali Abdalla <ali.si3luwa@gmail.com> * Fix gradio.js aws path (#9397) * fix folder path for beta * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Trigger state change event on iterators (#9299) * Fix render async * add changeset * Fix regression * tests * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * add local fonts and update themes (#9367) * add local fonts and update themes * add changeset * tweak * - dedent css - fix if logic * store theme fonts locally + update themes with `LocalFont` * lint * fix font loading --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: freddyaboulton <alfonsoboulton@gmail.com> * Disable liking user message in chatbot by default but make it configurable (#9323) * Code * add changeset * revert * test" " * typo * Fix code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix Cached Examples for Streamed Media (#9373) * fix problem * add changeset * gitignore * lint * Add code * Add code * Fix extension * add changeset * unit test * typecheck * typecheck * lint * test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fixes annoying height bug in playground (#9402) * fix styling issue * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Chatbot Examples (#8966) * examples * examples * first pass * remove comments * remove comments * add changeset * Fix chatinterface e2e test (#9104) * Refactor test * comment * Fix image * add changeset * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix version + pkg name (#9110) * fix version + pkg name * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix dev (#9115) * fix asset locations * fix changeset * Be able to set optimizeDeps options in gradio.config.js (#9091) * Add code * add changeset * build * Remove unused import --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Better text styling on docs (#9108) * margin and size * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * setup npm-previews of all packages (#9118) * add workflow * fix pkg jsons * workflow name * add changeset * fix * add changeset * fix build command --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix chatinterface multimodal bug (#9119) * Add test * add changeset * comments --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chatbot examples * examples changes * chatinterface * chatinterface * pr fixes * remove html demo change * suggestion width * type fixes * format * comment our examples test * remove cache * comment example caching test * bug fix * bug fix * format * type fix * Proposal: remove `gr.make_waveform` and remove `matplotlib` as a dependency (#9312) * remove matplotlib * add changeset * remove tests, demo * Fix the Lite worker to set the matplotlib backend engine only when the matplotlib package is installed * add changeset * Fix comment --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> * Dont move files to cache automatically in chatbot postprocess (#9303) * Fix * add changeset * Add code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * test fix * format * changes * update guide * cache examples * add changeset * format * changes * changes * changes * changes * changes * changes * format * fixes * test chat interface fixes --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Freddy Boulton <alfonsoboulton@gmail.com> Co-authored-by: pngwn <hello@pngwn.io> Co-authored-by: Ali Abdalla <ali.si3luwa@gmail.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> Co-authored-by: Ali Abid <aliabid94@gmail.com> * Ssr part 2 (#9339) * chore: update versions (beta) (#9263) * Center icon in button when no text is present (#9405) * center button when only icon is present * add changeset * format * add story --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix SSR apps on spaces (#9412) * test * add changeset * test * test * test * fix? * fix? * add changeset * fix * fix * fix * fix * fix finally? * fix * add changeset * lints etc * add changeset * remove spa mode * fix env * typing * change * lint * remove node logs * remove node logs --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Small fixes to `gr.Dataframe` and chatbot docs (#9376) * docs * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9416) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Allow skipping an arbitrary number of output components, and also raise a warning if the number of output components does not match the number of values returned from a function (#9406) * demo * add warnings * add changeset * add changeset * add doc section * format * fix check * fix typing issues * docs * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * fix css (#9427) * fix css * add changeset * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Dawood <dawoodkhan82@gmail.com> * Fix Python unit tests on `5.0-dev` branch (#9432) * fix python unit tests * changes * changes * fix * Lite: HTTPX client improvement (#9413) * Use the httpx client in resolve_with_google_dns both for normal and Lite * add changeset * Set decode_content=False * Add type hints * Set decode_content=True and remove the Content-Encoding header so the content is decoded by urllib3 instead of httpx * Fix * Add comment * Restore the original resolve_with_google_dns to make such changes in another PR * add changeset * Update comment * Updated the test requirements * Fix type hint * Revert "Updated the test requirements" This reverts commit2e43584a87
. --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Use or `pathlib.Path` objects to indicate filepaths for `css`, `js`, and `head` parameters (#9448) * format * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Reduce analytics that are collected (#9447) * reduce analytics collected * analytics * add changeset * bring back css --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix SSR mode flag with `mount_gradio_app` and revert changes to pytests (#9446) * Revert "Fix Python unit tests on `5.0-dev` branch (#9432)" This reverts commit278645b649
. * revert changes to pytest * add changeset * fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Small changes to caching (#9438) * caching changes * add changeset * typo * typo * changes * fix * fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Refactoring playground (#9426) * Use @gradio/code.BaseCode instead of its default export like https://github.com/gradio-app/gradio/pull/8804 * Delete unused code * add changeset * Fix * Rename a variable to be descriptive * Mount single <Code> instance instead of creating one for each demo * Fix the initial value passed to createGradioApp * Use const instead of let * Rename variable * Update * Fix layout * Restore the preset requirements * Delete unused variable * Add type hint * Attach the keydown handler directly to the input element instead of the window object * Add code editor widget --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Chatbot bug fixes (#9453) * image fixes * add changeset * more fixes * fix * fix * css fixes --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Update object detection guide (#9456) * First draft * Add code * update guide * add changeset * revert * edits * Add code * notebooks * fix code --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Start/stop recoding from the backend. Add guide on conversational chatbots (#9419) * Add code * stop displatch * first draft * edit * add changeset * lint * Docstring * Make recording * fix video * fix guide link * redirect * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Improve UI on the Playground (#9462) * ai prompt always there * fix overflow * better ui and suggested prompts * cancel generation and showing erro * formatting * add changeset * fix height issue and button * changes * fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix. Triggered dataframe change event for header change (#9469) * Fix. Triggered dataframe change event for header change * add changeset * lint * add changeset --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9420) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * format * Fix package.json `exports` of @gradio/preview (#9468) * Fix package.json exports of @gradio/preview * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Fix plots (#9464) * fix * add changeset * lint * clean * ts * format * add changeset * format * remove test that is wrong * fixxxxxx * add changeset * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Pre/post-processing download requests (#9383) * changes * add changeset * changes * change * changes * changes * changes * changes * change * changes * changes * changes * changes * changes * changes * changes * Update gradio/processing_utils.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * changes * changes * changes * changes * changes * changes * Fix Lite's ASGI receiver to convert memoryview to bytes as the multipart parser called in98cbcaef82/gradio/route_utils.py (L650)
calls bytes.find() and memoryview objects don't have the method * add changeset * Fix async_get_with_secure_transport to use the unsecure but Pyodide-compatible transport in the case of Wasm --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> * Add support for 3rd party providers to `gr.load`, and provide a better UX for conversational models (#9470) * changes * add changeset * changes * changes * docstring * chatbot * changes * fix test * format * add changeset * update req * remove conversational * add changeset * remove args --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9476) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Fix `slider-color` var (#9481) * fix slider-color * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Playground requirements tab (#9460) * Add a tab navigation to the playground so the user can specify the requirement packages * Add Transformers.js.py demo and fix the playground to install the requirements immediately after switching the demo * add changeset * Format * add changeset * Fix preview flex * Add requirements to the share link and deploy to Spaces buttons * Add requirements.txt to each demo * Format * Update notebooks * Fix * Update --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: aliabd <ali.si3luwa@gmail.com> * Fix prettierignore (#9486) * Minor fixes to docs and a demo (#9493) * small things * docstring * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Check for `file_types` parameter in the backend (#9431) * file check fix * format * add changeset * tests * add changeset * Update gradio/components/file.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Update client/python/gradio_client/utils.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * fixes * fixes * test fix * test fix * test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Ensure media queries work for SSR mode (#9428) * asd * asd * fix * add changeset * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Fix custom component CLI unit tests (#9495) * fix * fix audio test * fix template * add changeset --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fixes: Chatbot examples for custom chatbot + rename `suggestions` -> `examples` (#9488) * fix * add changeset * notebooks * fixes * fix * type fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * UI theme fixes (#9496) * changes * add changeset * changes * changes * add changeset * changes --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Centre components within `Block` when height and width are set (#9504) * add centering margin * add changeset * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Improve `gr.Code` (#9450) * fix check icon on download * * apply unused min_width param * improve gutter spacing * hide `BlockLabel` spacing if `show_label` is false * format * tweak spacing, remove `fit-content` * add changeset * revert height change * fix * allow setting max_lines to None * add line wrapping * add wrap lines param * fix type error * fix py test * fix type check --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix node process to run with correct server name (#9506) * fix node process * add changeset * add changeset * format * cleanup --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * yaml lint * Add Bokeh plot demo (#9423) * Add Bokeh plot demo * Update notebook --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Expanding AI Playground Prompt for Qwen (#9452) * expand prompt for qwen * add changeset * clean * add changeset * heavily modify prompt * changes * many changes * fix weird syntax error * fix * ? * changes * fix requirements * formatting --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * docs: update 01_quickstart.md (#9515) arbitary -> arbitrary Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Fix change triggers for dropdown and radio (#9519) * fix change triggers * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9485) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Fix single select dropdown (#9526) * Set the default value of Dropdown as undefined instead of [] when multiselect=false * Refactoring * add changeset * Fix tests --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Hide x axis labels (#9497) * changes * add changeset * fix --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Add Jinja2 language to Code component (#9545) * Add jinja2 codemirror language * add jinja2 * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Disable sagemaker_check() for now (#9546) * Add is_sagemaker param to Blocks, so sagemaker_check() can be explicitly disabled * revert * add changeset * format * add changeset --------- Co-authored-by: Mate Valko <> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9544) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Update gr.ColorPicker UI (#9570) * update color picker dialog * add changeset * add tinycolor types * fix disabled param * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix cut off in gr.ImageEditor (#9525) * remove default height value * remove canvas w x h * revert comment * add changeset * add changeset * fix stage-wrap shift * empty tweak * add changeset * tweak * type fix * test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Change caching to occur not at the creation of a `gr.Examples()` but when the Blocks is actually launched (#9508) * changes * changes * add changeset * await * add changeset * changes * changes * changes * changes --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Add `css_paths` and `head_paths` parameters (#9524) * paths * add changeset * changes * fixes * add new lines * remove js_paths * add changeset * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix most flaky Python tests in `5.0-dev` branch (#9550) * fix flaky tests * add changeset * token * changes * fixes * hf token * format * test * format * root url * format * fix * fix tests * add changeset * remove huggingface hub fixed version * add changeset * remove print --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Api info fix (#9522) * api-info-fix * add changeset * Add with fallback * route utils * update --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Send Streaming data over Websocket if possible. Also support base64 output format for images. (#9483) * b64 first draft * ws * onMount + demos and guide * guide * add changeset * Add code * lint * type check * Have a fallback * Add code * delete unneccessary input * API info tweaks * Revert type param type hint * Add code * api-info-fix * add changeset * Add with fallback * route utils * update * final tweaks * type check * fix * add changeset * fix * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Allow `info=` to render markdown (#9521) * allow info to render markdown * add changeset * update docstrings * format * fixes * add changeset * fix * add changeset * root * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Equal height columns (#9577) * changes * add changeset * add changeset --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix chatinterface embedding height issues (#9571) * changes * add changeset * changes * changes * lint --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * chore: update versions (beta) (#9572) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Update gr.Dataframe UI with action popover (#9575) * add dialog for actions * add changeset * add story * add changeset * * remove temp select column * change open dialog UX in mobile * fix border * fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Disable the submit button and enter-key submit when the text is empty (#9583) * Disable the submit button and enter-key submit when the text is empty * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Chat Interface Multimodal Fix & Fallback to `gr.Examples()` (#9584) * fic * add changeset * fallback to original examples * add changeset * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Fix `gr.Chatbot` panels layout (#9499) * fix avatar margins * separate component logic out and add message component * fix panel mode and upate chatbot buttons * add changeset * fix type check * fix typecheck * reduce message padding * fix empty message * revert css removal * test * test * Revert "test" This reverts commit40c9e396a1
. * Revert "test" This reverts commit660a6b06ea
. * move message-wrap styes * bubble width + markdown tweak * fix test --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Equal height in row false by default (#9591) * changes * add changeset * changes --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix reload mode (#9576) * Ddebuig * Fixing * fix * notebook * add changeset * SSR MODE * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Chatbot autoscroll (#9582) * Auto scroll on the Chatbot component * Scroll down button's design * Parameterize autoscroll * add changeset * Fix test * Fix * Fix the <Video> component to dispatch the load event after the metadata is loaded * add changeset * Add tick * Fix * Fix * Add loadstart and loadeddata and remove load event forwarder from <Video> because <video> doesn't dispatch the load event * Fix <Player> as well * Fix * Add pending_message as the scroll trigger and remove unnecessary tick * Refactoring <Image> * add changeset * Fix * Fix * icon fix * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Dawood <dawoodkhan82@gmail.com> * Only move files to the cache that have a meta key (#9589) * Fix code * add changeset * Code * test * tests * add changeset * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Some more chatbot fixes (#9593) * some fixes * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix favicon in ssr mode (#9592) * Fix favicon * fix * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * SSR e2e + fixes (#9590) * fix tests in ssr mode * fix loading race condition * fix some tests * add ci * cleanup * format * add changeset * clean * test name * broke it, fix * fix? * clean * lint --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Deep equal check with hash (#9580) * check_equal_by_hash * add changeset * changes * change * changes * hash --------- Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Adding new themes to Gradio 5.0 (#9437) * Adding citrus, colorful and headlines themes * add changeset * Fix from running format_backend * Add new themes to theming guide * Rename headlines theme demo file * changes * add changeset * changes * changes * fix name * revert kitchen sink * ocean and docs * changes * add changeset * changes * changes --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Ali Abid <aliabid94@gmail.com> * Fix custom component CLI on main/5.0 (#9482) * Add code * add changeset * WIP * add changeset * Working SSR * WIP * Proper ssr build * fix paths * fix * revert .vscode change * format * lint * uncomment * fix --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Fix e2e test bug (#9597) * code * try this out * Fix markdown code copy/check button in gr.Chatbot (#9598) * fix broken copy button * tweak * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * Update `README.md` with 5.0 info and GIFs (#9564) * update gif * format * changes * readme * language * Tweak gr.Dataframe menu UX (#9601) * * show menu on click * only show column options in headers * improve spacing * add changeset * fix type check --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> * chore: update versions (beta) (#9586) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Ensure undo/try shows for final bot message in gr.Chatbot (#9600) * fix undo and retry reactivity * add changeset * tweak * fix ts check * changes * changes --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Ali Abid <aliabid94@gmail.com> * chore: update versions (beta) (#9604) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: pngwn <hello@pngwn.io> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Freddy Boulton <alfonsoboulton@gmail.com> Co-authored-by: Ali Abdalla <ali.si3luwa@gmail.com> Co-authored-by: aliabid94 <aabid94@gmail.com> Co-authored-by: Ali Abid <aliabid94@gmail.com> Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> Co-authored-by: Dawood Khan <dawoodkhan82@gmail.com> Co-authored-by: Hannah <hannahblair@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Gradio PR Bot <121576822+gradio-pr-bot@users.noreply.github.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: Tayfun Sen <tayfun.sen@gmail.com> Co-authored-by: Ifeanyi Idiaye <72707830+Ifeanyi55@users.noreply.github.com> Co-authored-by: Charles <charles@huggingface.co> Co-authored-by: Michał Pstrąg <m.pstrag.kontakt@gmail.com> Co-authored-by: Julien Chaumond <julien@huggingface.co> Co-authored-by: Nikita Krasnytskyi <nikita.kras.kyiv@gmail.com> Co-authored-by: Lucain <lucainp@gmail.com> Co-authored-by: Joodith <67360396+Joodith@users.noreply.github.com> Co-authored-by: Col0ring <47329987+Col0ring@users.noreply.github.com> Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com> Co-authored-by: Mate Valko <3168272+vmatt@users.noreply.github.com> Co-authored-by: Allison <allison@huggingface.co>
1616 lines
55 KiB
Python
1616 lines
55 KiB
Python
"""Contains tests for networking.py and app.py"""
|
|
|
|
import functools
|
|
import json
|
|
import os
|
|
import pickle
|
|
import tempfile
|
|
import time
|
|
from contextlib import asynccontextmanager, closing
|
|
from pathlib import Path
|
|
from threading import Thread
|
|
from unittest.mock import patch
|
|
|
|
import gradio_client as grc
|
|
import httpx
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
import requests
|
|
import starlette.routing
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.testclient import TestClient
|
|
from gradio_client import media_data
|
|
|
|
import gradio as gr
|
|
from gradio import (
|
|
Blocks,
|
|
Button,
|
|
Interface,
|
|
Number,
|
|
Textbox,
|
|
close_all,
|
|
routes,
|
|
wasm_utils,
|
|
)
|
|
from gradio.route_utils import (
|
|
API_PREFIX,
|
|
FnIndexInferError,
|
|
compare_passwords_securely,
|
|
get_root_url,
|
|
starts_with_protocol,
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def test_client():
|
|
io = Interface(lambda x: x + x, "text", "text")
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
test_client = TestClient(app)
|
|
yield test_client
|
|
io.close()
|
|
close_all()
|
|
|
|
|
|
class TestRoutes:
|
|
def test_get_main_route(self, test_client):
|
|
response = test_client.get("/")
|
|
assert response.status_code == 200
|
|
|
|
def test_static_files_served_safely(self, test_client):
|
|
# Make sure things outside the static folder are not accessible
|
|
response = test_client.get(r"/static/..%2findex.html")
|
|
assert response.status_code == 403
|
|
response = test_client.get(r"/static/..%2f..%2fapi_docs.html")
|
|
assert response.status_code == 403
|
|
|
|
def test_get_config_route(self, test_client):
|
|
response = test_client.get("/config/")
|
|
assert response.status_code == 200
|
|
|
|
def test_favicon_route(self, test_client):
|
|
response = test_client.get("/favicon.ico")
|
|
assert response.status_code == 200
|
|
|
|
def test_upload_path(self, test_client):
|
|
with open("test/test_files/alphabet.txt", "rb") as f:
|
|
response = test_client.post(f"{API_PREFIX}/upload", files={"files": f})
|
|
assert response.status_code == 200
|
|
file = response.json()[0]
|
|
assert "alphabet" in file
|
|
assert file.endswith(".txt")
|
|
with open(file, "rb") as saved_file:
|
|
assert saved_file.read() == b"abcdefghijklmnopqrstuvwxyz"
|
|
|
|
def test_custom_upload_path(self, gradio_temp_dir):
|
|
io = Interface(lambda x: x + x, "text", "text")
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
test_client = TestClient(app)
|
|
with open("test/test_files/alphabet.txt", "rb") as f:
|
|
response = test_client.post(f"{API_PREFIX}/upload", files={"files": f})
|
|
assert response.status_code == 200
|
|
file = response.json()[0]
|
|
assert "alphabet" in file
|
|
assert file.startswith(str(gradio_temp_dir))
|
|
assert file.endswith(".txt")
|
|
with open(file, "rb") as saved_file:
|
|
assert saved_file.read() == b"abcdefghijklmnopqrstuvwxyz"
|
|
|
|
def test_predict_route(self, test_client):
|
|
response = test_client.post(
|
|
f"{API_PREFIX}/api/predict/", json={"data": ["test"], "fn_index": 0}
|
|
)
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["testtest"]
|
|
|
|
def test_named_predict_route(self):
|
|
with Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
i.change(lambda x: f"{x}1", i, o, api_name="p")
|
|
i.change(lambda x: f"{x}2", i, o, api_name="q")
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(f"{API_PREFIX}/api/p/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test1"]
|
|
|
|
response = client.post(f"{API_PREFIX}/api/q/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test2"]
|
|
|
|
def test_same_named_predict_route(self):
|
|
with Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
i.change(lambda x: f"{x}0", i, o, api_name="p")
|
|
i.change(lambda x: f"{x}1", i, o, api_name="p")
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(f"{API_PREFIX}/api/p/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test0"]
|
|
|
|
response = client.post(f"{API_PREFIX}/api/p_1/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test1"]
|
|
|
|
def test_multiple_renamed(self):
|
|
with Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
i.change(lambda x: f"{x}0", i, o, api_name="p")
|
|
i.change(lambda x: f"{x}1", i, o, api_name="p")
|
|
i.change(lambda x: f"{x}2", i, o, api_name="p_1")
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(f"{API_PREFIX}/api/p/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test0"]
|
|
|
|
response = client.post(f"{API_PREFIX}/api/p_1/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test1"]
|
|
|
|
response = client.post(f"{API_PREFIX}/api/p_1_1/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test2"]
|
|
|
|
def test_predict_route_without_fn_index(self, test_client):
|
|
response = test_client.post(
|
|
f"{API_PREFIX}/api/predict/", json={"data": ["test"]}
|
|
)
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["testtest"]
|
|
|
|
def test_predict_route_batching(self):
|
|
def batch_fn(x):
|
|
results = []
|
|
for word in x:
|
|
results.append(f"Hello {word}")
|
|
return (results,)
|
|
|
|
with gr.Blocks() as demo:
|
|
text = gr.Textbox()
|
|
btn = gr.Button()
|
|
btn.click(batch_fn, inputs=text, outputs=text, batch=True, api_name="pred")
|
|
|
|
demo.queue(api_open=True)
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(f"{API_PREFIX}/api/pred/", json={"data": ["test"]})
|
|
output = dict(response.json())
|
|
assert output["data"] == ["Hello test"]
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(
|
|
f"{API_PREFIX}/api/pred/",
|
|
json={"data": [["test", "test2"]], "batched": True},
|
|
)
|
|
output = dict(response.json())
|
|
assert output["data"] == [["Hello test", "Hello test2"]]
|
|
|
|
def test_state(self):
|
|
def predict(input, history):
|
|
if history is None:
|
|
history = ""
|
|
history += input
|
|
return history, history
|
|
|
|
io = Interface(predict, ["textbox", "state"], ["textbox", "state"])
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.post(
|
|
f"{API_PREFIX}/api/predict/",
|
|
json={"data": ["test", None], "fn_index": 0, "session_hash": "_"},
|
|
)
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test", None]
|
|
response = client.post(
|
|
f"{API_PREFIX}/api/predict/",
|
|
json={"data": ["test", None], "fn_index": 0, "session_hash": "_"},
|
|
)
|
|
output = dict(response.json())
|
|
assert output["data"] == ["testtest", None]
|
|
|
|
def test_get_allowed_paths(self):
|
|
allowed_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
allowed_file.write(media_data.BASE64_IMAGE)
|
|
allowed_file.flush()
|
|
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
file_response = client.get(f"{API_PREFIX}/file={allowed_file.name}")
|
|
assert file_response.status_code == 403
|
|
io.close()
|
|
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(
|
|
prevent_thread_lock=True,
|
|
allowed_paths=[os.path.dirname(allowed_file.name)],
|
|
)
|
|
client = TestClient(app)
|
|
file_response = client.get(f"{API_PREFIX}/file={allowed_file.name}")
|
|
assert file_response.status_code == 200
|
|
assert len(file_response.text) == len(media_data.BASE64_IMAGE)
|
|
io.close()
|
|
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(
|
|
prevent_thread_lock=True,
|
|
allowed_paths=[os.path.abspath(allowed_file.name)],
|
|
)
|
|
client = TestClient(app)
|
|
file_response = client.get(f"{API_PREFIX}/file={allowed_file.name}")
|
|
assert file_response.status_code == 200
|
|
assert len(file_response.text) == len(media_data.BASE64_IMAGE)
|
|
io.close()
|
|
|
|
def test_response_attachment_format(self):
|
|
image_file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".png")
|
|
image_file.write(media_data.BASE64_IMAGE)
|
|
image_file.flush()
|
|
|
|
html_file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".html")
|
|
html_file.write("<html>Hello, world!</html>")
|
|
html_file.flush()
|
|
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(
|
|
prevent_thread_lock=True,
|
|
allowed_paths=[
|
|
image_file.name,
|
|
html_file.name,
|
|
],
|
|
)
|
|
|
|
html_file2 = tempfile.NamedTemporaryFile(
|
|
mode="w", delete=False, suffix=".html", dir=app.uploaded_file_dir
|
|
)
|
|
html_file2.write("<html>Hello, world!</html>")
|
|
html_file2.flush()
|
|
html_file2_name = str(Path(app.uploaded_file_dir) / html_file2.name)
|
|
|
|
client = TestClient(app)
|
|
|
|
file_response = client.get(f"{API_PREFIX}/file={image_file.name}")
|
|
assert file_response.headers["Content-Type"] == "image/png"
|
|
assert "inline" in file_response.headers["Content-Disposition"]
|
|
|
|
file_response = client.get(f"{API_PREFIX}/file={html_file.name}")
|
|
assert file_response.headers["Content-Type"] == "text/html; charset=utf-8"
|
|
assert "inline" in file_response.headers["Content-Disposition"]
|
|
|
|
file_response = client.get(f"{API_PREFIX}/file={html_file2_name}")
|
|
assert file_response.headers["Content-Type"] == "application/octet-stream"
|
|
assert "attachment" in file_response.headers["Content-Disposition"]
|
|
|
|
def test_allowed_and_blocked_paths(self):
|
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(
|
|
prevent_thread_lock=True,
|
|
allowed_paths=[os.path.dirname(tmp_file.name)],
|
|
)
|
|
client = TestClient(app)
|
|
file_response = client.get(f"{API_PREFIX}/file={tmp_file.name}")
|
|
assert file_response.status_code == 200
|
|
io.close()
|
|
os.remove(tmp_file.name)
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(
|
|
prevent_thread_lock=True,
|
|
allowed_paths=[os.path.dirname(tmp_file.name)],
|
|
blocked_paths=[os.path.dirname(tmp_file.name)],
|
|
)
|
|
client = TestClient(app)
|
|
file_response = client.get(f"{API_PREFIX}/file={tmp_file.name}")
|
|
assert file_response.status_code == 403
|
|
io.close()
|
|
os.remove(tmp_file.name)
|
|
|
|
def test_get_file_created_by_app(self, test_client):
|
|
app, _, _ = gr.Interface(lambda s: s.name, gr.File(), gr.File()).launch(
|
|
prevent_thread_lock=True
|
|
)
|
|
client = TestClient(app)
|
|
with open("test/test_files/alphabet.txt", "rb") as f:
|
|
file_response = test_client.post(f"{API_PREFIX}/upload", files={"files": f})
|
|
response = client.post(
|
|
f"{API_PREFIX}/api/predict/",
|
|
json={
|
|
"data": [
|
|
{
|
|
"path": file_response.json()[0],
|
|
"size": os.path.getsize("test/test_files/alphabet.txt"),
|
|
}
|
|
],
|
|
"fn_index": 0,
|
|
"session_hash": "_",
|
|
},
|
|
).json()
|
|
created_file = response["data"][0]["path"]
|
|
file_response = client.get(f"{API_PREFIX}/file={created_file}")
|
|
assert file_response.is_success
|
|
|
|
backwards_compatible_file_response = client.get(
|
|
f"{API_PREFIX}/file/{created_file}"
|
|
)
|
|
assert backwards_compatible_file_response.is_success
|
|
|
|
file_response_with_full_range = client.get(
|
|
f"{API_PREFIX}/file={created_file}", headers={"Range": "bytes=0-"}
|
|
)
|
|
assert file_response_with_full_range.is_success
|
|
assert file_response.text == file_response_with_full_range.text
|
|
|
|
file_response_with_partial_range = client.get(
|
|
f"{API_PREFIX}/file={created_file}", headers={"Range": "bytes=0-10"}
|
|
)
|
|
assert file_response_with_partial_range.is_success
|
|
assert len(file_response_with_partial_range.text) == 11
|
|
|
|
def test_mount_gradio_app(self):
|
|
app = FastAPI()
|
|
|
|
demo = gr.Interface(
|
|
lambda s: f"Hello from ps, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
demo1 = gr.Interface(
|
|
lambda s: f"Hello from py, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
|
|
app = gr.mount_gradio_app(app, demo, path="/ps")
|
|
app = gr.mount_gradio_app(app, demo1, path="/py")
|
|
|
|
# Use context manager to trigger start up events
|
|
with TestClient(app) as client:
|
|
assert client.get("/ps").is_success
|
|
assert client.get("/py").is_success
|
|
|
|
def test_mount_gradio_app_with_app_kwargs(self):
|
|
app = FastAPI()
|
|
demo = gr.Interface(lambda s: f"You said {s}!", "textbox", "textbox").queue()
|
|
app = gr.mount_gradio_app(
|
|
app,
|
|
demo,
|
|
path="/echo",
|
|
app_kwargs={"docs_url": "/docs-custom"},
|
|
)
|
|
# Use context manager to trigger start up events
|
|
with TestClient(app) as client:
|
|
assert client.get("/echo/docs-custom").is_success
|
|
|
|
def test_mount_gradio_app_with_auth_and_params(self):
|
|
app = FastAPI()
|
|
demo = gr.Interface(lambda s: f"You said {s}!", "textbox", "textbox").queue()
|
|
app = gr.mount_gradio_app(
|
|
app,
|
|
demo,
|
|
path=f"{API_PREFIX}/echo",
|
|
auth=("a", "b"),
|
|
root_path=f"{API_PREFIX}/echo",
|
|
allowed_paths=["test/test_files/bus.png"],
|
|
)
|
|
# Use context manager to trigger start up events
|
|
with TestClient(app) as client:
|
|
assert client.get(f"{API_PREFIX}/echo/config").status_code == 401
|
|
assert demo.root_path == f"{API_PREFIX}/echo"
|
|
assert demo.allowed_paths == ["test/test_files/bus.png"]
|
|
assert demo.show_error
|
|
|
|
def test_mount_gradio_app_with_lifespan(self):
|
|
@asynccontextmanager
|
|
async def empty_lifespan(app: FastAPI):
|
|
yield
|
|
|
|
app = FastAPI(lifespan=empty_lifespan)
|
|
|
|
demo = gr.Interface(
|
|
lambda s: f"Hello from ps, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
demo1 = gr.Interface(
|
|
lambda s: f"Hello from py, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
|
|
app = gr.mount_gradio_app(app, demo, path="/ps")
|
|
app = gr.mount_gradio_app(app, demo1, path="/py")
|
|
|
|
# Use context manager to trigger start up events
|
|
with TestClient(app) as client:
|
|
assert client.get("/ps").is_success
|
|
assert client.get("/py").is_success
|
|
|
|
def test_mount_gradio_app_with_startup(self):
|
|
app = FastAPI()
|
|
|
|
@app.on_event("startup")
|
|
async def empty_startup():
|
|
return
|
|
|
|
demo = gr.Interface(
|
|
lambda s: f"Hello from ps, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
demo1 = gr.Interface(
|
|
lambda s: f"Hello from py, {s}!", "textbox", "textbox"
|
|
).queue()
|
|
|
|
app = gr.mount_gradio_app(app, demo, path="/ps")
|
|
app = gr.mount_gradio_app(app, demo1, path="/py")
|
|
|
|
# Use context manager to trigger start up events
|
|
with TestClient(app) as client:
|
|
assert client.get("/ps").is_success
|
|
assert client.get("/py").is_success
|
|
|
|
def test_gradio_app_with_auth_dependency(self):
|
|
def block_anonymous(request: Request):
|
|
return request.headers.get("user")
|
|
|
|
demo = gr.Interface(lambda s: s, "textbox", "textbox")
|
|
app, _, _ = demo.launch(
|
|
auth_dependency=block_anonymous, prevent_thread_lock=True
|
|
)
|
|
|
|
with TestClient(app) as client:
|
|
assert not client.get("/", headers={}).is_success
|
|
assert client.get("/", headers={"user": "abubakar"}).is_success
|
|
|
|
def test_mount_gradio_app_with_auth_dependency(self):
|
|
app = FastAPI()
|
|
|
|
def get_user(request: Request):
|
|
return request.headers.get("user")
|
|
|
|
demo = gr.Interface(lambda s: f"Hello from ps, {s}!", "textbox", "textbox")
|
|
|
|
app = gr.mount_gradio_app(app, demo, path="/demo", auth_dependency=get_user)
|
|
|
|
with TestClient(app) as client:
|
|
assert client.get("/demo", headers={"user": "abubakar"}).is_success
|
|
assert not client.get("/demo").is_success
|
|
|
|
def test_static_file_missing(self, test_client):
|
|
response = test_client.get(rf"{API_PREFIX}/static/not-here.js")
|
|
assert response.status_code == 404
|
|
|
|
def test_asset_file_missing(self, test_client):
|
|
response = test_client.get(rf"{API_PREFIX}/assets/not-here.js")
|
|
assert response.status_code == 404
|
|
|
|
def test_cannot_access_files_in_working_directory(self, test_client):
|
|
response = test_client.get(rf"{API_PREFIX}/file=not-here.js")
|
|
assert response.status_code == 403
|
|
response = test_client.get(rf"{API_PREFIX}/file=subdir/.env")
|
|
assert response.status_code == 403
|
|
|
|
def test_cannot_access_directories_in_working_directory(self, test_client):
|
|
response = test_client.get(rf"{API_PREFIX}/file=gradio")
|
|
assert response.status_code == 403
|
|
|
|
def test_block_protocols_that_expose_windows_credentials(self, test_client):
|
|
response = test_client.get(rf"{API_PREFIX}/file=//11.0.225.200/share")
|
|
assert response.status_code == 403
|
|
|
|
def test_do_not_expose_existence_of_files_outside_working_directory(
|
|
self, test_client
|
|
):
|
|
response = test_client.get(
|
|
rf"{API_PREFIX}/file=../fake-file-that-does-not-exist.js"
|
|
)
|
|
assert response.status_code == 403 # not a 404
|
|
|
|
def test_proxy_route_is_restricted_to_load_urls(self):
|
|
gr.context.Context.hf_token = "abcdef" # type: ignore
|
|
app = routes.App()
|
|
interface = gr.Interface(lambda x: x, "text", "text")
|
|
app.configure_app(interface)
|
|
with pytest.raises(PermissionError):
|
|
app.build_proxy_request(
|
|
"https://gradio-tests-test-loading-examples-private.hf.space/file=Bunny.obj"
|
|
)
|
|
with pytest.raises(PermissionError):
|
|
app.build_proxy_request("https://google.com")
|
|
interface.proxy_urls = {
|
|
"https://gradio-tests-test-loading-examples-private.hf.space"
|
|
}
|
|
app.build_proxy_request(
|
|
"https://gradio-tests-test-loading-examples-private.hf.space/file=Bunny.obj"
|
|
)
|
|
|
|
def test_proxy_does_not_leak_hf_token_externally(self):
|
|
gr.context.Context.hf_token = "abcdef" # type: ignore
|
|
app = routes.App()
|
|
interface = gr.Interface(lambda x: x, "text", "text")
|
|
interface.proxy_urls = {
|
|
"https://gradio-tests-test-loading-examples-private.hf.space",
|
|
"https://google.com",
|
|
}
|
|
app.configure_app(interface)
|
|
r = app.build_proxy_request(
|
|
"https://gradio-tests-test-loading-examples-private.hf.space/file=Bunny.obj"
|
|
)
|
|
assert "authorization" in dict(r.headers)
|
|
r = app.build_proxy_request("https://google.com")
|
|
assert "authorization" not in dict(r.headers)
|
|
|
|
def test_can_get_config_that_includes_non_pickle_able_objects(self):
|
|
my_dict = {"a": 1, "b": 2, "c": 3}
|
|
with Blocks() as demo:
|
|
gr.JSON(my_dict.keys()) # type: ignore
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.get("/")
|
|
assert response.is_success
|
|
response = client.get("/config/")
|
|
assert response.is_success
|
|
|
|
def test_default_cors_restrictions(self):
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
custom_headers = {
|
|
"host": "localhost:7860",
|
|
"origin": "https://example.com",
|
|
}
|
|
file_response = client.get(f"{API_PREFIX}/config", headers=custom_headers)
|
|
assert "access-control-allow-origin" not in file_response.headers
|
|
|
|
custom_headers = {
|
|
"host": "localhost:7860",
|
|
"origin": "null",
|
|
}
|
|
file_response = client.get(f"{API_PREFIX}/config", headers=custom_headers)
|
|
assert "access-control-allow-origin" not in file_response.headers
|
|
|
|
custom_headers = {
|
|
"host": "localhost:7860",
|
|
"origin": "127.0.0.1",
|
|
}
|
|
file_response = client.get(f"{API_PREFIX}/config", headers=custom_headers)
|
|
assert file_response.headers["access-control-allow-origin"] == "127.0.0.1"
|
|
|
|
io.close()
|
|
|
|
def test_loose_cors_restrictions(self):
|
|
io = gr.Interface(lambda s: s.name, gr.File(), gr.File())
|
|
app, _, _ = io.launch(prevent_thread_lock=True, strict_cors=False)
|
|
client = TestClient(app)
|
|
custom_headers = {
|
|
"host": "localhost:7860",
|
|
"origin": "https://example.com",
|
|
}
|
|
file_response = client.get(f"{API_PREFIX}/config", headers=custom_headers)
|
|
assert "access-control-allow-origin" not in file_response.headers
|
|
|
|
custom_headers = {
|
|
"host": "localhost:7860",
|
|
"origin": "null",
|
|
}
|
|
file_response = client.get(f"{API_PREFIX}/config", headers=custom_headers)
|
|
assert file_response.headers["access-control-allow-origin"] == "null"
|
|
|
|
io.close()
|
|
|
|
def test_delete_cache(self, connect, gradio_temp_dir, capsys):
|
|
def check_num_files_exist(blocks: Blocks):
|
|
num_files = 0
|
|
for temp_file_set in blocks.temp_file_sets:
|
|
for temp_file in temp_file_set:
|
|
if os.path.exists(temp_file):
|
|
num_files += 1
|
|
return num_files
|
|
|
|
demo = gr.Interface(lambda s: s, gr.Textbox(), gr.File(), delete_cache=None)
|
|
with connect(demo) as client:
|
|
client.predict("test/test_files/cheetah1.jpg")
|
|
assert check_num_files_exist(demo) == 1
|
|
|
|
demo_delete = gr.Interface(
|
|
lambda s: s, gr.Textbox(), gr.File(), delete_cache=(60, 30)
|
|
)
|
|
with connect(demo_delete) as client:
|
|
client.predict("test/test_files/alphabet.txt")
|
|
client.predict("test/test_files/bus.png")
|
|
assert check_num_files_exist(demo_delete) == 2
|
|
assert check_num_files_exist(demo_delete) == 0
|
|
assert check_num_files_exist(demo) == 1
|
|
|
|
@asynccontextmanager
|
|
async def mylifespan(app: FastAPI):
|
|
print("IN CUSTOM LIFESPAN")
|
|
yield
|
|
print("AFTER CUSTOM LIFESPAN")
|
|
|
|
demo_custom_lifespan = gr.Interface(
|
|
lambda s: s, gr.Textbox(), gr.File(), delete_cache=(5, 1)
|
|
)
|
|
|
|
with connect(
|
|
demo_custom_lifespan, app_kwargs={"lifespan": mylifespan}
|
|
) as client:
|
|
client.predict("test/test_files/alphabet.txt")
|
|
assert check_num_files_exist(demo_custom_lifespan) == 0
|
|
captured = capsys.readouterr()
|
|
assert "IN CUSTOM LIFESPAN" in captured.out
|
|
assert "AFTER CUSTOM LIFESPAN" in captured.out
|
|
|
|
def test_monitoring_link(self):
|
|
with Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
i.change(lambda x: x, i, o)
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
response = client.get(f"{API_PREFIX}/monitoring")
|
|
assert response.status_code == 200
|
|
|
|
def test_monitoring_link_disabled(self):
|
|
with Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
i.change(lambda x: x, i, o)
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True, enable_monitoring=False)
|
|
client = TestClient(app)
|
|
response = client.get(f"{API_PREFIX}/monitoring")
|
|
assert response.status_code == 403
|
|
|
|
|
|
class TestApp:
|
|
def test_create_app(self):
|
|
app = routes.App.create_app(Interface(lambda x: x, "text", "text"))
|
|
assert isinstance(app, FastAPI)
|
|
|
|
|
|
class TestAuthenticatedRoutes:
|
|
def test_post_login(self):
|
|
io = Interface(lambda x: x, "text", "text")
|
|
app, _, _ = io.launch(
|
|
auth=("test", "correct_password"),
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(
|
|
"/login",
|
|
data={"username": "test", "password": "correct_password"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
response = client.post(
|
|
"/login",
|
|
data={"username": "test", "password": "incorrect_password"},
|
|
)
|
|
assert response.status_code == 400
|
|
|
|
client.post(
|
|
"/login",
|
|
data={"username": "test", "password": "correct_password"},
|
|
)
|
|
response = client.post(
|
|
"/login",
|
|
data={"username": " test ", "password": "correct_password"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
def test_logout(self):
|
|
io = Interface(lambda x: x, "text", "text")
|
|
app, _, _ = io.launch(
|
|
auth=("test", "correct_password"),
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
client.post(
|
|
"/login",
|
|
data={"username": "test", "password": "correct_password"},
|
|
)
|
|
|
|
response = client.post(
|
|
f"{API_PREFIX}/run/predict",
|
|
json={"data": ["test"]},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
response = client.get("/logout")
|
|
|
|
response = client.post(
|
|
"{API_PREFIX}/run/predict",
|
|
json={"data": ["test"]},
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
def test_monitoring_route(self):
|
|
io = Interface(lambda x: x, "text", "text")
|
|
app, _, _ = io.launch(
|
|
auth=("test", "correct_password"),
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
client.post(
|
|
"/login",
|
|
data={"username": "test", "password": "correct_password"},
|
|
)
|
|
|
|
response = client.get(
|
|
f"{API_PREFIX}/monitoring",
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
response = client.get("/logout")
|
|
|
|
response = client.get(
|
|
f"{API_PREFIX}/monitoring",
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
class TestQueueRoutes:
|
|
@pytest.mark.asyncio
|
|
async def test_queue_join_routes_sets_app_if_none_set(self):
|
|
io = Interface(lambda x: x, "text", "text").queue()
|
|
io.launch(prevent_thread_lock=True)
|
|
assert io.local_url
|
|
client = grc.Client(io.local_url)
|
|
client.predict("test")
|
|
|
|
assert io._queue.server_app == io.server_app
|
|
|
|
|
|
class TestDevMode:
|
|
def test_mount_gradio_app_set_dev_mode_false(self):
|
|
app = FastAPI()
|
|
|
|
@app.get(f"{API_PREFIX}/")
|
|
def read_main():
|
|
return {"message": "Hello!"}
|
|
|
|
with gr.Blocks() as blocks:
|
|
gr.Textbox("Hello from gradio!")
|
|
|
|
app = routes.mount_gradio_app(app, blocks, path=f"{API_PREFIX}/gradio")
|
|
gradio_fast_api = next(
|
|
route for route in app.routes if isinstance(route, starlette.routing.Mount)
|
|
)
|
|
assert not gradio_fast_api.app.blocks.dev_mode # type: ignore
|
|
|
|
|
|
class TestPassingRequest:
|
|
def test_request_included_with_interface(self):
|
|
def identity(name, request: gr.Request):
|
|
assert isinstance(request.client.host, str)
|
|
return name
|
|
|
|
app, _, _ = gr.Interface(identity, "textbox", "textbox").launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/predict/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test"]
|
|
|
|
def test_request_included_with_chat_interface(self):
|
|
def identity(x, y, request: gr.Request):
|
|
assert isinstance(request.client.host, str)
|
|
return x
|
|
|
|
app, _, _ = gr.ChatInterface(identity).launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/chat/", json={"data": ["test", None]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test", None]
|
|
|
|
def test_request_included_with_chat_interface_when_streaming(self):
|
|
def identity(x, y, request: gr.Request):
|
|
assert isinstance(request.client.host, str)
|
|
for i in range(len(x)):
|
|
yield x[: i + 1]
|
|
|
|
app, _, _ = (
|
|
gr.ChatInterface(identity)
|
|
.queue(api_open=True)
|
|
.launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/chat/", json={"data": ["test", None]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["t", None]
|
|
|
|
def test_request_get_headers(self):
|
|
def identity(name, request: gr.Request):
|
|
assert isinstance(request.headers["user-agent"], str)
|
|
assert isinstance(request.headers.items(), list)
|
|
assert isinstance(request.headers.keys(), list)
|
|
assert isinstance(request.headers.values(), list)
|
|
assert isinstance(dict(request.headers), dict)
|
|
user_agent = request.headers["user-agent"]
|
|
assert "testclient" in user_agent
|
|
return name
|
|
|
|
app, _, _ = gr.Interface(identity, "textbox", "textbox").launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/predict/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test"]
|
|
|
|
def test_request_includes_username_as_none_if_no_auth(self):
|
|
def identity(name, request: gr.Request):
|
|
assert request.username is None
|
|
return name
|
|
|
|
app, _, _ = gr.Interface(identity, "textbox", "textbox").launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/predict/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test"]
|
|
|
|
def test_request_includes_username_with_auth(self):
|
|
def identity(name, request: gr.Request):
|
|
assert request.username == "admin"
|
|
return name
|
|
|
|
app, _, _ = gr.Interface(identity, "textbox", "textbox").launch(
|
|
prevent_thread_lock=True, auth=("admin", "password")
|
|
)
|
|
client = TestClient(app)
|
|
|
|
client.post(
|
|
"/login",
|
|
data={"username": "admin", "password": "password"},
|
|
)
|
|
response = client.post(f"{API_PREFIX}/api/predict/", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test"]
|
|
|
|
def test_request_is_pickleable(self):
|
|
"""
|
|
For ZeroGPU, we need to ensure that the gr.Request object is pickle-able.
|
|
"""
|
|
|
|
def identity(name, request: gr.Request):
|
|
pickled = pickle.dumps(request)
|
|
unpickled = pickle.loads(pickled)
|
|
assert request.client.host == unpickled.client.host
|
|
assert request.client.port == unpickled.client.port
|
|
assert dict(request.query_params) == dict(unpickled.query_params)
|
|
assert request.query_params["a"] == unpickled.query_params["a"]
|
|
assert dict(request.headers) == dict(unpickled.headers)
|
|
assert request.username == unpickled.username
|
|
return name
|
|
|
|
app, _, _ = gr.Interface(identity, "textbox", "textbox").launch(
|
|
prevent_thread_lock=True,
|
|
)
|
|
client = TestClient(app)
|
|
|
|
response = client.post(f"{API_PREFIX}/api/predict?a=b", json={"data": ["test"]})
|
|
assert response.status_code == 200
|
|
output = dict(response.json())
|
|
assert output["data"] == ["test"]
|
|
|
|
|
|
def test_predict_route_is_blocked_if_api_open_false():
|
|
io = Interface(lambda x: x, "text", "text", examples=[["freddy"]]).queue(
|
|
api_open=False
|
|
)
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
assert io.show_api
|
|
client = TestClient(app)
|
|
result = client.post(
|
|
f"{API_PREFIX}/api/predict",
|
|
json={"fn_index": 0, "data": [5], "session_hash": "foo"},
|
|
)
|
|
assert result.status_code == 404
|
|
|
|
|
|
def test_predict_route_not_blocked_if_queue_disabled():
|
|
with Blocks() as demo:
|
|
input = Textbox()
|
|
output = Textbox()
|
|
number = Number()
|
|
button = Button()
|
|
button.click(
|
|
lambda x: f"Hello, {x}!", input, output, queue=False, api_name="not_blocked"
|
|
)
|
|
button.click(lambda: 42, None, number, queue=True, api_name="blocked")
|
|
app, _, _ = demo.queue(api_open=False).launch(
|
|
prevent_thread_lock=True, show_api=True
|
|
)
|
|
assert demo.show_api
|
|
client = TestClient(app)
|
|
|
|
result = client.post(
|
|
f"{API_PREFIX}/api/blocked", json={"data": [], "session_hash": "foo"}
|
|
)
|
|
assert result.status_code == 404
|
|
result = client.post(
|
|
f"{API_PREFIX}/api/not_blocked",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
)
|
|
assert result.status_code == 200
|
|
assert result.json()["data"] == ["Hello, freddy!"]
|
|
|
|
|
|
def test_predict_route_not_blocked_if_routes_open():
|
|
with Blocks() as demo:
|
|
input = Textbox()
|
|
output = Textbox()
|
|
button = Button()
|
|
button.click(
|
|
lambda x: f"Hello, {x}!", input, output, queue=True, api_name="not_blocked"
|
|
)
|
|
app, _, _ = demo.queue(api_open=True).launch(
|
|
prevent_thread_lock=True, show_api=False
|
|
)
|
|
assert not demo.show_api
|
|
client = TestClient(app)
|
|
|
|
result = client.post(
|
|
f"{API_PREFIX}/api/not_blocked",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
)
|
|
assert result.status_code == 200
|
|
assert result.json()["data"] == ["Hello, freddy!"]
|
|
|
|
demo.close()
|
|
demo.queue(api_open=False).launch(prevent_thread_lock=True, show_api=False)
|
|
assert not demo.show_api
|
|
|
|
|
|
def test_show_api_queue_not_enabled():
|
|
io = Interface(lambda x: x, "text", "text", examples=[["freddy"]])
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
assert io.show_api
|
|
io.close()
|
|
io.launch(prevent_thread_lock=True, show_api=False)
|
|
assert not io.show_api
|
|
|
|
|
|
def test_orjson_serialization():
|
|
df = pd.DataFrame(
|
|
{
|
|
"date_1": pd.date_range("2021-01-01", periods=2),
|
|
"date_2": pd.date_range("2022-02-15", periods=2).strftime("%B %d, %Y, %r"),
|
|
"number": np.array([0.2233, 0.57281]),
|
|
"number_2": np.array([84, 23]).astype(np.int64),
|
|
"bool": [True, False],
|
|
"markdown": ["# Hello", "# Goodbye"],
|
|
}
|
|
)
|
|
|
|
with gr.Blocks() as demo:
|
|
gr.DataFrame(df)
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
test_client = TestClient(app)
|
|
response = test_client.get("/")
|
|
assert response.status_code == 200
|
|
demo.close()
|
|
|
|
|
|
def test_api_name_set_for_all_events(connect):
|
|
with gr.Blocks() as demo:
|
|
i = Textbox()
|
|
o = Textbox()
|
|
btn = Button()
|
|
btn1 = Button()
|
|
btn2 = Button()
|
|
btn3 = Button()
|
|
btn4 = Button()
|
|
btn5 = Button()
|
|
btn6 = Button()
|
|
btn7 = Button()
|
|
btn8 = Button()
|
|
|
|
def greet(i):
|
|
return "Hello " + i
|
|
|
|
def goodbye(i):
|
|
return "Goodbye " + i
|
|
|
|
def greet_me(i):
|
|
return "Hello"
|
|
|
|
def say_goodbye(i):
|
|
return "Goodbye"
|
|
|
|
say_goodbye.__name__ = "Say_$$_goodbye"
|
|
|
|
# Otherwise changed by ruff
|
|
foo = lambda s: s # noqa
|
|
|
|
def foo2(s):
|
|
return s + " foo"
|
|
|
|
foo2.__name__ = "foo-2"
|
|
|
|
class Callable:
|
|
def __call__(self, a) -> str:
|
|
return "From __call__"
|
|
|
|
def from_partial(a, b):
|
|
return b + a
|
|
|
|
part = functools.partial(from_partial, b="From partial: ")
|
|
|
|
btn.click(greet, i, o)
|
|
btn1.click(goodbye, i, o)
|
|
btn2.click(greet_me, i, o)
|
|
btn3.click(say_goodbye, i, o)
|
|
btn4.click(None, i, o)
|
|
btn5.click(foo, i, o)
|
|
btn6.click(foo2, i, o)
|
|
btn7.click(Callable(), i, o)
|
|
btn8.click(part, i, o)
|
|
|
|
with closing(demo) as io:
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/greet", json={"data": ["freddy"], "session_hash": "foo"}
|
|
).json()["data"] == ["Hello freddy"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/goodbye",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
).json()["data"] == ["Goodbye freddy"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/greet_me",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
).json()["data"] == ["Hello"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/Say__goodbye",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
).json()["data"] == ["Goodbye"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/lambda", json={"data": ["freddy"], "session_hash": "foo"}
|
|
).json()["data"] == ["freddy"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/foo-2", json={"data": ["freddy"], "session_hash": "foo"}
|
|
).json()["data"] == ["freddy foo"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/Callable",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
).json()["data"] == ["From __call__"]
|
|
assert client.post(
|
|
f"{API_PREFIX}/api/partial",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
).json()["data"] == ["From partial: freddy"]
|
|
with pytest.raises(FnIndexInferError):
|
|
client.post(
|
|
f"{API_PREFIX}/api/Say_goodbye",
|
|
json={"data": ["freddy"], "session_hash": "foo"},
|
|
)
|
|
|
|
with connect(demo) as client:
|
|
assert client.predict("freddy", api_name="/greet") == "Hello freddy"
|
|
assert client.predict("freddy", api_name="/goodbye") == "Goodbye freddy"
|
|
assert client.predict("freddy", api_name="/greet_me") == "Hello"
|
|
assert client.predict("freddy", api_name="/Say__goodbye") == "Goodbye"
|
|
|
|
|
|
class TestShowAPI:
|
|
@patch.object(wasm_utils, "IS_WASM", True)
|
|
def test_show_api_false_when_is_wasm_true(self):
|
|
interface = Interface(lambda x: x, "text", "text", examples=[["hannah"]])
|
|
assert (
|
|
interface.show_api is False
|
|
), "show_api should be False when IS_WASM is True"
|
|
|
|
@patch.object(wasm_utils, "IS_WASM", False)
|
|
def test_show_api_true_when_is_wasm_false(self):
|
|
interface = Interface(lambda x: x, "text", "text", examples=[["hannah"]])
|
|
assert (
|
|
interface.show_api is True
|
|
), "show_api should be True when IS_WASM is False"
|
|
|
|
|
|
def test_component_server_endpoints(connect):
|
|
here = os.path.dirname(os.path.abspath(__file__))
|
|
with gr.Blocks() as demo:
|
|
file_explorer = gr.FileExplorer(root_dir=here)
|
|
|
|
with closing(demo) as io:
|
|
app, _, _ = io.launch(prevent_thread_lock=True)
|
|
client = TestClient(app)
|
|
success_req = client.post(
|
|
f"{API_PREFIX}/component_server/",
|
|
json={
|
|
"session_hash": "123",
|
|
"component_id": file_explorer._id,
|
|
"fn_name": "ls",
|
|
"data": None,
|
|
},
|
|
)
|
|
assert success_req.status_code == 200
|
|
assert len(success_req.json()) > 0
|
|
fail_req = client.post(
|
|
f"{API_PREFIX}/component_server/",
|
|
json={
|
|
"session_hash": "123",
|
|
"component_id": file_explorer._id,
|
|
"fn_name": "preprocess",
|
|
"data": None,
|
|
},
|
|
)
|
|
assert fail_req.status_code == 404
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"request_url, route_path, root_path, expected_root_url",
|
|
[
|
|
(
|
|
f"http://localhost:7860/{API_PREFIX}",
|
|
f"{API_PREFIX}/",
|
|
None,
|
|
"http://localhost:7860",
|
|
),
|
|
(
|
|
f"http://localhost:7860/{API_PREFIX}/demo/test",
|
|
f"{API_PREFIX}/demo/test",
|
|
None,
|
|
"http://localhost:7860",
|
|
),
|
|
(
|
|
f"http://localhost:7860/{API_PREFIX}/demo/test?query=1",
|
|
f"{API_PREFIX}/demo/test",
|
|
None,
|
|
"http://localhost:7860",
|
|
),
|
|
(
|
|
f"http://localhost:7860/{API_PREFIX}/demo/test?query=1",
|
|
f"{API_PREFIX}/demo/test/",
|
|
"/gradio",
|
|
"http://localhost:7860/gradio",
|
|
),
|
|
(
|
|
"http://localhost:7860/demo/test?query=1",
|
|
"/demo/test",
|
|
"/gradio/",
|
|
"http://localhost:7860/gradio",
|
|
),
|
|
(
|
|
"https://localhost:7860/demo/test?query=1",
|
|
"/demo/test",
|
|
"/gradio/",
|
|
"https://localhost:7860/gradio",
|
|
),
|
|
(
|
|
"https://www.gradio.app/playground/",
|
|
f"{API_PREFIX}/",
|
|
"/playground",
|
|
"https://www.gradio.app/playground",
|
|
),
|
|
(
|
|
"https://www.gradio.app/playground/",
|
|
f"{API_PREFIX}/",
|
|
"http://www.gradio.app/",
|
|
"http://www.gradio.app",
|
|
),
|
|
],
|
|
)
|
|
def test_get_root_url(
|
|
request_url: str, route_path: str, root_path: str, expected_root_url: str
|
|
):
|
|
scope = {
|
|
"type": "http",
|
|
"headers": [],
|
|
"path": request_url,
|
|
}
|
|
request = Request(scope)
|
|
assert get_root_url(request, route_path, root_path) == expected_root_url
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"headers, root_path, route_path, expected_root_url",
|
|
[
|
|
({}, "/gradio/", "/", "http://gradio.app/gradio"),
|
|
(
|
|
{"x-forwarded-proto": "http"},
|
|
"/gradio/",
|
|
"/",
|
|
"http://gradio.app/gradio",
|
|
),
|
|
(
|
|
{"x-forwarded-proto": "https"},
|
|
"/gradio/",
|
|
"/",
|
|
"https://gradio.app/gradio",
|
|
),
|
|
(
|
|
{"x-forwarded-host": "gradio.dev"},
|
|
"/gradio/",
|
|
"/",
|
|
"http://gradio.dev/gradio",
|
|
),
|
|
(
|
|
{"x-forwarded-host": "gradio.dev"},
|
|
"/gradio/",
|
|
"/config",
|
|
"http://gradio.dev/gradio",
|
|
),
|
|
(
|
|
{"x-forwarded-host": "gradio.dev", "x-forwarded-proto": "https"},
|
|
"/",
|
|
"/",
|
|
"https://gradio.dev",
|
|
),
|
|
(
|
|
{
|
|
"x-forwarded-host": "gradio.dev,internal.gradio.dev",
|
|
"x-forwarded-proto": "https,http",
|
|
},
|
|
"/",
|
|
"/",
|
|
"https://gradio.dev",
|
|
),
|
|
(
|
|
{"x-forwarded-host": "gradio.dev", "x-forwarded-proto": "https"},
|
|
"http://google.com",
|
|
"/",
|
|
"http://google.com",
|
|
),
|
|
],
|
|
)
|
|
def test_get_root_url_headers(
|
|
headers: dict[str, str], root_path: str, route_path: str, expected_root_url: str
|
|
):
|
|
scope = {
|
|
"type": "http",
|
|
"headers": [(k.encode(), v.encode()) for k, v in headers.items()],
|
|
"path": "http://gradio.app",
|
|
}
|
|
request = Request(scope)
|
|
assert get_root_url(request, route_path, root_path) == expected_root_url
|
|
|
|
|
|
class TestSimpleAPIRoutes:
|
|
def get_demo(self):
|
|
with Blocks() as demo:
|
|
input = Textbox()
|
|
output = Textbox()
|
|
output2 = Textbox()
|
|
|
|
def fn_1(x):
|
|
return f"Hello, {x}!"
|
|
|
|
def fn_2(x):
|
|
for i in range(len(x)):
|
|
time.sleep(0.5)
|
|
yield f"Hello, {x[:i+1]}!"
|
|
if len(x) < 3:
|
|
raise ValueError("Small input")
|
|
|
|
def fn_3():
|
|
return "a", "b"
|
|
|
|
btn1, btn2, btn3 = Button(), Button(), Button()
|
|
btn1.click(fn_1, input, output, api_name="fn1")
|
|
btn2.click(fn_2, input, output2, api_name="fn2")
|
|
btn3.click(fn_3, None, [output, output2], api_name="fn3")
|
|
return demo
|
|
|
|
def test_successful_simple_route(self):
|
|
demo = self.get_demo()
|
|
demo.launch(prevent_thread_lock=True)
|
|
|
|
response = requests.post(
|
|
f"{demo.local_api_url}call/fn1", json={"data": ["world"]}
|
|
)
|
|
|
|
assert response.status_code == 200, "Failed to call fn1"
|
|
response = response.json()
|
|
event_id = response["event_id"]
|
|
|
|
output = []
|
|
response = requests.get(f"{demo.local_api_url}call/fn1/{event_id}", stream=True)
|
|
|
|
for line in response.iter_lines():
|
|
if line:
|
|
output.append(line.decode("utf-8"))
|
|
|
|
assert output == ["event: complete", 'data: ["Hello, world!"]']
|
|
|
|
response = requests.post(f"{demo.local_api_url}call/fn3", json={"data": []})
|
|
|
|
assert response.status_code == 200, "Failed to call fn3"
|
|
response = response.json()
|
|
event_id = response["event_id"]
|
|
|
|
output = []
|
|
response = requests.get(f"{demo.local_api_url}call/fn3/{event_id}", stream=True)
|
|
|
|
for line in response.iter_lines():
|
|
if line:
|
|
output.append(line.decode("utf-8"))
|
|
|
|
assert output == ["event: complete", 'data: ["a", "b"]']
|
|
|
|
def test_generative_simple_route(self):
|
|
demo = self.get_demo()
|
|
demo.launch(prevent_thread_lock=True)
|
|
|
|
response = requests.post(
|
|
f"{demo.local_api_url}call/fn2", json={"data": ["world"]}
|
|
)
|
|
|
|
assert response.status_code == 200, "Failed to call fn2"
|
|
response = response.json()
|
|
event_id = response["event_id"]
|
|
|
|
output = []
|
|
response = requests.get(f"{demo.local_api_url}call/fn2/{event_id}", stream=True)
|
|
|
|
for line in response.iter_lines():
|
|
if line:
|
|
output.append(line.decode("utf-8"))
|
|
|
|
assert output == [
|
|
"event: generating",
|
|
'data: ["Hello, w!"]',
|
|
"event: generating",
|
|
'data: ["Hello, wo!"]',
|
|
"event: generating",
|
|
'data: ["Hello, wor!"]',
|
|
"event: generating",
|
|
'data: ["Hello, worl!"]',
|
|
"event: generating",
|
|
'data: ["Hello, world!"]',
|
|
"event: complete",
|
|
'data: ["Hello, world!"]',
|
|
]
|
|
|
|
response = requests.post(f"{demo.local_api_url}call/fn2", json={"data": ["w"]})
|
|
|
|
assert response.status_code == 200, "Failed to call fn2"
|
|
response = response.json()
|
|
event_id = response["event_id"]
|
|
|
|
output = []
|
|
response = requests.get(f"{demo.local_api_url}call/fn2/{event_id}", stream=True)
|
|
|
|
for line in response.iter_lines():
|
|
if line:
|
|
output.append(line.decode("utf-8"))
|
|
|
|
assert output == [
|
|
"event: generating",
|
|
'data: ["Hello, w!"]',
|
|
"event: error",
|
|
"data: null",
|
|
]
|
|
|
|
|
|
def test_compare_passwords_securely():
|
|
password1 = "password"
|
|
password2 = "pässword"
|
|
assert compare_passwords_securely(password1, password1)
|
|
assert not compare_passwords_securely(password1, password2)
|
|
assert compare_passwords_securely(password2, password2)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"string, expected",
|
|
[
|
|
("http://localhost:7860/", True),
|
|
("https://localhost:7860/", True),
|
|
("ftp://localhost:7860/", True),
|
|
("smb://example.com", True),
|
|
("ipfs://QmTzQ1Nj5R9BzF1djVQv8gvzZxVkJb1vhrLcXL1QyJzZE", True),
|
|
("usr/local/bin", False),
|
|
("localhost:7860", False),
|
|
("localhost", False),
|
|
("C:/Users/username", False),
|
|
("//path", True),
|
|
("\\\\path", True),
|
|
("/usr/bin//test", False),
|
|
("/\\10.0.225.200/share", True),
|
|
("\\/10.0.225.200/share", True),
|
|
("/home//user", False),
|
|
("C:\\folder\\file", False),
|
|
],
|
|
)
|
|
def test_starts_with_protocol(string, expected):
|
|
assert starts_with_protocol(string) == expected
|
|
|
|
|
|
def test_max_file_size_used_in_upload_route(connect):
|
|
with gr.Blocks() as demo:
|
|
gr.Markdown("Max file size demo")
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True, max_file_size="1kb")
|
|
test_client = TestClient(app)
|
|
with open("test/test_files/cheetah1.jpg", "rb") as f:
|
|
r = test_client.post(f"{API_PREFIX}/upload", files={"files": f})
|
|
assert r.status_code == 413
|
|
with open("test/test_files/alphabet.txt", "rb") as f:
|
|
r = test_client.post(f"{API_PREFIX}/upload", files={"files": f})
|
|
assert r.status_code == 200
|
|
|
|
|
|
def test_docs_url():
|
|
with gr.Blocks() as demo:
|
|
num = gr.Number(value=0)
|
|
button = gr.Button()
|
|
button.click(lambda n: n + 1, [num], [num])
|
|
|
|
app, _, _ = demo.launch(
|
|
app_kwargs={"docs_url": f"{API_PREFIX}/docs"}, prevent_thread_lock=True
|
|
)
|
|
try:
|
|
test_client = TestClient(app)
|
|
with test_client:
|
|
r = test_client.get(f"{API_PREFIX}/docs")
|
|
assert r.status_code == 200
|
|
finally:
|
|
demo.close()
|
|
|
|
|
|
def test_file_access():
|
|
with gr.Blocks() as demo:
|
|
gr.Markdown("Test")
|
|
|
|
allowed_dir = (Path(tempfile.gettempdir()) / "test_file_access_dir").resolve()
|
|
allowed_dir.mkdir(parents=True, exist_ok=True)
|
|
allowed_file = Path(allowed_dir / "allowed.txt")
|
|
allowed_file.touch()
|
|
|
|
not_allowed_file = Path(tempfile.gettempdir()) / "not_allowed.txt"
|
|
not_allowed_file.touch()
|
|
|
|
app, _, _ = demo.launch(
|
|
prevent_thread_lock=True,
|
|
blocked_paths=["test/test_files"],
|
|
allowed_paths=[str(allowed_dir)],
|
|
)
|
|
test_client = TestClient(app)
|
|
try:
|
|
with test_client:
|
|
r = test_client.get(f"{API_PREFIX}/file={allowed_dir}/allowed.txt")
|
|
assert r.status_code == 200
|
|
r = test_client.get(f"{API_PREFIX}/file={allowed_dir}/../not_allowed.txt")
|
|
assert r.status_code in [403, 404] # 403 in Linux, 404 in Windows
|
|
r = test_client.get(f"{API_PREFIX}/file=//test/test_files/cheetah1.jpg")
|
|
assert r.status_code == 403
|
|
r = test_client.get(f"{API_PREFIX}/file=test/test_files/cheetah1.jpg")
|
|
assert r.status_code == 403
|
|
r = test_client.get(f"{API_PREFIX}/file=//test/test_files/cheetah1.jpg")
|
|
assert r.status_code == 403
|
|
tmp = Path(tempfile.gettempdir()) / "upload_test.txt"
|
|
tmp.write_text("Hello")
|
|
with open(str(tmp), "rb") as f:
|
|
files = {"files": ("..", f)}
|
|
response = test_client.post(f"{API_PREFIX}/upload", files=files)
|
|
assert response.status_code == 400
|
|
finally:
|
|
demo.close()
|
|
not_allowed_file.unlink()
|
|
allowed_file.unlink()
|
|
|
|
|
|
def test_bash_api_serialization():
|
|
demo = gr.Interface(lambda x: x, "json", "json")
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
test_client = TestClient(app)
|
|
|
|
with test_client:
|
|
submit = test_client.post(
|
|
f"{API_PREFIX}/call/predict", json={"data": [{"a": 1}]}
|
|
)
|
|
event_id = submit.json()["event_id"]
|
|
response = test_client.get(f"{API_PREFIX}/call/predict/{event_id}")
|
|
assert response.status_code == 200
|
|
assert "event: complete\ndata:" in response.text
|
|
assert json.dumps({"a": 1}) in response.text
|
|
|
|
|
|
def test_bash_api_multiple_inputs_outputs():
|
|
demo = gr.Interface(
|
|
lambda x, y: (y, x), ["textbox", "number"], ["number", "textbox"]
|
|
)
|
|
|
|
app, _, _ = demo.launch(prevent_thread_lock=True)
|
|
test_client = TestClient(app)
|
|
|
|
with test_client:
|
|
submit = test_client.post(
|
|
f"{API_PREFIX}/call/predict", json={"data": ["abc", 123]}
|
|
)
|
|
event_id = submit.json()["event_id"]
|
|
response = test_client.get(f"{API_PREFIX}/call/predict/{event_id}")
|
|
assert response.status_code == 200
|
|
assert "event: complete\ndata:" in response.text
|
|
assert json.dumps([123, "abc"]) in response.text
|
|
|
|
|
|
def test_attacker_cannot_change_root_in_config(
|
|
attacker_threads=1, victim_threads=10, max_attempts=30
|
|
):
|
|
def attacker(url):
|
|
"""Simulates the attacker sending a request with a malicious header."""
|
|
for _ in range(max_attempts):
|
|
httpx.get(url + "config", headers={"X-Forwarded-Host": "evil"})
|
|
|
|
def victim(url, results):
|
|
"""Simulates the victim making a normal request and checking the response."""
|
|
for _ in range(max_attempts):
|
|
res = httpx.get(url)
|
|
config = json.loads(
|
|
res.text.split("window.gradio_config =", 1)[1].split(";</script>", 1)[0]
|
|
)
|
|
if "evil" in config["root"]:
|
|
results.append(True)
|
|
return
|
|
|
|
results.append(False)
|
|
|
|
with gr.Blocks() as demo:
|
|
i1 = gr.Image("test/test_files/cheetah1.jpg")
|
|
t = gr.Textbox()
|
|
i2 = gr.Image()
|
|
t.change(lambda x: x, i1, i2)
|
|
|
|
_, url, _ = demo.launch(prevent_thread_lock=True)
|
|
|
|
threads = []
|
|
results = []
|
|
|
|
for _ in range(attacker_threads):
|
|
t_attacker = Thread(target=attacker, args=(url,))
|
|
threads.append(t_attacker)
|
|
|
|
for _ in range(victim_threads):
|
|
t_victim = Thread(
|
|
target=victim,
|
|
args=(
|
|
url,
|
|
results,
|
|
),
|
|
)
|
|
threads.append(t_victim)
|
|
|
|
for t in threads:
|
|
t.start()
|
|
|
|
for t in threads:
|
|
t.join()
|
|
|
|
assert not any(results), "attacker was able to modify a victim's config root url"
|