├── .gitignore ├── .readthedocs.yml ├── LICENCE ├── MANIFEST.in ├── README.md ├── README.rst ├── build_tools ├── README.template.md ├── make_readme.py └── predeploy.py ├── deploy.sh ├── docs ├── .streamlit │ └── secrets.toml ├── Makefile ├── _build │ ├── doctrees │ │ ├── api │ │ │ ├── modules.doctree │ │ │ ├── streamlitextras.authenticator.doctree │ │ │ ├── streamlitextras.cookiemanager.doctree │ │ │ ├── streamlitextras.doctree │ │ │ ├── streamlitextras.logger.doctree │ │ │ ├── streamlitextras.router.doctree │ │ │ └── streamlitextras.threader.doctree │ │ ├── environment.pickle │ │ ├── index.doctree │ │ ├── overview.doctree │ │ ├── project.doctree │ │ └── project │ │ │ └── license.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ ├── index.html │ │ └── streamlitextras │ │ │ ├── authenticator.html │ │ │ ├── authenticator │ │ │ ├── exceptions.html │ │ │ ├── user.html │ │ │ └── utils.html │ │ │ ├── cookiemanager.html │ │ │ ├── helpers.html │ │ │ ├── logger.html │ │ │ ├── logger │ │ │ └── firebasesink.html │ │ │ ├── router.html │ │ │ ├── threader.html │ │ │ ├── utils.html │ │ │ └── webutils.html │ │ ├── _sources │ │ ├── api │ │ │ ├── modules.rst.txt │ │ │ ├── streamlitextras.authenticator.rst.txt │ │ │ ├── streamlitextras.cookiemanager.rst.txt │ │ │ ├── streamlitextras.logger.rst.txt │ │ │ ├── streamlitextras.router.rst.txt │ │ │ ├── streamlitextras.rst.txt │ │ │ └── streamlitextras.threader.rst.txt │ │ ├── index.rst.txt │ │ ├── overview.rst.txt │ │ ├── project.rst.txt │ │ └── project │ │ │ └── license.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery-3.6.0.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── copybutton.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sphinx_highlight.js │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ │ ├── api │ │ ├── modules.html │ │ ├── streamlitextras.authenticator.html │ │ ├── streamlitextras.cookiemanager.html │ │ ├── streamlitextras.html │ │ ├── streamlitextras.logger.html │ │ ├── streamlitextras.router.html │ │ └── streamlitextras.threader.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── overview.html │ │ ├── project.html │ │ ├── project │ │ └── license.html │ │ ├── py-modindex.html │ │ ├── search.html │ │ └── searchindex.js ├── _extensions │ └── autodoc_stub_file.py ├── _static │ └── js │ │ └── copybutton.js ├── _templates │ ├── breadcrumbs.html │ └── layout.html ├── api │ ├── modules.rst │ ├── streamlitextras.authenticator.rst │ ├── streamlitextras.cookiemanager.rst │ ├── streamlitextras.logger.rst │ ├── streamlitextras.router.rst │ ├── streamlitextras.rst │ └── streamlitextras.threader.rst ├── conf.py ├── index.rst ├── overview.rst ├── project.rst ├── project │ └── license.rst └── reruntrigger.py ├── requirements.txt ├── setup.py └── streamlitextras ├── __init__.py ├── authenticator ├── README.md ├── __init__.py ├── exceptions.py ├── user.py └── utils.py ├── cookiemanager ├── README.md ├── __init__.py └── frontend │ ├── build │ ├── asset-manifest.json │ ├── index.html │ ├── precache-manifest.d091de9fa4668477ba52acdbecc2c864.js │ ├── service-worker.js │ └── static │ │ └── js │ │ ├── 2.2bc12ddb.chunk.js │ │ ├── 2.2bc12ddb.chunk.js.LICENSE.txt │ │ ├── 2.2bc12ddb.chunk.js.map │ │ ├── main.a8b332f3.chunk.js │ │ ├── main.a8b332f3.chunk.js.map │ │ ├── runtime-main.41b280b5.js │ │ └── runtime-main.41b280b5.js.map │ ├── package.json │ ├── public │ └── index.html │ └── src │ └── index.jsx ├── helpers.py ├── logger ├── README.md ├── __init__.py └── firebasesink.py ├── router ├── README.md └── __init__.py ├── storageservice.py ├── threader ├── README.md └── __init__.py ├── utils.py └── webutils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | dist/ 3 | *.egg-info/ 4 | 5 | #Development 6 | .idea/ 7 | .vscode/ 8 | log.txt 9 | log.log 10 | key.pem 11 | cert.pem 12 | /testing/ 13 | yarn.lock 14 | *.pyc 15 | __pycache__/ 16 | node_modules/ 17 | .parcel-cache 18 | pnpm-lock.yaml 19 | /streamlitextras/*/frontend/build/ 20 | 21 | # App state files 22 | logs/ 23 | uploads/ 24 | sessions/ 25 | 26 | # App secrets 27 | /.streamlit/secrets.toml 28 | .devenv 29 | .env -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-20.04 5 | tools: 6 | python: '3.10' 7 | 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | - method: pip 12 | path: . 13 | extra_requirements: 14 | - dev 15 | 16 | sphinx: 17 | builder: html 18 | configuration: docs/conf.py 19 | 20 | formats: 21 | - htmlzip 22 | - pdf 23 | - epub -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | This file is part of the Python Streamlit Extras Package. 2 | Copyright (C) 2022, A.D. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | recursive-include streamlitextras/*/frontend/build/ * -------------------------------------------------------------------------------- /build_tools/README.template.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras 2 | 3 | These are some components and modules designed to make working with streamlit easier. 4 | 5 | I had a project that required some of these parts, I tried some other community projects that were similar, 6 | but none of them had the features I required, so I ended up rewriting my own implementations of them. 7 | 8 | ## Installation and Requirements 9 | 10 | Install from PyPI with pip: 11 | `python3 -m pip install streamlit-base-extras` 12 | 13 | Requires Streamlit 1.23.1+ and Python 3.10+, 14 | 15 | ## The modules 16 | 17 | #### Router 18 | Page router with various features. 19 | 20 | @@ROUTER 21 | 22 | #### Cookie Manager 23 | Component function to manage in-browser cookies from streamlit. 24 | 25 | @@COOKIEMANAGER 26 | 27 | #### Authenticator 28 | Authentication module that creates streamlit register/login forms, and uses firebase auth to register and manage users. 29 | Can also be inherited to use a custom authentication provider. 30 | 31 | @@AUTHENTICATOR 32 | 33 | #### Threader 34 | Makes spawning and working with `threading.Threads` with streamlit easy. 35 | 36 | @@THREADER 37 | 38 | #### Logger 39 | Implementation of Loguru set up to work well with this package. 40 | 41 | @@LOGGER 42 | 43 | #### Other helpers 44 | See the [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html) for a full list of functions and their usage in these files. 45 | 46 | ###### streamlitextras.webutils 47 | Some utility functions to run javascript, wrappers around various javascript routines, 48 | and some other browser related formatting utilities. 49 | 50 | ```Python 51 | import streamlit as st 52 | from streamlitextras.webutils import stxs_javascript, get_user_timezone, \ 53 | bytes_to_data_uri, trigger_download 54 | 55 | def main(): 56 | # Returns tz database name can be used with pytz and datetime 57 | timezone = get_user_timezone() 58 | continent, city = timezone.split("/") 59 | stxs_javascript(f"""alert("Hello person from {city}! Welcome to my streamlit app.");""" 60 | 61 | uploaded_file = st.file_uploader("Upload a file") 62 | 63 | if uploaded_file: 64 | data_uri = bytes_to_data_uri(uploaded_file) 65 | # Browser will prompt to save the file 66 | trigger_download(data_uri, "The file you just uploaded.renamed") 67 | 68 | if __name__ == "__main__": 69 | main() 70 | ``` 71 | 72 | See the [source code](streamlitextras/webutils.py). 73 | 74 | ###### streamlitextras.helpers 75 | Class implementation that streamlines creating basic HTML elements with st.markdown, 76 | and some other useful functions. 77 | 78 | See the [source code](streamlitextras/helpers.py). 79 | 80 | ###### streamlitextras.storageservice 81 | Helper to interact with Google Cloud Storage with a service worker account. 82 | 83 | It has some basic wrapper functions to use the service account to manage buckets and blobs, 84 | as well as computing hashes from Python bytes objects that match gcloud blobs. 85 | 86 | See the [source code](streamlitextras/storageservice.py) and the [google python api reference](https://googleapis.dev/python/storage/latest/) for more. 87 | 88 | ```Python 89 | import streamlitextras.storageservice as storageservice 90 | 91 | with open("my.file", "rb") as f: 92 | computed_md5_hash = storageservice.compute_bytes_md5hash(f.read()) 93 | 94 | buckets = storageservice.get_buckets() # Returns an iterable 95 | list_buckets = [] 96 | for bucket in buckets: 97 | list_buckets.append(buckets) 98 | buckets = list_buckets 99 | 100 | blobs = get_blobs(buckets[0].name) # Returns an iterable 101 | list_blobs = p[] 102 | for blob in blobs: 103 | list_blobs.append(blob) 104 | blobs = list_blobs 105 | 106 | my_file_blob = blobs[0] # my.file 107 | 108 | assert my_file_blob.md5_hash == computed_md5_hash # True 109 | ``` 110 | 111 | You will need to set up a service worker on your google cloud project or firebase project, 112 | and add the details from its .json key to `.streamlit/secrets.toml` 113 | 114 | ```TOML 115 | [gcp_service_account] 116 | type = "" 117 | project_id = "" 118 | private_key_id = "" 119 | private_key = "" 120 | client_email = "" 121 | client_id = "" 122 | auth_uri = "" 123 | token_uri = "" 124 | auth_provider_x509_cert_url = "" 125 | client_x509_cert_url = "" 126 | ``` 127 | 128 | ###### streamlitextras.utils 129 | Some utility functions for Python development. 130 | 131 | See the [source code](streamlitextras/utils.py). 132 | -------------------------------------------------------------------------------- /build_tools/make_readme.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def process_readme(readme): 5 | """ """ 6 | sections = readme.split("\n#") 7 | 8 | sections_processed = [] 9 | for section in sections: 10 | sub_sections = section.split("\n") 11 | section_title = sub_sections[0].strip() 12 | section_body = sub_sections[1:] 13 | section_body = "\n".join(section_body).strip() 14 | code_snippet = section_body.split("``") if "```" in section_body else None 15 | out_snippet = None 16 | if code_snippet: 17 | for i, part in enumerate(code_snippet): 18 | if part.startswith("`Python"): 19 | out_snippet = f"""``{"".join(code_snippet[i:i+1])}```""" 20 | break 21 | sections_processed.append( 22 | (section_title, section_body, out_snippet, sub_sections, section) 23 | ) 24 | 25 | return sections_processed 26 | 27 | 28 | def make_readme(): 29 | with open("build_tools/README.template.md", "r") as f: 30 | main_readme = f.read() 31 | 32 | main_folder = "streamlitextras" 33 | module_folders = [ 34 | f 35 | for f in os.listdir(main_folder) 36 | if os.path.isdir(os.path.join(main_folder, f)) 37 | ] 38 | for module_folder in module_folders: 39 | module_path = os.path.join(str(main_folder), module_folder) 40 | module_readme_file = os.path.join(module_path, "README.md") 41 | if not os.path.exists(module_readme_file): 42 | continue 43 | with open(module_readme_file, "r") as f: 44 | readme = f.read() 45 | sections = process_readme(readme) 46 | inserted_title = sections[0][0].replace("# Streamlit Extras ", "") 47 | inserted_description = sections[0][0] 48 | inserted_usage = sections[1][1] 49 | inserted_usage = sections[1][2] 50 | 51 | title_match = f"@@{inserted_title.upper()}".replace(" ", "").strip() 52 | footer = f"\n\nSee the [package readme]({module_path}) or [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html) for more details.\n" 53 | main_readme = main_readme.replace(title_match, inserted_usage + footer) 54 | 55 | with open("README.md", "w") as f: 56 | f.write(main_readme) 57 | 58 | return main_readme 59 | 60 | 61 | if __name__ == "__main__": 62 | make_readme() 63 | -------------------------------------------------------------------------------- /build_tools/predeploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import re 3 | import sys 4 | import requests 5 | 6 | package_name = "streamlit-base-extras" 7 | version_init_file = "streamlitextras/__init__.py" 8 | 9 | 10 | def get_local_package_version(): 11 | with open(version_init_file, "r") as file: 12 | regex_version = r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]' 13 | version = re.search(regex_version, file.read(), re.MULTILINE).group(1) 14 | return version 15 | 16 | 17 | def get_latest_deployed_version(package_name=package_name): 18 | url = f"https://pypi.org/simple/{package_name}/" 19 | response = requests.get(url) 20 | latest_version = ( 21 | response.content.decode("utf-8") 22 | .split("
")[-3] 23 | .split("-")[-1] 24 | .replace(".tar.gz", "") 25 | ) 26 | return latest_version 27 | 28 | 29 | deployed_package_version = get_latest_deployed_version() 30 | 31 | 32 | def bump_version(version): 33 | parts = version.split(".") 34 | bump = int(parts[-1]) + 1 35 | new_version = ".".join(parts[:-1]) + f".{bump}" 36 | print( 37 | f"Package version changed from {version} to {new_version} in {version_init_file}" 38 | ) 39 | with open(version_init_file, "r+") as file: 40 | file_content = file.read() 41 | new_file_content = file_content.replace( 42 | f'__version__ = "{version}"', f'__version__ = "{new_version}"' 43 | ) 44 | file.seek(0) 45 | file.write(new_file_content) 46 | file.truncate() 47 | 48 | 49 | version = get_local_package_version() 50 | 51 | if deployed_package_version == version: 52 | print(f"Version number already deployed ({version})") 53 | response = input("Auto bump version in __init__.py? (y/other) ") 54 | if response == "y": 55 | bump_version(version) 56 | else: 57 | print("Cancelling deployment. Please reconcile version.") 58 | sys.exit(1) 59 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./dist 2 | rm -rf ./stextras.egg-info 3 | python3 ./build_tools/predeploy.py && 4 | python3 ./build_tools/make_readme.py && 5 | pandoc README.md --from markdown --to rst -s -o README.rst && 6 | cd docs && 7 | sed -i '1cAPI Reference' ./api/streamlitextras.rst && 8 | make clean && make html && 9 | cd .. && 10 | python3 -m build && 11 | python3 -m twine upload dist/* 12 | -------------------------------------------------------------------------------- /docs/.streamlit/secrets.toml: -------------------------------------------------------------------------------- 1 | [firebase] 2 | apiKey = "" 3 | authDomain = "" 4 | projectId = "" 5 | storageBucket = "" 6 | messagingSenderId = "" 7 | appId = "" 8 | measurementId = "" 9 | databaseURL = "" 10 | 11 | [authenticator] 12 | cookie_key = "" 13 | admin_ids = "" 14 | developer_ids = "" 15 | 16 | [gcp_service_account] 17 | type = "" 18 | project_id = "" 19 | private_key_id = "" 20 | private_key = "" 21 | client_email = "" 22 | client_id = "" 23 | auth_uri = "" 24 | token_uri = "" 25 | auth_provider_x509_cert_url = "" 26 | client_x509_cert_url = "" -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = streamlitextras 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_build/doctrees/api/modules.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/modules.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.authenticator.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.authenticator.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.cookiemanager.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.cookiemanager.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.logger.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.logger.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.router.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.router.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/streamlitextras.threader.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/api/streamlitextras.threader.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/overview.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/overview.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/project.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/project.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/project/license.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/doctrees/project/license.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 1b2fe49e4f88691df024e5f029ce4b24 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/_build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview: module code — StreamlitExtras documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 50 | 51 |
55 | 56 |
57 |
58 |
59 |
    60 |
  • »
  • 61 |
  • Overview: module code
  • 62 | 63 |
64 |
65 |
66 | 87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | CLICK 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 | 115 | 195 | 196 | 210 |
211 |
212 |
213 |
214 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/modules.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | streamlitextras 8 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.authenticator.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras.authenticator package 2 | ===================================== 3 | 4 | .. automodule:: streamlitextras.authenticator 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | streamlitextras.authenticator.exceptions module 13 | ----------------------------------------------- 14 | 15 | .. automodule:: streamlitextras.authenticator.exceptions 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | streamlitextras.authenticator.user module 21 | ----------------------------------------- 22 | 23 | .. automodule:: streamlitextras.authenticator.user 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | streamlitextras.authenticator.utils module 29 | ------------------------------------------ 30 | 31 | .. automodule:: streamlitextras.authenticator.utils 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.cookiemanager.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras.cookiemanager package 2 | ===================================== 3 | 4 | .. automodule:: streamlitextras.cookiemanager 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.logger.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras.logger package 2 | ============================== 3 | 4 | .. automodule:: streamlitextras.logger 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | streamlitextras.logger.firebasesink module 13 | ------------------------------------------ 14 | 15 | .. automodule:: streamlitextras.logger.firebasesink 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.router.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras.router package 2 | ============================== 3 | 4 | .. automodule:: streamlitextras.router 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras package 2 | ======================= 3 | 4 | .. automodule:: streamlitextras 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | :maxdepth: 4 14 | 15 | streamlitextras.authenticator 16 | streamlitextras.cookiemanager 17 | streamlitextras.logger 18 | streamlitextras.router 19 | streamlitextras.threader 20 | 21 | Submodules 22 | ---------- 23 | 24 | streamlitextras.helpers module 25 | ------------------------------ 26 | 27 | .. automodule:: streamlitextras.helpers 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | streamlitextras.storageservice module 33 | ------------------------------------- 34 | 35 | .. automodule:: streamlitextras.storageservice 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | streamlitextras.utils module 41 | ---------------------------- 42 | 43 | .. automodule:: streamlitextras.utils 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | streamlitextras.webutils module 49 | ------------------------------- 50 | 51 | .. automodule:: streamlitextras.webutils 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/streamlitextras.threader.rst.txt: -------------------------------------------------------------------------------- 1 | streamlitextras.threader package 2 | ================================ 3 | 4 | .. automodule:: streamlitextras.threader 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Table of contents 4 | ================= 5 | 6 | .. toctree:: 7 | :includehidden: 8 | :maxdepth: 2 9 | 10 | overview.rst 11 | api/streamlitextras.rst 12 | project.rst 13 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/overview.rst.txt: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | .. include:: ../README.rst 5 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/project.rst.txt: -------------------------------------------------------------------------------- 1 | Project Information 2 | =================== 3 | 4 | .. toctree:: 5 | 6 | project/license.rst 7 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/project/license.rst.txt: -------------------------------------------------------------------------------- 1 | License 2 | ####### 3 | 4 | .. include:: ../../LICENSE 5 | -------------------------------------------------------------------------------- /docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _sphinx_javascript_frameworks_compat.js 3 | * ~~~~~~~~~~ 4 | * 5 | * Compatability shim for jQuery and underscores.js. 6 | * 7 | * WILL BE REMOVED IN Sphinx 6.0 8 | * xref RemovedInSphinx60Warning 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | 18 | /** 19 | * small helper function to urldecode strings 20 | * 21 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 22 | */ 23 | jQuery.urldecode = function(x) { 24 | if (!x) { 25 | return x 26 | } 27 | return decodeURIComponent(x.replace(/\+/g, ' ')); 28 | }; 29 | 30 | /** 31 | * small helper function to urlencode strings 32 | */ 33 | jQuery.urlencode = encodeURIComponent; 34 | 35 | /** 36 | * This function returns the parsed url parameters of the 37 | * current request. Multiple values per key are supported, 38 | * it will always return arrays of strings for the value parts. 39 | */ 40 | jQuery.getQueryParameters = function(s) { 41 | if (typeof s === 'undefined') 42 | s = document.location.search; 43 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 44 | var result = {}; 45 | for (var i = 0; i < parts.length; i++) { 46 | var tmp = parts[i].split('=', 2); 47 | var key = jQuery.urldecode(tmp[0]); 48 | var value = jQuery.urldecode(tmp[1]); 49 | if (key in result) 50 | result[key].push(value); 51 | else 52 | result[key] = [value]; 53 | } 54 | return result; 55 | }; 56 | 57 | /** 58 | * highlight a given string on a jquery object by wrapping it in 59 | * span elements with the given class name. 60 | */ 61 | jQuery.fn.highlightText = function(text, className) { 62 | function highlight(node, addItems) { 63 | if (node.nodeType === 3) { 64 | var val = node.nodeValue; 65 | var pos = val.toLowerCase().indexOf(text); 66 | if (pos >= 0 && 67 | !jQuery(node.parentNode).hasClass(className) && 68 | !jQuery(node.parentNode).hasClass("nohighlight")) { 69 | var span; 70 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 71 | if (isInSVG) { 72 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 73 | } else { 74 | span = document.createElement("span"); 75 | span.className = className; 76 | } 77 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 78 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 79 | document.createTextNode(val.substr(pos + text.length)), 80 | node.nextSibling)); 81 | node.nodeValue = val.substr(0, pos); 82 | if (isInSVG) { 83 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 84 | var bbox = node.parentElement.getBBox(); 85 | rect.x.baseVal.value = bbox.x; 86 | rect.y.baseVal.value = bbox.y; 87 | rect.width.baseVal.value = bbox.width; 88 | rect.height.baseVal.value = bbox.height; 89 | rect.setAttribute('class', className); 90 | addItems.push({ 91 | "parent": node.parentNode, 92 | "target": rect}); 93 | } 94 | } 95 | } 96 | else if (!jQuery(node).is("button, select, textarea")) { 97 | jQuery.each(node.childNodes, function() { 98 | highlight(this, addItems); 99 | }); 100 | } 101 | } 102 | var addItems = []; 103 | var result = this.each(function() { 104 | highlight(this, addItems); 105 | }); 106 | for (var i = 0; i < addItems.length; ++i) { 107 | jQuery(addItems[i].parent).before(addItems[i].target); 108 | } 109 | return result; 110 | }; 111 | 112 | /* 113 | * backward compatibility for jQuery.browser 114 | * This will be supported until firefox bug is fixed. 115 | */ 116 | if (!jQuery.browser) { 117 | jQuery.uaMatch = function(ua) { 118 | ua = ua.toLowerCase(); 119 | 120 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 121 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 122 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 123 | /(msie) ([\w.]+)/.exec(ua) || 124 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 125 | []; 126 | 127 | return { 128 | browser: match[ 1 ] || "", 129 | version: match[ 2 ] || "0" 130 | }; 131 | }; 132 | jQuery.browser = {}; 133 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 134 | } 135 | -------------------------------------------------------------------------------- /docs/_build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/_build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '', 4 | LANGUAGE: 'English', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/_build/html/_static/js/copybutton.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /* Add a [>>>] button on the top-right corner of code samples to hide 3 | * the >>> and ... prompts and the output and thus make the code 4 | * copyable. */ 5 | var hig = $('.highlight-default') 6 | var div = hig.find('.highlight') 7 | var pre = div.find('pre'); 8 | 9 | // get the styles from the current theme 10 | pre.parent().parent().css('position', 'relative'); 11 | var hide_text = 'Hide the prompts and output'; 12 | var show_text = 'Show the prompts and output'; 13 | var border_width = hig.css('border-top-width'); 14 | var border_style = hig.css('border-top-style'); 15 | var border_color = hig.css('border-top-color'); 16 | var button_styles = { 17 | 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', 18 | 'border-color': border_color, 'border-style': border_style, 19 | 'border-width': border_width, 'color': border_color, 'text-size': '75%', 20 | 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', 21 | 'border-radius': '0', 'border-width': '0px 0px 1px 1px', 22 | 'line-height': '14px', 'font-size': '12px' 23 | } 24 | 25 | // create and add the button to all the code blocks that contain >>> 26 | div.each(function(index) { 27 | var jthis = $(this); 28 | if (jthis.find('.gp').length > 0) { 29 | var button = $('>>>'); 30 | button.css(button_styles) 31 | button.attr('title', hide_text); 32 | button.data('hidden', 'false'); 33 | jthis.prepend(button); 34 | } 35 | // tracebacks (.gt) contain bare text elements that need to be 36 | // wrapped in a span to work with .nextUntil() (see later) 37 | jthis.find('pre:has(.gt)').contents().filter(function() { 38 | return ((this.nodeType == 3) && (this.data.trim().length > 0)); 39 | }).wrap(''); 40 | }); 41 | 42 | // define the behavior of the button when it's clicked 43 | $('.copybutton').click(function(e){ 44 | e.preventDefault(); 45 | var button = $(this); 46 | if (button.data('hidden') === 'false') { 47 | // hide the code output 48 | button.parent().find('.go, .gp, .gt').hide(); 49 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); 50 | button.css('text-decoration', 'line-through'); 51 | button.attr('title', show_text); 52 | button.data('hidden', 'true'); 53 | } else { 54 | // show the code output 55 | button.parent().find('.go, .gp, .gt').show(); 56 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); 57 | button.css('text-decoration', 'none'); 58 | button.attr('title', hide_text); 59 | button.data('hidden', 'false'); 60 | } 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/_build/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/_build/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/_build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #eeffcc; } 8 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 22 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 23 | .highlight .go { color: #333333 } /* Generic.Output */ 24 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 25 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 27 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 28 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 29 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 30 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 31 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 32 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 33 | .highlight .kt { color: #902000 } /* Keyword.Type */ 34 | .highlight .m { color: #208050 } /* Literal.Number */ 35 | .highlight .s { color: #4070a0 } /* Literal.String */ 36 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 37 | .highlight .nb { color: #007020 } /* Name.Builtin */ 38 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 39 | .highlight .no { color: #60add5 } /* Name.Constant */ 40 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 41 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 42 | .highlight .ne { color: #007020 } /* Name.Exception */ 43 | .highlight .nf { color: #06287e } /* Name.Function */ 44 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 45 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 46 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 47 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 48 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 49 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 50 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 51 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 55 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 56 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 57 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 58 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 59 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 60 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 61 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 62 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 63 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 64 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 65 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 66 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 67 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 68 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 69 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 70 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 71 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 72 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 73 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 74 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blipk/StreamlitExtras/9f92313a110587484b6ab8ce63cf84af86dde09c/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/project.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Project Information — StreamlitExtras documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 55 | 56 |
60 | 61 |
62 |
63 |
64 |
    65 |
  • »
  • 66 |
  • Project Information
  • 67 | 68 |
69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 |

Project Information

77 |
78 | 81 |
82 |
83 | 84 | 85 |
86 |
87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | CLICK 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 | 115 | 195 | 196 | 213 |
214 |
215 |
216 |
217 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /docs/_build/html/project/license.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | License — StreamlitExtras documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 54 | 55 |
59 | 60 |
61 |
62 |
63 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 |

License

77 |
78 | 79 | 80 |
81 |
82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 92 | 93 | CLICK 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 | 110 | 190 | 191 | 207 |
208 |
209 |
210 |
211 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — StreamlitExtras documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 53 | 54 |
58 | 59 |
60 |
61 |
62 |
    63 |
  • »
  • 64 |
  • Search
  • 65 | 66 |
67 |
68 |
69 |
70 | 71 |
72 | 73 | 80 | 81 | 82 |
83 | 84 |
85 | 86 |
87 |
88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 98 | 99 | CLICK 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
115 | 116 | 196 | 197 |
198 | 199 |
200 | 201 |
202 |

203 |
204 | 205 | Built with Sphinx using a 206 | theme 207 | provided by Read the Docs. 208 | 209 | 210 |
211 |
212 |
213 |
214 |
215 | 220 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /docs/_extensions/autodoc_stub_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Small Sphinx extension intended to generate documentation for stub files. 3 | 4 | It retrieves only the docstrings of "/__init__.pyi", hence avoiding possible errors (caused by 5 | missing imports or forward references). The stub file is loaded as a dummy module which contains 6 | only the top-level docstring. All the formatting can therefore be handled by the "autodoc" 7 | extension, which permits cross-reference. 8 | 9 | The docstring of the stub file should list the available type hints and add short explanation of 10 | their usage. 11 | 12 | Warning: for some reason, the docs NEEDS to be re-generated for changes in the stub file to be taken 13 | into account: ``make clean && make html``. 14 | """ 15 | import os 16 | import sys 17 | import types 18 | 19 | 20 | def get_module_docstring(filepath): 21 | with open(filepath) as file: 22 | source = file.read() 23 | 24 | co = compile(source, filepath, "exec") 25 | 26 | if co.co_consts and isinstance(co.co_consts[0], str): 27 | docstring = co.co_consts[0] 28 | else: 29 | docstring = None 30 | 31 | return docstring 32 | 33 | 34 | def setup(app): 35 | module_name = "autodoc_stub_file.streamlitextras" 36 | dirname = os.path.dirname(os.path.abspath(__file__)) 37 | stub_path = os.path.join(dirname, "..", "..", "streamlitextras", "__init__.py") 38 | docstring = get_module_docstring(stub_path) 39 | module = types.ModuleType(module_name, docstring) 40 | sys.modules[module_name] = module 41 | -------------------------------------------------------------------------------- /docs/_static/js/copybutton.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /* Add a [>>>] button on the top-right corner of code samples to hide 3 | * the >>> and ... prompts and the output and thus make the code 4 | * copyable. */ 5 | var hig = $('.highlight-default') 6 | var div = hig.find('.highlight') 7 | var pre = div.find('pre'); 8 | 9 | // get the styles from the current theme 10 | pre.parent().parent().css('position', 'relative'); 11 | var hide_text = 'Hide the prompts and output'; 12 | var show_text = 'Show the prompts and output'; 13 | var border_width = hig.css('border-top-width'); 14 | var border_style = hig.css('border-top-style'); 15 | var border_color = hig.css('border-top-color'); 16 | var button_styles = { 17 | 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', 18 | 'border-color': border_color, 'border-style': border_style, 19 | 'border-width': border_width, 'color': border_color, 'text-size': '75%', 20 | 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', 21 | 'border-radius': '0', 'border-width': '0px 0px 1px 1px', 22 | 'line-height': '14px', 'font-size': '12px' 23 | } 24 | 25 | // create and add the button to all the code blocks that contain >>> 26 | div.each(function(index) { 27 | var jthis = $(this); 28 | if (jthis.find('.gp').length > 0) { 29 | var button = $('>>>'); 30 | button.css(button_styles) 31 | button.attr('title', hide_text); 32 | button.data('hidden', 'false'); 33 | jthis.prepend(button); 34 | } 35 | // tracebacks (.gt) contain bare text elements that need to be 36 | // wrapped in a span to work with .nextUntil() (see later) 37 | jthis.find('pre:has(.gt)').contents().filter(function() { 38 | return ((this.nodeType == 3) && (this.data.trim().length > 0)); 39 | }).wrap(''); 40 | }); 41 | 42 | // define the behavior of the button when it's clicked 43 | $('.copybutton').click(function(e){ 44 | e.preventDefault(); 45 | var button = $(this); 46 | if (button.data('hidden') === 'false') { 47 | // hide the code output 48 | button.parent().find('.go, .gp, .gt').hide(); 49 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); 50 | button.css('text-decoration', 'line-through'); 51 | button.attr('title', show_text); 52 | button.data('hidden', 'true'); 53 | } else { 54 | // show the code output 55 | button.parent().find('.go, .gp, .gt').show(); 56 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); 57 | button.css('text-decoration', 'none'); 58 | button.attr('title', hide_text); 59 | button.data('hidden', 'false'); 60 | } 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/_templates/breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {%- extends "sphinx_rtd_theme/breadcrumbs.html" %} 2 | 3 | {% block breadcrumbs_aside %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends '!layout.html' %} 2 | {% block document %} 3 | {{super()}} 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | CLICK 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 112 | {% endblock %} 113 | -------------------------------------------------------------------------------- /docs/api/modules.rst: -------------------------------------------------------------------------------- 1 | streamlitextras 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | streamlitextras 8 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.authenticator.rst: -------------------------------------------------------------------------------- 1 | streamlitextras.authenticator package 2 | ===================================== 3 | 4 | .. automodule:: streamlitextras.authenticator 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | streamlitextras.authenticator.exceptions module 13 | ----------------------------------------------- 14 | 15 | .. automodule:: streamlitextras.authenticator.exceptions 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | streamlitextras.authenticator.user module 21 | ----------------------------------------- 22 | 23 | .. automodule:: streamlitextras.authenticator.user 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | streamlitextras.authenticator.utils module 29 | ------------------------------------------ 30 | 31 | .. automodule:: streamlitextras.authenticator.utils 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.cookiemanager.rst: -------------------------------------------------------------------------------- 1 | streamlitextras.cookiemanager package 2 | ===================================== 3 | 4 | .. automodule:: streamlitextras.cookiemanager 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.logger.rst: -------------------------------------------------------------------------------- 1 | streamlitextras.logger package 2 | ============================== 3 | 4 | .. automodule:: streamlitextras.logger 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | streamlitextras.logger.firebasesink module 13 | ------------------------------------------ 14 | 15 | .. automodule:: streamlitextras.logger.firebasesink 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.router.rst: -------------------------------------------------------------------------------- 1 | streamlitextras.router package 2 | ============================== 3 | 4 | .. automodule:: streamlitextras.router 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.rst: -------------------------------------------------------------------------------- 1 | streamlitextras package 2 | ======================= 3 | 4 | .. automodule:: streamlitextras 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | :maxdepth: 4 14 | 15 | streamlitextras.authenticator 16 | streamlitextras.cookiemanager 17 | streamlitextras.logger 18 | streamlitextras.router 19 | streamlitextras.threader 20 | 21 | Submodules 22 | ---------- 23 | 24 | streamlitextras.helpers module 25 | ------------------------------ 26 | 27 | .. automodule:: streamlitextras.helpers 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | streamlitextras.storageservice module 33 | ------------------------------------- 34 | 35 | .. automodule:: streamlitextras.storageservice 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | streamlitextras.utils module 41 | ---------------------------- 42 | 43 | .. automodule:: streamlitextras.utils 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | streamlitextras.webutils module 49 | ------------------------------- 50 | 51 | .. automodule:: streamlitextras.webutils 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /docs/api/streamlitextras.threader.rst: -------------------------------------------------------------------------------- 1 | streamlitextras.threader package 2 | ================================ 3 | 4 | .. automodule:: streamlitextras.threader 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath("..")) 19 | sys.path.insert(0, os.path.abspath("_extensions")) 20 | sys.path.insert(0, os.path.abspath(".streamlit")) 21 | 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = "StreamlitExtras" 26 | copyright = "2022, A.D." 27 | author = "BLIPK" 28 | 29 | # The short X.Y version 30 | version = "" 31 | # The full version, including alpha/beta/rc tags 32 | release = "" 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # 39 | # needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | "sphinx.ext.autodoc", 46 | "sphinx.ext.napoleon", 47 | "sphinx.ext.viewcode", 48 | "sphinx.ext.intersphinx", 49 | "sphinxcontrib.apidoc", 50 | "autodoc_stub_file", 51 | ] 52 | 53 | apidoc_module_dir = "../streamlitextras" 54 | apidoc_output_dir = "./api" 55 | apidoc_module_first = True 56 | 57 | # Add any paths that contain templates here, relative to this directory. 58 | templates_path = ["_templates"] 59 | 60 | # The suffix(es) of source filenames. 61 | # You can specify multiple suffix as a list of string: 62 | # 63 | # source_suffix = ['.rst', '.md'] 64 | source_suffix = ".rst" 65 | 66 | # The master toctree document. 67 | master_doc = "index" 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = "English" 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | # This pattern also affects html_static_path and html_extra_path . 79 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 80 | 81 | # The name of the Pygments (syntax highlighting) style to use. 82 | pygments_style = "sphinx" 83 | 84 | # Warn about all references where the target cannot be found. 85 | nitpicky = True 86 | 87 | 88 | # -- Options for HTML output ------------------------------------------------- 89 | 90 | # The theme to use for HTML and HTML Help pages. See the documentation for 91 | # a list of builtin themes. 92 | # 93 | html_theme = "sphinx_rtd_theme" 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ["_static"] 105 | 106 | # Custom sidebar templates, must be a dictionary that maps document names 107 | # to template names. 108 | # 109 | # The default sidebars (for documents that don't match any pattern) are 110 | # defined by theme itself. Builtin themes are using these templates by 111 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 112 | # 'searchbox.html']``. 113 | # 114 | # html_sidebars = {} 115 | 116 | 117 | # -- Options for HTMLHelp output --------------------------------------------- 118 | 119 | # Output file base name for HTML help builder. 120 | htmlhelp_basename = "streamlitextrasdoc" 121 | 122 | 123 | # -- Options for LaTeX output ------------------------------------------------ 124 | 125 | latex_elements = { 126 | # The paper size ('letterpaper' or 'a4paper'). 127 | # 128 | # 'papersize': 'letterpaper', 129 | # The font size ('10pt', '11pt' or '12pt'). 130 | # 131 | # 'pointsize': '10pt', 132 | # Additional stuff for the LaTeX preamble. 133 | # 134 | # 'preamble': '', 135 | # Latex figure (float) alignment 136 | # 137 | # 'figure_align': 'htbp', 138 | } 139 | 140 | # Grouping the document tree into LaTeX files. List of tuples 141 | # (source start file, target name, title, 142 | # author, documentclass [howto, manual, or own class]). 143 | latex_documents = [ 144 | ( 145 | master_doc, 146 | "streamlitextras.tex", 147 | "Streamlit Extras Documentation", 148 | "BLIPK", 149 | "manual", 150 | ) 151 | ] 152 | 153 | 154 | # -- Options for manual page output ------------------------------------------ 155 | 156 | # One entry per manual page. List of tuples 157 | # (source start file, name, description, authors, manual section). 158 | man_pages = [ 159 | (master_doc, "streamlitextras", "Streamlit Extras Documentation", [author], 1) 160 | ] 161 | 162 | 163 | # -- Options for Texinfo output ---------------------------------------------- 164 | 165 | # Grouping the document tree into Texinfo files. List of tuples 166 | # (source start file, target name, title, author, 167 | # dir menu entry, description, category) 168 | texinfo_documents = [ 169 | ( 170 | master_doc, 171 | "streamlitextras", 172 | "Streamlit Extras Documentation", 173 | author, 174 | "streamlitextras", 175 | "One line description of project.", 176 | "Miscellaneous", 177 | ) 178 | ] 179 | 180 | 181 | # -- Extension configuration ------------------------------------------------- 182 | 183 | html_context = {"github_user": "blipk", "github_repo": "streamlitextras"} 184 | 185 | add_module_names = False 186 | autodoc_member_order = "bysource" 187 | intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} 188 | html_show_sourcelink = False 189 | html_show_copyright = False 190 | napoleon_use_rtype = False 191 | napoleon_use_ivar = True 192 | 193 | 194 | def setup(app): 195 | # app.add_css_file("css/mod.css") 196 | app.add_js_file("js/copybutton.js") 197 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Table of contents 4 | ================= 5 | 6 | .. toctree:: 7 | :includehidden: 8 | :maxdepth: 2 9 | 10 | overview.rst 11 | api/streamlitextras.rst 12 | project.rst 13 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | .. include:: ../README.rst 5 | -------------------------------------------------------------------------------- /docs/project.rst: -------------------------------------------------------------------------------- 1 | Project Information 2 | =================== 3 | 4 | .. toctree:: 5 | 6 | project/license.rst 7 | -------------------------------------------------------------------------------- /docs/project/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ####### 3 | 4 | .. include:: ../../LICENSE 5 | -------------------------------------------------------------------------------- /docs/reruntrigger.py: -------------------------------------------------------------------------------- 1 | timestamp = 1666657170.2776253 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit>=1.23.1 2 | urllib3==1.26.16 # Newer versions break pyrebase 3 | requests 4 | gcloud 5 | pyjwt 6 | firebase 7 | pyrebase4 8 | sseclient 9 | PyCryptodome 10 | requests_toolbelt 11 | firebase-admin 12 | google-cloud-storage 13 | 14 | twine 15 | Sphinx==5.2.3 ; python_version>='3.10' 16 | sphinx-autobuild==2021.3.14 ; python_version>='3.10' 17 | sphinx-rtd-theme==1.0.0 ; python_version>='3.10' 18 | docutils==0.16 ; python_version>='3.10' 19 | sphinxcontrib-apidoc ; python_version>='3.10' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | package_name = "streamlit-base-extras" 4 | version_init_file = "streamlitextras/__init__.py" 5 | 6 | try: 7 | from setuptools import setup, find_packages 8 | except ImportError: 9 | from distutils.core import setup, find_packages 10 | 11 | 12 | with open(version_init_file, "r") as file: 13 | regex_version = r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]' 14 | version = re.search(regex_version, file.read(), re.MULTILINE).group(1) 15 | 16 | with open("README.md", "r") as f: 17 | readme = f.read() 18 | 19 | setup( 20 | name=package_name, 21 | version=version, 22 | author="blipk", 23 | author_email="blipk+@github.com", 24 | description="Make building with streamlit easier.", 25 | long_description=readme, 26 | long_description_content_type="text/markdown", 27 | url="https://github.com/blipk/streamlitextras", 28 | packages=find_packages(), 29 | include_package_data=True, 30 | download_url=f"https://github.com/blipk/streamlitextras/archive/{version}.tar.gz", 31 | project_urls={ 32 | "Changelog": "https://github.com/blipk/streamlitextras/commits/", 33 | "Documentation": "https://streamlitextras.readthedocs.io/en/stable/index.html", 34 | }, 35 | keywords=[ 36 | "streamlitextras", 37 | "streamlit", 38 | "router", 39 | "authenticator", 40 | "javascript", 41 | "cookie", 42 | "thread", 43 | ], 44 | license="MIT license", 45 | classifiers=[ 46 | "Development Status :: 5 - Production/Stable", 47 | "License :: OSI Approved :: MIT License", 48 | "Operating System :: OS Independent", 49 | "Intended Audience :: Developers", 50 | "Natural Language :: English", 51 | "Topic :: Internet :: WWW/HTTP", 52 | "Topic :: Internet :: WWW/HTTP :: Browsers", 53 | "Topic :: Software Development :: Libraries", 54 | "Programming Language :: Python", 55 | "Programming Language :: Python :: 3", 56 | "Programming Language :: Python :: 3.10", 57 | "Programming Language :: Python :: 3 :: Only", 58 | "Programming Language :: Python :: Implementation :: PyPy", 59 | "Programming Language :: Python :: Implementation :: CPython", 60 | ], 61 | install_requires=[ 62 | "streamlit >= 1.23.1", 63 | "streamlit-javascript", 64 | "loguru", 65 | "requests", 66 | "gcloud", 67 | "pyjwt", 68 | "firebase", 69 | "pyrebase4", 70 | "sseclient", 71 | "PyCryptodome", 72 | "requests_toolbelt", 73 | "firebase-admin", 74 | "google-cloud-storage", 75 | ], 76 | extras_require={ 77 | "dev": [ 78 | # Docs 79 | "Sphinx==5.2.3 ; python_version>='3.10'", 80 | "sphinx-autobuild==2021.3.14 ; python_version>='3.10'", 81 | "sphinx-rtd-theme==1.0.0 ; python_version>='3.10'", 82 | "docutils==0.16 ; python_version>='3.10'", 83 | "sphinxcontrib-apidoc ; python_version>='3.10'", 84 | ] 85 | }, 86 | python_requires=">=3.10", 87 | ) 88 | -------------------------------------------------------------------------------- /streamlitextras/__init__.py: -------------------------------------------------------------------------------- 1 | # from .router import Router 2 | # from .cookiemanager import CookieManager 3 | # from .authenticator import Authenticator 4 | # from .authenticator.user import User 5 | # from .authenticator.exceptions import AuthException, LoginError, RegisterError, ResetError, UpdateError 6 | 7 | __version__ = "0.2.45" 8 | -------------------------------------------------------------------------------- /streamlitextras/authenticator/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras Authenticator 2 | 3 | Authenticator is a class to manage creating streamlit authentication forms (login/registration) and to create and manage users via firebase auth. 4 | 5 | #### Basic usage 6 | 7 | ```Python 8 | import streamlit as st 9 | from streamlitextras.authenticator import get_auth 10 | 11 | auth = None 12 | def main(): 13 | global auth 14 | auth = get_auth("my_cookie_name") 15 | auth.delayed_init() # This is required to make sure current Authenticator stays in session state 16 | 17 | auth_status = auth.auth_status 18 | user = auth.current_user 19 | 20 | if auth_status and user: 21 | st.write(f"Welcome {user.displayName}!") 22 | else: 23 | auth_page() 24 | 25 | def auth_page(): 26 | info_box = st.container() 27 | 28 | if auth.current_form == "login" or not auth.current_form: 29 | user, res, error = auth.login("Login") 30 | if auth.current_form == "register": 31 | res, error = auth.register_user("Register") 32 | elif auth.current_form == "reset_password": 33 | res, error = auth.reset_password("Request password change email") 34 | 35 | if res: 36 | info_box.info("Success!") 37 | 38 | if error: 39 | info_box.error(error.message) 40 | 41 | if __name__ == "__main__": 42 | main() 43 | 44 | ``` 45 | 46 | You will also need to set up some secrets in .streamlit/secrets.toml, you can find the firebase details through the section at the bottom on your firebase project settings page. 47 | 48 | ```TOML 49 | [authenticator] 50 | cookie_key = "my_c00k!e_jwt_k3y" 51 | admin_ids = "" 52 | developer_ids = "" 53 | 54 | [firebase] 55 | apiKey = "" 56 | authDomain = "" 57 | projectId = "" 58 | storageBucket = "" 59 | messagingSenderId = "" 60 | appId = "" 61 | measurementId = "" 62 | databaseURL = "" 63 | ``` 64 | 65 | ###### The User 66 | 67 | As per the example above, when a user is logged in you can access it with `auth.current_user`. The user class has two basic properties `is_admin` and `is_developer` which return True if the users firebase localId is in your config.toml admin_ids or developer_ids 68 | 69 | It also has the function `User.refresh_token()` which will update the users firebase auth refresh token, this is done automatically in Authenticator when required, but should also be used before making any additional calls to firebase or google cloud that use the users `localId` 70 | 71 | You will also probably want extend the User class to implement user functionality specific to your app, you can provide a reference to your own class that inherits `authenticator.User` and pass it to `get_auth("cookie_name", user_class=MyUser)` 72 | 73 | ```Python 74 | from streamlitextras.authenticator import User 75 | 76 | class MyUser(User): 77 | def __init__(self, authenticator, **kwargs): 78 | super().__init__(authenticator, **kwargs) 79 | 80 | def get_user_files(): 81 | """Get user files or something else specific to your app""" 82 | ``` 83 | 84 | #### Advanced usage 85 | 86 | If you want to use the streamlit integration but use a different authentication service provider, you can create your own class that inherits Authenticator and override the private methods. -------------------------------------------------------------------------------- /streamlitextras/authenticator/exceptions.py: -------------------------------------------------------------------------------- 1 | # Exceptions for the firebase auth helper in autenticator.py 2 | from typing import Union, Optional 3 | from requests import HTTPError 4 | 5 | 6 | class AuthException(Exception): 7 | """ 8 | Base exception for Authenticator class 9 | """ 10 | 11 | def __init__( 12 | self, 13 | message: str, 14 | firebase_error: Optional[str] = None, 15 | requests_exception: Optional[Union[HTTPError, Exception]] = None, 16 | ): 17 | """ 18 | :param str message: message to be displayed to the user 19 | :param Optional[str] firebase_error: firebase error message enum e.g INVALID_PASSWORD 20 | :param Union[HTTPError, Exception] requests_exception: requests.HTTPError with .response and .request attributes (or other associated exception) 21 | """ 22 | self.message = message 23 | self.firebase_error = firebase_error 24 | self.requests_exception = requests_exception 25 | 26 | 27 | class LoginError(AuthException): 28 | """Exceptions raised for the login user widget.""" 29 | 30 | 31 | class RegisterError(AuthException): 32 | """Exceptions raised for the register user widget.""" 33 | 34 | 35 | class ResetError(AuthException): 36 | """Exceptions raised for the rest password widget.""" 37 | 38 | 39 | class UpdateError(AuthException): 40 | """Exceptions raised for the update user details widget.""" 41 | -------------------------------------------------------------------------------- /streamlitextras/authenticator/user.py: -------------------------------------------------------------------------------- 1 | from streamlitextras.logger import log 2 | from streamlitextras.utils import repr_ 3 | from streamlitextras.authenticator.utils import handle_firebase_action 4 | from streamlitextras.authenticator.exceptions import ( 5 | AuthException, 6 | LoginError, 7 | RegisterError, 8 | ResetError, 9 | UpdateError, 10 | ) 11 | 12 | 13 | class User: 14 | """ 15 | This class is used as an interface for Authenticators users 16 | """ 17 | 18 | def __init__(self, authenticator, auth_data, login_data={}, debug: bool = False): 19 | """ 20 | Initializes a user account with associated firebase tokens and account information 21 | 22 | :param Authenticator authenticator: The associated Authenticator class that spawned this user 23 | :param dict auth_data: 24 | Dict containing session_cookie, decoded_claims and user_record 25 | :param dict login_data: 26 | The data from the initial login, not always provided (when reading session cookies) 27 | """ 28 | self.debug = debug 29 | 30 | self.authenticator = authenticator 31 | self.auth_data = auth_data 32 | self.login_data = login_data 33 | 34 | self.idToken = login_data.get("idToken", None) 35 | self.expiresIn = login_data.get("expiresIn", None) 36 | self.registered = login_data.get("registered", None) 37 | self.refreshToken = login_data.get("refreshToken", None) 38 | 39 | self.user_record = auth_data["user_record"] 40 | self.session_cookie = auth_data["session_cookie"] 41 | self.decoded_claims = auth_data["decoded_claims"] 42 | 43 | self.uid = self.decoded_claims["uid"] 44 | self.localId = self.user_record["localId"] 45 | 46 | self.email = self.user_record["email"] 47 | self.emailVerified = self.user_record["emailVerified"] 48 | self.displayName = self.user_record.get("displayName", None) 49 | self.createdAt = self.user_record.get("createdAt", None) 50 | self.lastLoginAt = self.user_record.get("lastLoginAt", None) 51 | self.lastRefreshAt = self.user_record.get("lastRefreshAt", None) 52 | self.passwordUpdatedAt = self.user_record.get("passwordUpdatedAt", None) 53 | self.providerUserInfo = self.user_record.get("providerUserInfo", None) 54 | self.validSince = self.user_record.get("validSince", None) 55 | self.photoUrl = self.user_record.get("photoUrl", None) 56 | 57 | self.account_info = login_data.get("account_info", None) 58 | self.users = self.account_info["users"] if self.account_info else None 59 | self.user = self.users[0] if self.users else None 60 | self.disabled = self.user.get("disabled", None) if self.user else None 61 | self.customAuth = self.user.get("customAuth", None) if self.user else None 62 | 63 | def refresh_token(self): 64 | refreshed = None 65 | refresh_errors = { 66 | "TOKEN_EXPIRED": "Too many recent sessions. Please try again later." 67 | } 68 | user_refresh, refresh_error = handle_firebase_action( 69 | self.authenticator.auth.refresh, 70 | LoginError, 71 | refresh_errors, 72 | self.refreshToken, 73 | ) 74 | if not refresh_error: 75 | self.login_data = {**self.login_data, **user_refresh} 76 | for key in self.__dict__.keys(): 77 | if key in user_refresh and hasattr(self, key): 78 | setattr(self, key, user_refresh[key]) 79 | self.authenticator._create_session(self.login_data) 80 | refreshed = True 81 | if self.debug: 82 | log.info(f"Users tokens refreshed {self.uid}.") 83 | else: 84 | log.error(f"Error refreshing users firebase token {refresh_error}") 85 | refreshed = False 86 | 87 | return refreshed 88 | 89 | @property 90 | def firebase_user(self): 91 | """ 92 | Gets the UserRecord object from the official firebase python SDK 93 | 94 | # https://firebase.google.com/docs/reference/admin/python/firebase_admin.auth#firebase_admin.auth.UserRecord 95 | """ 96 | self.authenticator.service_auth.get_user(self.uid) 97 | 98 | @property 99 | def is_admin(self): 100 | """ 101 | Returns true if users firebase id is in self.authenticator.admin_ids 102 | """ 103 | admin_ids = self.authenticator.admin_ids 104 | return self.localId in admin_ids 105 | 106 | @property 107 | def is_developer(self): 108 | """ 109 | Returns true if users firebase id is in self.authenticator.developer_ids 110 | """ 111 | developer_ids = self.authenticator.developer_ids 112 | return self.localId in developer_ids 113 | 114 | def __repr__(self) -> str: 115 | return repr_( 116 | self, 117 | ["passwordHash", "login_data", "refreshToken", "idToken", "authenticator"], 118 | only_keys=["uid", "email"], 119 | ) 120 | -------------------------------------------------------------------------------- /streamlitextras/authenticator/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | # Proxy for handling pyrebase/firebase errors 6 | def handle_firebase_action( 7 | action_function, exception_type, error_dict, *fn_args, **fn_kwargs 8 | ): 9 | """ 10 | Handler for pyrebase/firebase actions. 11 | """ 12 | error = None 13 | result = None 14 | try: 15 | # print("Args", fn_args, fn_kwargs) 16 | result = action_function(*fn_args, **fn_kwargs) 17 | except requests.HTTPError as e: 18 | # Pyrebase/4 is intercepting and raising an incorrectly initialized requests.HTTPError, 19 | # so the response has to be extracted from builtin BaseException args rather than e.response 20 | response_dict = json.loads(e.args[0].response.text.replace('"', '"')) 21 | error_type = None 22 | try: 23 | error_type = response_dict["error"]["message"] 24 | except: 25 | try: 26 | error_type = response_dict["error"]["errors"][0]["message"] 27 | except: 28 | pass 29 | 30 | if e.args[0].response.status_code == 400: 31 | if not error_dict: 32 | error = exception_type( 33 | f"Unknown {exception_type.__name__} while trying to {action_function.__name__} ({error_type})", 34 | error_type, 35 | e.args[0], 36 | ) 37 | else: 38 | if error_type in error_dict: 39 | error = exception_type( 40 | error_dict[error_type], error_type, e.args[0] 41 | ) 42 | else: 43 | for key in error_dict: 44 | if key in error_type: 45 | message = error_dict[key] + " " + error_type.split(" : ")[1] 46 | error = exception_type(message, error_type, e.args[0]) 47 | if not error: 48 | error = exception_type( 49 | f"Unknown {exception_type.__name__} while trying to {action_function.__name__} ({error_type})", 50 | error_type, 51 | e.args[0], 52 | ) 53 | else: 54 | error = exception_type( 55 | f"Storage network error, please try again later ({e.args[0].response.status_code}) ({error_type})", 56 | error_type, 57 | e.args[0], 58 | ) 59 | print(e.args[0]) 60 | if not error: 61 | raise 62 | except requests.ConnectionError as e: 63 | error = exception_type( 64 | "Storage connection error, please try again later", f"SERVER_{e.args[0]}", e 65 | ) 66 | 67 | return (result, error) 68 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras Cookie Manager 2 | 3 | Cookie Manager is a streamlit component function that uses the npm library universal-cookie to manage browser cookies from your streamlit code. 4 | 5 | 6 | #### Basic usage 7 | 8 | ```Python 9 | import streamlit as st 10 | from streamlitextras.cookiemanager import get_cookie_manager 11 | 12 | cookie_manager = None 13 | def main(): 14 | global cookie_manager 15 | cookie_manager = get_cookie_manager() 16 | cookie_manager.delayed_init() # Makes sure CookieManager stays in st.session_state 17 | 18 | cookie_manager.set("my_cookie_name", "I'm a cookie!") 19 | my_cookie_value = cookie_manager.get("my_cookie_name") 20 | print(my_cookie_value) # "I'm a cookie" 21 | 22 | my_cookies = cookie_manager.get_all() 23 | print(my_cookies) # {"my_cookie_name": "I'm a cookie!"} 24 | 25 | cookie_manager.delete("my_cookie_name") 26 | my_cookie_value = cookie_manager.get("my_cookie_name") 27 | print(my_cookie_value) # None 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | ``` 33 | 34 | The cookie will default to expire in 720 minutes in the users browsers timezone. 35 | 36 | 37 | #### Advanced usage 38 | 39 | Pythons `CookieManager.set()` also supports setting most cookie flags/properties e.g. 40 | 41 | ```Python 42 | import pytz 43 | import streamlit as st 44 | from streamlitextras.webutils import get_user_timezone 45 | from streamlitextras.cookiemanager import get_cookie_manager 46 | 47 | user_tz = get_user_timezone() 48 | expiry_date = (datetime.now(pytz.UTC) + timedelta(seconds=60*60)) 49 | cookie_expiry_time = expiry_date.astimezone(tz=pytz.timezone(user_tz)) 50 | 51 | cookie_manager = get_cookie_manager() 52 | cookie_manager.set("my_cookie_name", "I expire in one hour", expires_at=cookie_expiry_time) 53 | ``` 54 | 55 | For a full list see the [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html). 56 | 57 | ###### Note 58 | 59 | CookieManager will add a reference to the javascript universal-cookie class to both the JS globals `parent` and `window` object, so you can access it from the browser console or other parts of your app as `window.CookieManager`. 60 | 61 | Some streamlit componenets run in iframes so you may need to use `parent.window.CookieManager` 62 | 63 | Also note that this is a different CookieManager class to the Python CookieManager. -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import streamlit as st 4 | import streamlit.components.v1 as components 5 | from streamlitextras.logger import log 6 | from streamlitextras.utils import repr_ 7 | 8 | absolute_path = os.path.dirname(os.path.abspath(__file__)) 9 | build_path = os.path.join(absolute_path, "frontend/build") 10 | _component_func = components.declare_component("cookie_manager", path=build_path) 11 | 12 | 13 | class CookieManager: 14 | """ 15 | This is a streamlit component class to manage cookies. 16 | 17 | It uses a thin component instance wrapper around the universal-cookie library. 18 | """ 19 | 20 | def __init__(self, debug: bool = False): 21 | if "cookie_manager" in st.session_state and st.session_state["cookie_manager"]: 22 | self.cookies = st.session_state["cookie_manager"].cookies.copy() 23 | else: 24 | self.cookies = {} 25 | st.session_state["cookie_manager"] = self 26 | self.debug = debug 27 | if self.debug: 28 | log.debug(f"Initialized Cookie Manager {hex(id(self))}") 29 | 30 | def delayed_init(self): 31 | """ 32 | Used to delay initialization of streamlit objects so this class can be cached 33 | """ 34 | st.session_state["cookie_manager"] = self 35 | 36 | def cookie_manager(self, *args, **kwargs): 37 | time.sleep(0.1) 38 | try: 39 | result = _component_func(**kwargs) 40 | except st.errors.DuplicateWidgetID: 41 | kwargs["key"] = f"""{kwargs["key"]}{time.time()}""" 42 | result = _component_func(**kwargs) 43 | time.sleep(0.1) # This ensures we get a result before streamlit redraws 44 | return result 45 | 46 | def set( 47 | self, 48 | name, 49 | value, 50 | expires_at=None, 51 | secure=None, 52 | path=None, 53 | same_site=None, 54 | key="set", 55 | ): 56 | """ 57 | Set a cookie with name, value and options. 58 | Defaults are set in the JS component and listed below 59 | 60 | :param name: The name of the cookie 61 | :param value: The value of the cookie 62 | :param expires_at: 63 | Datetime of when the cookie expires. 64 | Default is set in the js at 1 day from browser timezone. 65 | :param secure: Secure flag, default is true. 66 | :param path: Cookie path, default is / 67 | :param same_site: Same site attribute, default is "strict" 68 | 69 | :param key: streamlit key used for the component instance 70 | 71 | :returns: True if the operation was successful, else False 72 | """ 73 | if name is None or name == "": 74 | return False 75 | 76 | expires_at = expires_at.isoformat() if expires_at else None 77 | options = { 78 | "name": name, 79 | "value": value, 80 | "expires": expires_at, 81 | "sameSite": same_site, 82 | } 83 | result = self.cookie_manager( 84 | method="set", options=options, key=key, default=False 85 | ) 86 | if result: 87 | self.cookies[name] = result 88 | 89 | return True 90 | 91 | def get(self, name, key="get"): 92 | """ 93 | Gets a value of a cookie. 94 | NOTE: The value isn't got directly from the component instance 95 | This class fills all cookies in self.cookies everytime streamlit instances it on the page 96 | 97 | Returns empty {} dict if no result. Returns None if the operation failed. 98 | 99 | :param name: The name of the cookie to get the value of 100 | 101 | :returns Union[str, dict]: 102 | Returns the cookie value, it may be deserialized from JSON to a dict 103 | """ 104 | if not self.cookies: 105 | self.cookies = self.cookie_manager(method="getAll", key=key, default={}) 106 | 107 | if name is None or name == "": 108 | return None 109 | result = self.cookies.get(name, None) 110 | time.sleep(0.4) # Give component time to render 111 | return result 112 | 113 | def get_all(self, key="get_all"): 114 | """ 115 | Get a dict of all the cookies in the browser, and update self.cookies 116 | 117 | :param key: streamlit key used for the component instance 118 | 119 | :returns: A dict of all the cookies 120 | """ 121 | self.cookies = self.cookie_manager(method="getAll", key=key, default={}) 122 | return self.cookies 123 | 124 | def delete(self, name, key="delete"): 125 | """ 126 | Delete a cookie from the browser 127 | 128 | :param name: the name of the cookie to delete 129 | :param key: streamlit key used for the component instance 130 | 131 | :returns: True if the operation was successful, or else False 132 | """ 133 | if name is None or name == "": 134 | return False 135 | 136 | result = self.cookie_manager( 137 | method="delete", options={"name": name}, key=key, default=False 138 | ) 139 | if result and name in self.cookies: 140 | del self.cookies[name] 141 | return result 142 | 143 | def __repr__(self) -> str: 144 | return repr_(self) 145 | 146 | 147 | # @st.cache(allow_output_mutation=True, show_spinner=False) 148 | def get_cookie_manager() -> CookieManager: 149 | if "cookie_manager" in st.session_state and st.session_state["cookie_manager"]: 150 | return st.session_state["cookie_manager"] 151 | return CookieManager() 152 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.js": "./static/js/main.a8b332f3.chunk.js", 4 | "main.js.map": "./static/js/main.a8b332f3.chunk.js.map", 5 | "runtime-main.js": "./static/js/runtime-main.41b280b5.js", 6 | "runtime-main.js.map": "./static/js/runtime-main.41b280b5.js.map", 7 | "static/js/2.2bc12ddb.chunk.js": "./static/js/2.2bc12ddb.chunk.js", 8 | "static/js/2.2bc12ddb.chunk.js.map": "./static/js/2.2bc12ddb.chunk.js.map", 9 | "index.html": "./index.html", 10 | "precache-manifest.d091de9fa4668477ba52acdbecc2c864.js": "./precache-manifest.d091de9fa4668477ba52acdbecc2c864.js", 11 | "service-worker.js": "./service-worker.js", 12 | "static/js/2.2bc12ddb.chunk.js.LICENSE.txt": "./static/js/2.2bc12ddb.chunk.js.LICENSE.txt" 13 | }, 14 | "entrypoints": [ 15 | "static/js/runtime-main.41b280b5.js", 16 | "static/js/2.2bc12ddb.chunk.js", 17 | "static/js/main.a8b332f3.chunk.js" 18 | ] 19 | } -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/index.html: -------------------------------------------------------------------------------- 1 | Cookie Manager
-------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/precache-manifest.d091de9fa4668477ba52acdbecc2c864.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "d52117df20953279ee158609f127fa64", 4 | "url": "./index.html" 5 | }, 6 | { 7 | "revision": "af08ea6b6607896320f4", 8 | "url": "./static/js/2.2bc12ddb.chunk.js" 9 | }, 10 | { 11 | "revision": "065af7f54f052065d2d7d684b53f2ae2", 12 | "url": "./static/js/2.2bc12ddb.chunk.js.LICENSE.txt" 13 | }, 14 | { 15 | "revision": "0e04dd2981e94467c748", 16 | "url": "./static/js/main.a8b332f3.chunk.js" 17 | }, 18 | { 19 | "revision": "58b1ca89039dae1abe7e", 20 | "url": "./static/js/runtime-main.41b280b5.js" 21 | } 22 | ]); -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "./precache-manifest.d091de9fa4668477ba52acdbecc2c864.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("./index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/static/js/2.2bc12ddb.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * cookie 9 | * Copyright(c) 2012-2014 Roman Shtylman 10 | * Copyright(c) 2015 Douglas Christopher Wilson 11 | * MIT Licensed 12 | */ 13 | 14 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 15 | 16 | /** 17 | * @license 18 | * Copyright 2018-2021 Streamlit Inc. 19 | * 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | 33 | /** @license React v16.13.1 34 | * react-is.production.min.js 35 | * 36 | * Copyright (c) Facebook, Inc. and its affiliates. 37 | * 38 | * This source code is licensed under the MIT license found in the 39 | * LICENSE file in the root directory of this source tree. 40 | */ 41 | 42 | /** @license React v16.14.0 43 | * react.production.min.js 44 | * 45 | * Copyright (c) Facebook, Inc. and its affiliates. 46 | * 47 | * This source code is licensed under the MIT license found in the 48 | * LICENSE file in the root directory of this source tree. 49 | */ 50 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/static/js/main.a8b332f3.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpstreamlit_extras_cookie_manager=this.webpackJsonpstreamlit_extras_cookie_manager||[]).push([[0],{5:function(e,t,a){e.exports=a(6)},6:function(e,t,a){"use strict";a.r(t);var s=a(4),n=a(0),i=null,o=new s.a;parent.CookieManager=o,window.CookieManager=o;n.a.events.addEventListener(n.a.RENDER_EVENT,(function(e){var t=e.detail,a=t.args,s=(t.disabled,t.theme,a.method),r=a.options,m=void 0===r?{}:r,p=m.name,l=m.value,g=m.expires,u=m.sameSite,c=m.secure,d=m.path,h={name:p,value:l,expires:new Date(g)||new Date((new Date).getTime()+432e5),sameSite:u||"strict",secure:c||!0,path:d||"/"},v={path:h.path,sameSite:h.sameSite},w="set"===s?o.set(p,l,h)||!0:"get"===s?o.get(p):"getAll"===s?o.getAll():"delete"===s?o.remove(p,v)||!0:null;w&&JSON.stringify(i)!=JSON.stringify(w)&&(i=w,n.a.setComponentValue(w),n.a.setComponentReady()),n.a.setFrameHeight(0)})),n.a.setComponentReady(),n.a.setFrameHeight(0)}},[[5,1,2]]]); 2 | //# sourceMappingURL=main.a8b332f3.chunk.js.map -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/static/js/main.a8b332f3.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.jsx"],"names":["lastOutput","cookies","Cookies","parent","window","Streamlit","events","addEventListener","RENDER_EVENT","event","detail","args","method","disabled","theme","options","name","value","expires","sameSite","secure","path","defaultedOptions","Date","getTime","defaultExpiryMinutes","removeOptions","output","set","get","getAll","remove","JSON","stringify","setComponentValue","setComponentReady","setFrameHeight"],"mappings":"oLAAA,yBAGIA,EAAa,KAOXC,EAAU,IAAIC,IAEpBC,OAAsB,cAAIF,EAC1BG,OAAsB,cAAIH,EAqC1BI,IAAUC,OAAOC,iBAAiBF,IAAUG,cAlCtB,SAACC,GACnB,MAAkCA,EAAMC,OAAhCC,EAAI,EAAJA,KACAC,GADc,EAARC,SAAe,EAALC,MACSH,EAAzBC,QAAO,EAAkBD,EAAjBI,eAAO,MAAG,GAAE,EACpBC,EAAiDD,EAAjDC,KAAMC,EAA2CF,EAA3CE,MAAOC,EAAoCH,EAApCG,QAASC,EAA2BJ,EAA3BI,SAAUC,EAAiBL,EAAjBK,OAAQC,EAASN,EAATM,KAE1CC,EAAmB,CACrB,KAAQN,EACR,MAASC,EACT,QAAW,IAAIM,KAAKL,IAAY,IAAIK,MAAK,IAAIA,MAAOC,UAAYC,OAChE,SAAYN,GAAY,SACxB,OAAUC,IAAU,EACpB,KAAQC,GAAQ,KAMdK,EAAgB,CAAEL,KAAMC,EAAiBD,KAAMF,SAAUG,EAAiBH,UAC1EQ,EACS,QAAXf,EAAmBX,EAAQ2B,IAAIZ,EAAMC,EAAOK,KAAqB,EAChD,QAAXV,EAAmBX,EAAQ4B,IAAIb,GACpB,WAAXJ,EAAsBX,EAAQ6B,SACnB,WAAXlB,EAAsBX,EAAQ8B,OAAOf,EAAMU,KAAkB,EAC7D,KAENC,GAAUK,KAAKC,UAAUjC,IAAegC,KAAKC,UAAUN,KACvD3B,EAAa2B,EACbtB,IAAU6B,kBAAkBP,GAC5BtB,IAAU8B,qBAGd9B,IAAU+B,eAAe,MAI7B/B,IAAU8B,oBACV9B,IAAU+B,eAAe,K","file":"static/js/main.a8b332f3.chunk.js","sourcesContent":["import Cookies from \"universal-cookie\"\nimport { Streamlit } from \"streamlit-component-lib\"\n\nlet lastOutput = null\n/*eslint-disable */\n// Called after successful set and remove\n// const changeListener = (params) => {\n// const { name, value, options } = params\n// console.log(name, value, options)\n// }\nconst cookies = new Cookies()\n// cookies.addChangeListener(changeListener)\nparent[\"CookieManager\"] = cookies\nwindow[\"CookieManager\"] = cookies\n/*eslint-enable */\n\nconst CookieManager = (event) => {\n const { args, disabled, theme } = event.detail\n const { method, options = {} } = args\n const { name, value, expires, sameSite, secure, path } = options\n const defaultExpiryMinutes = 720\n const defaultedOptions = {\n \"name\": name,\n \"value\": value,\n \"expires\": new Date(expires) || new Date(new Date().getTime() + defaultExpiryMinutes * 60000),\n \"sameSite\": sameSite || \"strict\",\n \"secure\": secure || true,\n \"path\": path || \"/\",\n //\"maxAge\"\n //\"domain\"\n //\"httpOnly\"\n }\n\n const removeOptions = { path: defaultedOptions.path, sameSite: defaultedOptions.sameSite }\n const output =\n method === \"set\" ? cookies.set(name, value, defaultedOptions) || true\n : method === \"get\" ? cookies.get(name)\n : method === \"getAll\" ? cookies.getAll()\n : method === \"delete\" ? cookies.remove(name, removeOptions) || true\n : null\n\n if (output && JSON.stringify(lastOutput) != JSON.stringify(output)) {\n lastOutput = output\n Streamlit.setComponentValue(output)\n Streamlit.setComponentReady()\n }\n\n Streamlit.setFrameHeight(0)\n}\n\nStreamlit.events.addEventListener(Streamlit.RENDER_EVENT, CookieManager)\nStreamlit.setComponentReady()\nStreamlit.setFrameHeight(0)\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/build/static/js/runtime-main.41b280b5.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,a,i=r[0],l=r[1],f=r[2],p=0,s=[];p0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "homepage": "." 32 | } 33 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cookie Manager 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /streamlitextras/cookiemanager/frontend/src/index.jsx: -------------------------------------------------------------------------------- 1 | import Cookies from "universal-cookie" 2 | import { Streamlit } from "streamlit-component-lib" 3 | 4 | let lastOutput = null 5 | /*eslint-disable */ 6 | // Called after successful set and remove 7 | // const changeListener = (params) => { 8 | // const { name, value, options } = params 9 | // console.log(name, value, options) 10 | // } 11 | const cookies = new Cookies() 12 | // cookies.addChangeListener(changeListener) 13 | parent["CookieManager"] = cookies 14 | window["CookieManager"] = cookies 15 | /*eslint-enable */ 16 | 17 | const CookieManager = (event) => { 18 | const { args, disabled, theme } = event.detail 19 | const { method, options = {} } = args 20 | const { name, value, expires, sameSite, secure, path } = options 21 | const defaultExpiryMinutes = 720 22 | const defaultedOptions = { 23 | "name": name, 24 | "value": value, 25 | "expires": new Date(expires) || new Date(new Date().getTime() + defaultExpiryMinutes * 60000), 26 | "sameSite": sameSite || "strict", 27 | "secure": secure || true, 28 | "path": path || "/", 29 | //"maxAge" 30 | //"domain" 31 | //"httpOnly" 32 | } 33 | 34 | const removeOptions = { path: defaultedOptions.path, sameSite: defaultedOptions.sameSite } 35 | const output = 36 | method === "set" ? cookies.set(name, value, defaultedOptions) || true 37 | : method === "get" ? cookies.get(name) 38 | : method === "getAll" ? cookies.getAll() 39 | : method === "delete" ? cookies.remove(name, removeOptions) || true 40 | : null 41 | 42 | if (output && JSON.stringify(lastOutput) != JSON.stringify(output)) { 43 | lastOutput = output 44 | Streamlit.setComponentValue(output) 45 | Streamlit.setComponentReady() 46 | } 47 | 48 | Streamlit.setFrameHeight(0) 49 | } 50 | 51 | Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, CookieManager) 52 | Streamlit.setComponentReady() 53 | Streamlit.setFrameHeight(0) 54 | -------------------------------------------------------------------------------- /streamlitextras/logger/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras Logger 2 | 3 | This is mostly used internally by Streamlit Extras, however you can use it as well to log in your app. 4 | See the Loguru documentation on PyPI for more information. 5 | 6 | It will log to stdout (console/terminal) as well as to two files in `./logs` 7 | 8 | 9 | #### Basic usage 10 | 11 | ```Python 12 | import streamlit as st 13 | from streamlitextras.logger import log 14 | 15 | def main(): 16 | log.debug("My app just started!") 17 | st.write("My app") 18 | 19 | if __name__ == "__main__": 20 | main() 21 | ``` 22 | 23 | ### Advanced usage 24 | 25 | The files in logs will contain trailing dicts of st.session_state() which can be processed and used to display logs in a page like so: 26 | 27 | ```Python 28 | 29 | import os 30 | import pytz 31 | import streamlit as st 32 | from streamlitextras.webutils import get_user_timezone 33 | from streamlitextras.logger import log, process_log_line 34 | 35 | def log_page(): 36 | log_file = "logs/debug_streamlit.log" 37 | if not os.path.exists(log_file): 38 | st.write("No log file.") 39 | return 40 | 41 | page_timezone = pytz.timezone(get_user_timezone()) 42 | 43 | with open(log_file, "r") as f: 44 | log_contents = f.read() 45 | 46 | log_levels = {} 47 | log_lines = log_contents.split("\n") 48 | 49 | for line in log_lines: 50 | log_obj = process_log_line(line) 51 | if not log_obj: 52 | continue 53 | level = log_obj["level"] 54 | if level not in log_levels: 55 | log_levels[level] = [] 56 | log_levels[level].append(log_obj) 57 | 58 | log_level_names = [str(name) for name in log_levels.keys()] 59 | option = st.selectbox("Log level:", log_level_names) 60 | 61 | timestamps = {} 62 | for log_obj in reversed(log_levels[option]): 63 | user = log_obj["extra"]["user"] if "user" in log_obj["extra"] else None 64 | time = log_obj["time"].astimezone(page_timezone) 65 | formatted_time = time.strftime("%a %d %b, %H:%M:%S") 66 | key = (formatted_time, user) 67 | if key not in timestamps: 68 | timestamps[key] = [] 69 | timestamps[key].append(log_obj) 70 | 71 | for key, log_objects in timestamps.items(): 72 | timestamp, timestamp_user = key 73 | columns = st.columns(2) 74 | info_display = f"""{timestamp}
{timestamp_user}""" 75 | columns[0].markdown(info_display, unsafe_allow_html=True) 76 | 77 | count = 1 78 | counter = None 79 | last_line = None 80 | for log_obj in reversed(log_objects): 81 | extra = log_obj["extra"] 82 | user = extra["user"] if "user" in extra else None 83 | 84 | if count > 1: 85 | counter.write(count) 86 | 87 | if len(extra["session_state"]) == 0: 88 | if last_line and log_obj["message"] == last_line["message"]: 89 | count += 1 90 | else: 91 | columns[1].write(log_obj["message"]) 92 | counter = columns[1].container() 93 | else: 94 | if last_line and log_obj["message"] == last_line["message"] and extra == last_line["extra"]: 95 | count += 1 96 | else: 97 | with columns[1].expander(log_obj["message"]): 98 | st.write(extra) 99 | counter = columns[1].container() 100 | 101 | if log_obj["exception"]: 102 | st.write(log_obj["exception"]) 103 | 104 | last_line = log_obj 105 | st.write("---") 106 | ``` 107 | 108 | See the `process_log_line()` definition for more information. -------------------------------------------------------------------------------- /streamlitextras/logger/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import ast 4 | import loguru 5 | import dateutil.parser 6 | from loguru._logger import Logger 7 | import streamlit as st 8 | from streamlitextras.utils import repr_ 9 | 10 | dev_emulation = os.environ.get("DEV_EMULATION", False) 11 | 12 | _LOGGER = loguru.logger 13 | log_folder = "logs" 14 | log_filename = "streamlit.log" 15 | 16 | module_filter = "" 17 | default_format = "{time} | {level: <8} | {name}:{module}:{function}:{file}:{line} | {message} | {extra}" 18 | detailed_format = "{time} | {level: <8} | {name}:{module}:{function}:{file.path}:{line} | {message} | {extra} | {exception} | {process.name}:{process} {thread.name}:{thread}" 19 | 20 | colour_format = ( 21 | "{time:YYYY-MM-DD HH:mm:ss.SSS} | " 22 | "{level: <8} | " 23 | "{name}:{module}:{function}:{file}:{line} | {extra} | " 24 | "\n{message}" 25 | ) 26 | 27 | colour_detailed_format = ( 28 | "{time:YYYY-MM-DD HH:mm:ss.SSS} | " 29 | "{level: <8} | " 30 | "{name}:{module}:{function}:{file.path}:{line} | " 31 | "\n{extra}{message} | " 32 | "{exception} | " 33 | "{process.name}:{process} @ {thread.name}:{thread}" 34 | ) 35 | if dev_emulation: 36 | colour_format = colour_format.replace("file", "file.path") 37 | 38 | handlers = [ 39 | { 40 | "sink": os.path.join(log_folder, log_filename), 41 | "rotation": "250 MB", 42 | "enqueue": True, 43 | "format": default_format, 44 | "filter": module_filter, 45 | "level": "INFO", 46 | "catch": True, 47 | }, 48 | { 49 | "sink": os.path.join(log_folder, "debug_" + log_filename), 50 | "rotation": "250 MB", 51 | "enqueue": True, 52 | "format": detailed_format, 53 | "filter": module_filter, 54 | "level": "TRACE", 55 | "backtrace": True, 56 | "diagnose": True, 57 | "catch": True, 58 | }, 59 | # Errors will be logged to sys.stdout 60 | # {"sink": sys.stderr, 61 | # "format": colour_detailed_format, 62 | # "filter": module_filter, 63 | # "level": "ERROR", 64 | # "backtrace": True, 65 | # "colorize": True}, 66 | { 67 | "sink": sys.stdout, 68 | "format": colour_format, 69 | "filter": module_filter, 70 | "level": "DEBUG", 71 | "colorize": True, 72 | }, 73 | ] 74 | 75 | 76 | def process_log_line(log_line: str): 77 | """ 78 | Process a log line formatted from this module into a dictionary of its sections. 79 | 80 | :param str log_line: The log line to process 81 | """ 82 | if not log_line: 83 | return None 84 | time, level, namespace, message, extra, exception, exec = log_line.split(" | ") 85 | level = level.strip() 86 | name, module, function_name, file_path, line = namespace.split(":") 87 | 88 | try: 89 | process, thread = exec.split(" @ ") 90 | except: 91 | process = exec.split(" ")[0] 92 | thread = " ".join(exec.split(" ")[1:]) 93 | process_name, process_id = process.split(":") 94 | thread_name, thread_id = thread.split(":") 95 | 96 | log_obj = { 97 | "log_line": log_line, 98 | "level": level, 99 | "time": dateutil.parser.isoparse(time), 100 | "message": message, 101 | "extra": ast.literal_eval(extra), 102 | "exception": exception, 103 | "namespace": namespace, 104 | "name": name, 105 | "module": module, 106 | "function_name": function_name, 107 | "file_path": file_path, 108 | "line": line, 109 | "process_name": process_name, 110 | "process_id": process_id, 111 | "thread_name": thread_name, 112 | "thread_id": thread_id, 113 | } 114 | return log_obj 115 | 116 | 117 | def default_bind(include_session_state: bool = False): 118 | """ 119 | Generate a dict from st.session_state to be stored with every log line 120 | """ 121 | extra = { 122 | "user": repr(st.session_state.get("user", None)), 123 | } 124 | 125 | if include_session_state: 126 | extra["session_state"] = ( 127 | {k: f"{v}" for k, v in st.session_state.to_dict().items()}, 128 | ) 129 | 130 | for k in list(extra.keys()): 131 | if not extra[k]: 132 | del extra[k] 133 | 134 | return extra 135 | 136 | 137 | def bind_log(extras=None) -> Logger: 138 | """ 139 | Bind the logger to the session state dictionary 140 | """ 141 | global log 142 | merged = {**default_bind(), **(extras if extras else {})} 143 | 144 | for k in list(merged): 145 | merged[k] = str(merged[k]) 146 | 147 | log = _LOGGER.bind(**merged) 148 | return log 149 | 150 | 151 | log: Logger 152 | 153 | 154 | def __getattr__(name): 155 | global log 156 | if name == "log": 157 | log = bind_log() 158 | return log 159 | raise AttributeError(f"module '{__name__}' has no attribute '{name}'") 160 | 161 | 162 | sinks = _LOGGER.configure(handlers=handlers, activation=[("", True)]) 163 | -------------------------------------------------------------------------------- /streamlitextras/logger/firebasesink.py: -------------------------------------------------------------------------------- 1 | from streamlitextras.authenticator.utils import handle_firebase_action 2 | 3 | 4 | def firestore_sink(message): 5 | """ 6 | Loguru sink to store logs in firestore 7 | """ 8 | raise NotImplementedError() 9 | -------------------------------------------------------------------------------- /streamlitextras/router/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras Router 2 | 3 | Router is a class to manage pages and routing to them, either via browser query strings or via other paths in your code. 4 | 5 | #### Basic usage 6 | 7 | Note that `st.set_page_config` must be called before getting the router. 8 | 9 | ```Python 10 | import random 11 | import streamlit as st 12 | from streamlitextras.router import get_router 13 | 14 | router = None 15 | def main(): 16 | global router 17 | pages = { 18 | "main": main_page, 19 | "other": another_page, 20 | } 21 | st.set_page_config( 22 | page_title="MyApp", 23 | layout="wide", 24 | initial_sidebar_state="auto" 25 | ) 26 | router = get_router() 27 | router.delayed_init() # This is required to make sure current Router stays in session state 28 | 29 | computed_chance = random.randrange(10) 30 | if computed_chance > 1: 31 | router.route() 32 | else: 33 | router.route("other", computed_chance) 34 | 35 | def main_page(page_state = None): 36 | st.write("This is the main.") 37 | 38 | def another_page(page_state = None): 39 | st.write(f"This is another page, you're lucky to be here. Number {page_state} lucky.") 40 | 41 | if __name__ == "__main__": 42 | main() 43 | ``` 44 | 45 | This will set the browser query string to `/?page_name=page_state` and render the page function associated with that page name in pages. 46 | 47 | `router.route()` takes the key from the `pages` dict as the first argument, and routes to that page, with no arguments will default to the first page in `pages` 48 | 49 | Due to a streamlit bug not being able to read empty query strings, page_state will show as `~` in the browser URL if there is none. 50 | 51 | Instead of using `router.route()` you can use `router.show_route_view()` which will bypass setting query strings and just run that page function: 52 | 53 | ```Python 54 | computed_chance = random.randrange(10) 55 | if computed_chance > 1: 56 | router.show_route_view() 57 | else: 58 | router.show_route_view("other", computed_chance) 59 | ``` 60 | 61 | See the [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html) for more usage options, such as setting a callable pre-route function, or passing extra page_state/dependencies to every page_function. 62 | 63 | #### Advanced usage 64 | 65 | Below is an implementation that uses a routes folder module to manage pages. 66 | 67 | routes folder `__init__.py` 68 | ```Python 69 | import streamlitextras 70 | 71 | router: streamlitextras.router.Router 72 | auth: streamlitextras.authenticator.Authenticator 73 | cookie_manager: streamlitextras.cookiemanager.CookieManager 74 | 75 | def __getattr__(name): 76 | if name == "cookie_manager": 77 | cookie_manager = streamlitextras.cookiemanager.get_cookie_manager() 78 | return cookie_manager 79 | elif name == "auth": 80 | auth = streamlitextras.authenticator.get_auth("my_cookie") 81 | return auth 82 | elif name == "router": 83 | router = streamlitextras.router.get_router(pages, pre_route) 84 | return router 85 | raise AttributeError(f"module '{__name__}' has no attribute '{name}'") 86 | 87 | from .preroute import pre_route 88 | from .userpage import user_page 89 | from .authpage import auth_page 90 | from .mainpage import main_page 91 | 92 | pages = { 93 | "main": main_page, 94 | "user": user_page, 95 | "auth": auth_page, 96 | } 97 | 98 | ``` 99 | 100 | `main.py` 101 | ```Python 102 | import os 103 | import streamlit as st 104 | from streamlitextras.authenticator.exceptions import AuthException 105 | from streamlitextras.logger import log 106 | st.set_page_config( 107 | page_title="", 108 | layout="wide", 109 | initial_sidebar_state="auto" 110 | ) 111 | import routes # NOTE: This has to be imported after st.set_page_config() 112 | 113 | 114 | router = None 115 | auth = None 116 | cookie_manager = None 117 | def main() -> None: 118 | router = routes.router 119 | auth = routes.auth 120 | cookie_manager = routes.cookie_manager 121 | 122 | # This is required to make sure current instances stay in session_state 123 | auth.delayed_init() 124 | router.delayed_init() 125 | 126 | try: 127 | auth_status = auth.auth_status 128 | user = auth.current_user 129 | log.info(f"Auth status: {auth_status} - User: {user.localId if user else user}") 130 | except AuthException as e: 131 | log.error(f"Error checking auth_status {e}") 132 | return router.show_route_view("auth") 133 | 134 | if auth_status and user: 135 | router.show_route_view(redirect_page_names=["auth"]) 136 | else: 137 | router.show_route_view("auth") 138 | 139 | if __name__ == "__main__": 140 | main() 141 | 142 | ``` 143 | 144 | For full reference see the [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html). -------------------------------------------------------------------------------- /streamlitextras/storageservice.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import binascii 4 | import datetime 5 | import streamlit as st 6 | from streamlit.runtime.uploaded_file_manager import UploadedFile 7 | 8 | from google.oauth2 import service_account 9 | from google.cloud import storage 10 | from google.cloud.storage.retry import _should_retry 11 | from google.api_core import retry 12 | 13 | from typing import Optional, Iterable, Union, Tuple 14 | from io import BytesIO 15 | 16 | # Default project and bucket 17 | default_gproject = st.secrets["gcp_service_account"]["project_id"] 18 | default_bucket = st.secrets["firebase"]["storageBucket"] 19 | 20 | # Create API client. 21 | credentials = service_account.Credentials.from_service_account_info( 22 | dict(st.secrets["gcp_service_account"]) 23 | ) 24 | client = storage.Client(credentials=credentials) 25 | 26 | _INITIAL_DELAY = 1.0 # seconds 27 | _MAXIMUM_DELAY = 4.0 28 | _DELAY_MULTIPLIER = 2.0 29 | _DEADLINE = 60.0 30 | retry_custom = retry.Retry( 31 | _should_retry, _INITIAL_DELAY, _MAXIMUM_DELAY, _DELAY_MULTIPLIER, _DEADLINE 32 | ) 33 | 34 | 35 | def compute_bytes_md5hash(data: bytes): 36 | md5hex = hashlib.md5(data).hexdigest() 37 | encoded_bytes = base64.b64encode(binascii.unhexlify(md5hex)) 38 | sig = encoded_bytes.rstrip(b"\n").decode("UTF-8") 39 | return sig 40 | 41 | 42 | def get_buckets( 43 | project: str = default_gproject, 44 | prefix: Optional[str] = None, 45 | page_size: int = 500, 46 | timeout: int = 8, 47 | retry: retry.Retry = retry_custom, 48 | print_names: bool = False, 49 | ) -> Iterable[storage.Bucket]: 50 | buckets = client.list_buckets( 51 | project=project, 52 | prefix=prefix, 53 | page_size=page_size, 54 | timeout=timeout, 55 | retry=retry, 56 | ) 57 | if print_names: 58 | for bucket in buckets: 59 | print(bucket) 60 | return buckets 61 | 62 | 63 | def get_blobs( 64 | bucket_or_name: Union[str, storage.Bucket] = default_bucket, 65 | prefix: Optional[str] = None, 66 | page_size: int = 500, 67 | timeout: int = 8, 68 | retry: retry.Retry = retry_custom, 69 | print_names: bool = False, 70 | ) -> Optional[Iterable[storage.Blob]]: 71 | """ 72 | Prints a list of all the blobs in the specified storage bucket, 73 | and returns them as an iterable 74 | """ 75 | blobs = client.list_blobs( 76 | bucket_or_name, prefix=prefix, page_size=page_size, timeout=timeout, retry=retry 77 | ) 78 | bucket_name = bucket_or_name if type(bucket_or_name) == str else bucket_or_name.name 79 | if print_names: 80 | for blob in blobs: 81 | print(blob) 82 | 83 | return blobs 84 | 85 | 86 | def get_bucket( 87 | bucket_name: str, timeout: int = 8, retry: retry.Retry = retry_custom 88 | ) -> Optional[storage.Bucket]: 89 | bucket = client.lookup_bucket(bucket_name, timeout=timeout, retry=retry) 90 | return bucket 91 | 92 | 93 | def create_bucket( 94 | bucket_name: str, 95 | project: str = default_gproject, 96 | billing_project: str = default_gproject, 97 | bucket_location: str = "us-east1", 98 | timeout: int = 8, 99 | retry: retry.Retry = retry_custom, 100 | ) -> storage.bucket.Bucket: 101 | bucket = client.create_bucket( 102 | bucket_name, 103 | location=bucket_location, 104 | project=project, 105 | user_project=billing_project, 106 | timeout=timeout, 107 | retry=retry, 108 | ) 109 | return bucket 110 | 111 | 112 | def upload_blob_data( 113 | blob_name: str, 114 | blob_data: Union[UploadedFile, str], 115 | bucket_name: str = default_bucket, 116 | content_type: Optional[str] = None, 117 | timeout: int = 8, 118 | retry: retry.Retry = retry_custom, 119 | ) -> Tuple[storage.Blob, str]: 120 | bucket = client.bucket(bucket_name) 121 | blob = bucket.blob(blob_name) 122 | 123 | if blob.exists(client, timeout=timeout, retry=retry): 124 | return (blob, get_blob_url(blob)) 125 | 126 | if type(blob_data) == UploadedFile: 127 | blob.upload_from_file( 128 | blob_data, content_type=content_type, timeout=timeout, retry=retry 129 | ) 130 | elif type(blob_data) == str: 131 | blob.upload_from_string( 132 | blob_data, content_type=content_type, timeout=timeout, retry=retry 133 | ) 134 | 135 | return (blob, get_blob_url(blob)) 136 | 137 | 138 | def get_blob_url(blob: storage.Blob): 139 | url = blob.generate_signed_url( 140 | expiration=datetime.timedelta(minutes=60), version="v4", method="GET" 141 | ) 142 | return url 143 | 144 | 145 | def download_blob_data( 146 | bucket_name: str, blob_name: str, return_type: str = "bytes" 147 | ) -> Union[BytesIO, str]: 148 | bucket = client.bucket(bucket_name) 149 | content = None 150 | if return_type == "bytes": 151 | content = bucket.blob(blob_name).download_as_bytes() 152 | elif return_type == "string": 153 | content = bucket.blob(blob_name).download_as_text(encoding="utf-8") 154 | 155 | return content 156 | -------------------------------------------------------------------------------- /streamlitextras/threader/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Extras Threader 2 | 3 | Utility functions to make running threading.Threads easy in streamlit. 4 | 5 | #### Basic usage 6 | 7 | This requires `runOnSave = true` in `./streamlit/config.toml`, 8 | and you will need to create an empty file named `reruntrigger.py` in the root of your project. 9 | 10 | ```Python 11 | import time 12 | import streamlit as st 13 | import reruntrigger_default # This is required so the watcher can rerun from this file 14 | from streamlitextras.threader import lock, trigger_rerun, \ 15 | streamlit_thread, get_thread, \ 16 | last_trigger_time 17 | 18 | def main(): 19 | thread_name = streamlit_thread(my_threaded_function, (5,)) 20 | st.write("This should be here before my_threaded_function() is done!") 21 | st.button("Thread info", on_click=button_callback, args=(thread_name,)) 22 | 23 | def button_callback(thread_name): 24 | # Sometimes streamlit will trigger button callbacks when re-running, 25 | # So we block them if we triggered a rerun recently 26 | if last_trigger_time() < 1: 27 | return 28 | my_thread = get_thread(thread_name) 29 | st.write(my_thread) # threading.Thread 30 | 31 | def my_threaded_function(time): 32 | time.sleep(time) 33 | with lock: 34 | # Do something that might interfere with other threads, 35 | # file operations or setting st.session_state 36 | pass 37 | print(f"Thread done! I slept for {time} seconds.") 38 | 39 | if __name__ == "__main__": 40 | main() 41 | ``` 42 | 43 | #### Advanced usage 44 | 45 | Mostly you may want to use `streamlit_thread(my_threaded_function, rerun_st=False)` if you don't want streamlit to rerun after the thread. 46 | 47 | **NOTE** The rerun trigger will rerun all streamlit sessions for all users on your site. 48 | 49 | To get around this, generate or use a unique id you have for each user session, 50 | use it in this modules function arguments and then import the rerun trigger for that session e.g. 51 | 52 | ```Python 53 | import importlib 54 | unique_id = st.session_state.get("session_uid", generate_uniqueid()) 55 | st.session_state["session_uid"] = unique_id 56 | importlib.import_module(f"reruntrigger_{unique_id}") 57 | 58 | 59 | from streamlitextras.threader import trigger_rerun 60 | st.button("Rerun for this session only", on_click=trigger_rerun, args=(unique_id,)) 61 | ``` 62 | 63 | See the [API docs](https://streamlitextras.readthedocs.io/en/latest/api/streamlitextras.html) or the source file for function argument reference. 64 | -------------------------------------------------------------------------------- /streamlitextras/threader/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import inspect 4 | import threading 5 | from collections.abc import Callable, Iterable, Mapping 6 | 7 | from streamlit.runtime.scriptrunner import ( 8 | add_script_run_ctx, 9 | get_script_run_ctx, 10 | RerunException, 11 | ScriptRunContext, 12 | ) 13 | from typing import Any, Callable, Optional 14 | 15 | # script_dir = os.path.dirname(os.path.realpath(__file__)) 16 | script_dir = os.getcwd() 17 | 18 | default_id = "default" 19 | 20 | 21 | def trigger_file_path(unique_id: str = default_id) -> str: 22 | trigger_file_path = os.path.join(script_dir, f"reruntrigger_{unique_id}.py") 23 | if not os.path.exists(trigger_file_path): 24 | with open(trigger_file_path, "w") as f: 25 | f.write(f"timestamp = {time.time()}") 26 | return trigger_file_path 27 | 28 | 29 | lock = threading.Lock() 30 | 31 | 32 | def last_trigger_time(unique_id: str = default_id) -> int: 33 | """ 34 | Returns the seconds since last writing the trigger file 35 | """ 36 | this_trigger_file_path = trigger_file_path(unique_id) 37 | if not os.path.exists(this_trigger_file_path): 38 | return 9999 39 | modified_time = os.path.getmtime(this_trigger_file_path) 40 | modified_time_seconds = time.time() - modified_time 41 | return modified_time_seconds 42 | 43 | 44 | def trigger_rerun( 45 | unique_id: str = default_id, last_write_margin: int = 1, delay: int = 0 46 | ) -> None: 47 | """ 48 | Triggers treamlit to rerun the current page state. 49 | runOnSave must be set to true in config.toml 50 | 51 | :param str unique_id: Unique ID to be triggered, should be set per session e.g. user id or a hash you create in their session state. 52 | :param int last_write_margin: 53 | If the file was modified less than this many seconds ago, the rerun will not be performed 54 | :param int delay: sleep for this many seconds before writing the rerun trigger 55 | """ 56 | if delay: 57 | time.sleep(delay) 58 | 59 | with lock: 60 | modified_time_seconds = last_trigger_time(unique_id) 61 | if last_write_margin == 0 or modified_time_seconds > last_write_margin: 62 | frame = inspect.currentframe() 63 | caller = frame.f_back.f_code.co_name 64 | caller_caller = frame.f_back.f_back.f_code.co_name 65 | trigger_file = trigger_file_path(unique_id) 66 | print( 67 | "Writing trigger", 68 | trigger_file, 69 | f"from `{caller_caller}.{caller}`", 70 | flush=True, 71 | ) 72 | with open(trigger_file, "w") as f: 73 | f.write(f"timestamp = {time.time()}") 74 | # https://github.com/streamlit/streamlit/issues/1792 75 | # https://discuss.streamlit.io/t/using-streamlit-with-multithreading/30990 76 | # https://discuss.streamlit.io/t/how-to-run-a-subprocess-programs-using-thread-inside-streamlit/2440/2 77 | # https://discuss.streamlit.io/t/how-to-monitor-the-filesystem-and-have-streamlit-updated-when-some-files-are-modified/822/3 78 | 79 | 80 | def thread_wrapper( 81 | thread_func, 82 | rerun_st=True, 83 | last_write_margin: int = 1, 84 | delay: int = 0, 85 | trigger_unique_id: str = default_id, 86 | *args, 87 | **kwargs, 88 | ) -> None: 89 | """ 90 | Wrapper for running thread functions 91 | For parameters see streamlit_thread() and trigger_rerun() 92 | """ 93 | # print("Hashseed in thread:", os.environ.get("PYTHONHASHSEED", False)) 94 | thread_func(*args, **kwargs) 95 | if rerun_st is True: 96 | trigger_rerun(trigger_unique_id, last_write_margin, delay) 97 | 98 | 99 | def streamlit_thread( 100 | thread_func: Callable, 101 | args: tuple = (), 102 | kwargs: dict = {}, 103 | rerun_st: bool = True, 104 | last_write_margin: int = 1, 105 | delay: int = 0, 106 | script_run_context: ScriptRunContext | None = None, 107 | autostart: bool = True, 108 | trigger_unique_id: str = default_id, 109 | error_handler: Callable | None = None, 110 | ) -> str: 111 | """ 112 | Spawns and starts a threading.Thread that runs thread_func with the passed args and kwargs 113 | 114 | :param Callable thread_func: The function to run in the thread 115 | :param tuple args: The args to pass to the function in the thread 116 | :param dict kwargs: The kwargs to pass to the function in the thread 117 | :param bool rerun_st: Whether to rerun streamlit after the thread function finishes 118 | 119 | :param Callable error_handler: Error handler function that takes the thread exception as an argument 120 | 121 | :returns: The name of the thread. Can use get_thread to get the threading.Thread instance 122 | """ 123 | # print("Thread entry hashseed:", os.environ.get("PYTHONHASHSEED", False)) 124 | args = (thread_func, rerun_st, last_write_margin, delay, trigger_unique_id, *args) 125 | thread = PropagatingThread( 126 | target=thread_wrapper, error_handler=error_handler, args=args, kwargs=kwargs 127 | ) 128 | 129 | if not script_run_context: 130 | script_run_context = get_script_run_ctx() 131 | add_script_run_ctx(thread, script_run_context) 132 | 133 | time.sleep(0.4) 134 | if autostart is True: 135 | thread.start() 136 | 137 | return thread.name 138 | 139 | 140 | def get_thread(thread_name) -> Optional[threading.Thread]: 141 | """ 142 | Gets the threading.Thread instance thats name attribute matches thread_name 143 | 144 | :param thread_name: The name attribute of the thread to look for. 145 | 146 | :returns: The threading.Thread or None if theres no thread with the supplied thread_name 147 | """ 148 | threads = threading.enumerate() 149 | target_thread = None 150 | for thread in threads: 151 | if thread.name == thread_name: 152 | target_thread = thread 153 | break 154 | return target_thread 155 | 156 | 157 | class PropagatingThread(threading.Thread): 158 | def __init__(self, *args, **kwargs) -> None: 159 | self.error_handler = kwargs.get("error_handler", None) 160 | del kwargs["error_handler"] 161 | super().__init__(*args, **kwargs) 162 | 163 | def run(self): 164 | self.exc = None 165 | try: 166 | self.ret = self._target(*self._args, **self._kwargs) 167 | except RerunException as e: 168 | self.exc = e 169 | except BaseException as e: 170 | self.exc = e 171 | if self.error_handler and callable(self.error_handler): 172 | self.error_handler(e) 173 | else: 174 | raise 175 | 176 | def join(self, timeout=None): 177 | super(PropagatingThread, self).join(timeout) 178 | if self.exc: 179 | if self.error_handler and callable(self.error_handler): 180 | self.error_handler(self.exc) 181 | else: 182 | raise self.exc 183 | # raise RuntimeError('Exception in thread') from self.exc 184 | return self.ret 185 | -------------------------------------------------------------------------------- /streamlitextras/utils.py: -------------------------------------------------------------------------------- 1 | # File contains utility or helper functions 2 | import os 3 | from typing import Optional 4 | from streamlit.runtime.uploaded_file_manager import UploadedFile 5 | 6 | 7 | def repr_( 8 | cls, ignore_keys: Optional[list[str]] = None, only_keys: Optional[list[str]] = None 9 | ) -> str: 10 | """ 11 | Returns a string detailing a class attributes from cls.__dict__ 12 | Makes nice printing for __repr__ implementations 13 | 14 | :param ignore_keys: If provided, these keys will be not be included in the attribute string 15 | :param only_keys: If provided, these keys will be the only ones included in the attribute string 16 | """ 17 | if not hasattr(cls, "__dict__"): 18 | return repr(cls) 19 | if not ignore_keys or only_keys: 20 | ignore_keys = [] 21 | classname = cls.__class__.__name__ 22 | 23 | try: 24 | args = ", ".join( 25 | [ 26 | f"{k}={repr(v)}" 27 | for (k, v) in cls.__dict__.items() 28 | if k not in ignore_keys and (k in only_keys if only_keys else True) 29 | ] 30 | ) 31 | except RecursionError: 32 | args = "Too much recursion" 33 | return f"{classname}({hex(id(cls))}, {args})" 34 | 35 | 36 | def save_file(st_file_object: UploadedFile, to_path: str) -> str: 37 | """ 38 | Saves a streamlit UploadedFile BytesIO object to the given relative path 39 | 40 | :param UploadedFile st_file_object: The UploadedFile bytes object to save to disk - contains filename and metadata 41 | :param str to_path: The relative path to a folder to save the file to 42 | :returns str: Will return the relative path which can be used as a URL 43 | """ 44 | os.makedirs(to_path, exist_ok=True) 45 | path = os.path.join(to_path, st_file_object.name) 46 | with open(path, "wb") as f: 47 | f.write(st_file_object.getbuffer()) 48 | return "/" + path 49 | 50 | 51 | def where_stack(): 52 | import inspect 53 | 54 | stack = inspect.stack() 55 | the_class = stack[1][0].f_locals["self"].__class__.__name__ 56 | the_method = stack[1][0].f_code.co_name 57 | 58 | print("I was called by {}.{}()".format(the_class, the_method)) 59 | -------------------------------------------------------------------------------- /streamlitextras/webutils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import html 3 | import uuid 4 | import base64 5 | import streamlit as st 6 | from io import BytesIO 7 | from typing import Union, Optional 8 | 9 | from streamlit_javascript import st_javascript 10 | 11 | from streamlit.runtime.uploaded_file_manager import UploadedFile 12 | 13 | 14 | def stxs_javascript(source: str) -> None: 15 | """ 16 | Runs javascript on the top level context of the page. 17 | 18 | Does this by embedding an iframe that attaches a script tag to its parent. 19 | 20 | :param str source: The script source to embed in the element 21 | """ 22 | div_id = uuid.uuid4() 23 | 24 | wrapped_source = f"(async () => {{{source}}})()" 25 | 26 | st.markdown( 27 | f""" 28 |