[
  {
    "path": ".gitignore",
    "content": "########################################################################\n# Python - https://github.com/github/gitignore/blob/master/Python.gitignore\n########################################################################\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# Distribution / packaging\nbuild/\ndist/\neggs/\n.eggs/\n*.egg-info/\n*.egg\n\n# Unit test / coverage reports\n.coverage\n.coverage\\.*\n.pytest_cache/\n.mypy_cache/\ntest-reports\n\n# Test fixtures\ncffi_bin\n\n# Pyenv Stuff\n.python-version\n\n########################################################################\n# OSX - https://github.com/github/gitignore/blob/master/Global/macOS.gitignore\n########################################################################\n.DS_Store\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n########################################################################\n# node - https://github.com/github/gitignore/blob/master/Node.gitignore\n########################################################################\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Dependency directories\nnode_modules/\n\n# Coverage directory used by tools like istanbul\ncoverage/\n\n# Lockfiles\nyarn.lock\npackage-lock.json\n\n########################################################################\n# JetBrains\n########################################################################\n.idea\n\n########################################################################\n# VSCode\n########################################################################\n.vscode/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 1.0.1\n\n- Fix: Changes to interval, refresh limit, or key indicate a reset of the\n  interval.\n\n## 1.0.0\n\n- **BREAKING CHANGE** - Parameters outside of `interval` have moved to\n  keyword-only parameters.\n- Added `debounce` parameter that delays refresh when user interaction occurs.\n  Defaults to `True` to mimic original behavior\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2018 The Python Packaging Authority\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "recursive-include streamlit_autorefresh/frontend/build *\n"
  },
  {
    "path": "README.md",
    "content": "# Streamlit Autorefresh\n\nStreamlit component to force a refresh without tying up a script.\n## Overview\n\nStreamlit apps are scripts that a server runs based on interactions. When\na user interacts with the web app, the script reruns.\n\nOne Streamlit use case is a dashboard or realtime stats application. The\npurpose is to regularly display stats on some interval. Currently, the\nbest way to support this is an infinite loop in the script, but that is\nnot a great practice and has led to a less desirable developer experience\nin shutting down scripts and servers.\n\n### How does this component help?\n\nThis component provides a timer on the frontend to regularly ping the Streamlit\nserver to rerun. This effectively allows the script to successfully execute and\nfinish properly and avoid tying up server resources. It effectively puts a\nlittle more work on the user's browser than on the server.\n\n# Installation\n\n```\npip install streamlit-autorefresh\n```\n\n# Parameters\n\n```\ninterval: int\n    Amount of time in milliseconds to \nlimit: int or None\n    Amount of refreshes to allow. If none, it will refresh infinitely.\n    While infinite refreshes sounds nice, it will continue to utilize\n    computing resources.\ndebounce: boolean\n    Whether to delay the autorefresh when user interaction occurs.\n    Defaults to True in order to avoid refreshes interfering with\n    interaction effects on scripts.\nkey: str or None\n    An optional key that uniquely identifies this component. If this is\n    None, and the component's arguments are changed, the component will\n    be re-mounted in the Streamlit frontend and lose its current state.\n```\n\n## Returns\n\n```\nint\n    Number of times the refresh has been triggered or max value of int\n```\n\n# Example Usage\n\n```python\nimport streamlit as st\nfrom streamlit_autorefresh import st_autorefresh\n\n# Run the autorefresh about every 2000 milliseconds (2 seconds) and stop\n# after it's been refreshed 100 times.\ncount = st_autorefresh(interval=2000, limit=100, key=\"fizzbuzzcounter\")\n\n# The function returns a counter for number of refreshes. This allows the\n# ability to make special requests at different intervals based on the count\nif count == 0:\n    st.write(\"Count is zero\")\nelif count % 3 == 0 and count % 5 == 0:\n    st.write(\"FizzBuzz\")\nelif count % 3 == 0:\n    st.write(\"Fizz\")\nelif count % 5 == 0:\n    st.write(\"Buzz\")\nelse:\n    st.write(f\"Count: {count}\")\n\n```\n\n## Caveats\n\nThis is a rather simplistic implementation and feature requests are welcome!\n\n- The Frontend timer is not a perfect system, so the refresh interval is a\nrough estimate. Feel free to adjust the interval to a limit that's practical\n- Just like an infinite loop, a small interval, will constantly ping and make\nserver do more work and should be treated with caution.\n- We recommend a `key` be added. It can be a string literal, but it will help\nin maintaining the refresh rate and count.\n- We recommend _NOT_ calling `st_autorefresh` multiple times in a script. It\nwill effectively create multiple timers and refresh at weird rates. It's best\nto use one function call and utilize the counter to better adjust different\nrefresh rates\n"
  },
  {
    "path": "setup.py",
    "content": "import setuptools\n\nsetuptools.setup(\n    name=\"streamlit-autorefresh\",\n    version=\"1.0.1\",\n    author=\"Ken McGrady\",\n    author_email=\"ken.mcgrady@gmail.com\",\n    description=\"Simple way to autorefresh your Streamlit apps\",\n    long_description=\"Simple way to autorefresh your Streamlit apps\",\n    long_description_content_type=\"text/plain\",\n    url=\"https://github.com/kmcgrady/streamlit-autorefresh\",\n    packages=setuptools.find_packages(),\n    include_package_data=True,\n    classifiers=[],\n    python_requires=\">=3.6\",\n    install_requires=[\n        # By definition, a Custom Component depends on Streamlit.\n        # If your component has other Python dependencies, list\n        # them here.\n        \"streamlit >= 0.75\",\n    ],\n)\n"
  },
  {
    "path": "streamlit_autorefresh/__init__.py",
    "content": "import os\nimport streamlit.components.v1 as components\n\n# Create a _RELEASE constant. We'll set this to False while we're developing\n# the component, and True when we're ready to package and distribute it.\n# (This is, of course, optional - there are innumerable ways to manage your\n# release process.)\n_RELEASE = False\n\n# Declare a Streamlit component. `declare_component` returns a function\n# that is used to create instances of the component. We're naming this\n# function \"_component_func\", with an underscore prefix, because we don't want\n# to expose it directly to users. Instead, we will create a custom wrapper\n# function, below, that will serve as our component's public API.\n\n# It's worth noting that this call to `declare_component` is the\n# *only thing* you need to do to create the binding between Streamlit and\n# your component frontend. Everything else we do in this file is simply a\n# best practice.\n\nif not _RELEASE:\n    _component_func = components.declare_component(\n        # We give the component a simple, descriptive name (\"my_component\"\n        # does not fit this bill, so please choose something better for your\n        # own component :)\n        \"st_autorefresh\",\n        # Pass `url` here to tell Streamlit that the component will be served\n        # by the local dev server that you run via `npm run start`.\n        # (This is useful while your component is in development.)\n        url=\"http://localhost:3001\",\n    )\nelse:\n    # When we're distributing a production version of the component, we'll\n    # replace the `url` param with `path`, and point it to to the component's\n    # build directory:\n    parent_dir = os.path.dirname(os.path.abspath(__file__))\n    build_dir = os.path.join(parent_dir, \"frontend/build\")\n    _component_func = components.declare_component(\"st_autorefresh\", path=build_dir)\n\n\n# Create a wrapper function for the component. This is an optional\n# best practice - we could simply expose the component function returned by\n# `declare_component` and call it done. The wrapper allows us to customize\n# our component's API: we can pre-process its input args, post-process its\n# output value, and add a docstring for users.\ndef st_autorefresh(interval=1000, *, limit=None, debounce=True, key=None):\n    \"\"\"Create an autorefresh instance to trigger a refresh of the application\n\n    Parameters\n    ----------\n    interval: int\n        Amount of time in milliseconds to \n    limit: int or None\n        Amount of refreshes to allow. If none, it will refresh infinitely.\n        While infinite refreshes sounds nice, it will continue to utilize\n        computing resources.\n    debounce: boolean\n        Whether to delay the autorefresh when user interaction occurs.\n        Defaults to True in order to avoid refreshes interfering with\n        interaction effects on scripts.\n    key: str or None\n        An optional key that uniquely identifies this component. If this is\n        None, and the component's arguments are changed, the component will\n        be re-mounted in the Streamlit frontend and lose its current state.\n\n    Returns\n    -------\n    int\n        Number of times the refresh has been triggered or max value of int\n    \"\"\"\n\n    count = _component_func(interval=interval, limit=limit, debounce=debounce, key=key)\n    if count is None:\n        return 0\n\n    return int(count)\n\n\n# Add some test code to play with the component while it's in development.\n# During development, we can run this just as we would any other Streamlit\n# app: `$ streamlit run my_component/__init__.py`\nif not _RELEASE:\n    import streamlit as st\n\n    secs = st.selectbox(\"Select a time reset\", [1, 2, 3, 5], 1, lambda x: str(x) + \" seconds\")\n    should_debounce = st.checkbox(\"Debounce?\", True)\n\n    st.button(\"Click me for a distraction\")\n\n    # We use the special \"key\" argument to assign a fixed identity to this\n    # component instance. By default, when a component's arguments change,\n    # it is considered a new instance and will be re-mounted on the frontend\n    # and lose its current state. In this case, we want to vary the component's\n    # \"name\" argument without having it get recreated.\n    # Run the autorefresh about every 2000 milliseconds (2 seconds) and stop\n    # after it's been refreshed 100 times.\n    count = st_autorefresh(interval=secs * 1000, limit=100, debounce=should_debounce, key=\"fizzbuzzcounter\")\n\n    # The function returns a counter for number of refreshes. This allows the\n    # ability to make special requests at different intervals based on the count\n    if count == 0:\n        st.write(\"Count is zero\")\n    elif count % 3 == 0 and count % 5 == 0:\n        st.write(\"FizzBuzz\")\n    elif count % 3 == 0:\n        st.write(\"Fizz\")\n    elif count % 5 == 0:\n        st.write(\"Buzz\")\n    else:\n        st.write(f\"Count: {count}\")\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/.prettierrc",
    "content": "{\n  \"endOfLine\": \"lf\",\n  \"semi\": false,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/package.json",
    "content": "{\n  \"name\": \"streamlit_component_template\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@testing-library/jest-dom\": \"^4.2.4\",\n    \"@testing-library/react\": \"^9.3.2\",\n    \"@testing-library/user-event\": \"^7.1.2\",\n    \"@types/jest\": \"^24.0.0\",\n    \"@types/node\": \"^12.0.0\",\n    \"react-scripts\": \"3.4.1\",\n    \"streamlit-component-lib\": \"^1.1.3\",\n    \"typescript\": \"^4.9.5\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"eslintConfig\": {\n    \"extends\": \"react-app\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"homepage\": \".\"\n}\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>Streamlit Component</title>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta name=\"description\" content=\"Streamlit Component\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n  </body>\n</html>\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/src/index.ts",
    "content": "import { Streamlit, RenderData } from \"streamlit-component-lib\"\n\nfunction counter(): () => number {\n  let count = 0\n\n  return function (): number {\n    count += 1\n    return count\n  }\n}\n\nclass ArgsCheck {\n  limit: number | null = null\n  interval: number | null = null\n  key: string | null = null\n\n  setLimit(limit: number | null) {\n    const hasChanged = limit !== this.limit\n    this.limit = limit\n\n    return hasChanged\n  }\n\n  setInterval(intervalTime: number) {\n    const hasChanged = intervalTime !== this.interval\n    this.interval = intervalTime\n\n    return hasChanged\n  }\n\n  setKey(key: string | null) {\n    const hasChanged = key !== this.key\n    this.key = key\n\n    return hasChanged\n  }\n}\n\nconst refreshCounter = counter()\nconst argsChecker = new ArgsCheck()\nlet interval: number\n\n/**\n * The component's render function. This will be called immediately after\n * the component is initially loaded, and then again every time the\n * component gets new data from Python.\n */\nfunction onRender(event: Event): void {\n  document.body.innerHTML = \"\"\n  // Get the RenderData from the event\n  const data = (event as CustomEvent<RenderData>).detail\n  const refreshLimit = data.args.limit ? parseInt(data.args.limit, 10) : null\n  const refreshInterval = parseInt(data.args.interval, 10)\n  const shouldReset = argsChecker.setInterval(refreshInterval) || \n    argsChecker.setKey(data.args.key) || argsChecker.setLimit(refreshLimit)\n  if (interval) {\n    if (data.args.debounce || shouldReset) {\n      clearInterval(interval)\n    } else {\n      // We already have an interval so clear the screen.\n      return\n    }\n  }\n\n  interval = window.setInterval(() => {\n    const newCount = Math.min(refreshCounter(), Number.MAX_SAFE_INTEGER)\n    // There is no refresh counter or we are within the limit\n    if (!refreshLimit || newCount < refreshLimit) {\n      Streamlit.setComponentValue(newCount)\n    } else {\n      // No need to keep pinging, so clear the interval\n      clearInterval(interval)\n    }\n  }, refreshInterval)\n}\n\n// Attach our `onRender` handler to Streamlit's render event.\nStreamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender)\n\n// Tell Streamlit we're ready to start receiving data. We won't get our\n// first RENDER_EVENT until we call this function.\nStreamlit.setComponentReady()\n\n// Finally, tell Streamlit to update our initial height. We omit the\n// `height` parameter here to have it default to our scrollHeight.\nStreamlit.setFrameHeight(0)\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/src/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "streamlit_autorefresh/frontend/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react\"\n  },\n  \"include\": [\"src\"]\n}\n"
  }
]