├── docs ├── CNAME ├── favicon.ico ├── img │ ├── card.png │ ├── form.png │ ├── grid.png │ ├── chart.png │ ├── index.png │ ├── social.png │ ├── table.png │ └── autocomplete.png ├── hero.html ├── login.html ├── gallery │ ├── card.html │ ├── form.html │ └── chart.html ├── gallery.html ├── index.html └── flask.html ├── sample-apps ├── pypi-analytics │ ├── requirements.txt │ ├── Procfile │ ├── Makefile │ └── main.py └── websockets │ ├── requirements.txt │ ├── Procfile │ ├── Makefile │ └── main.py ├── .vscode └── settings.json ├── generator ├── docs │ ├── login.py │ ├── gallery.py │ ├── index.py │ ├── flask.py │ ├── about.py │ ├── component_reference.py │ ├── common │ │ └── components.py │ └── playground.py ├── gallery │ ├── chart.py │ ├── card.py │ ├── grid.py │ ├── table.py │ └── form.py ├── screenshot.py ├── generate.py └── includes │ └── advanced.py.txt ├── integration_tests └── flask_test.py ├── src └── pyvibe │ └── component_interface.py ├── pyproject.toml ├── LICENSE ├── .github └── workflows │ └── python-publish.yml ├── Makefile ├── CONTRIBUTING.md ├── .gitignore └── README.md /docs/CNAME: -------------------------------------------------------------------------------- 1 | www.pyvibe.com -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /docs/img/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/card.png -------------------------------------------------------------------------------- /docs/img/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/form.png -------------------------------------------------------------------------------- /docs/img/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/grid.png -------------------------------------------------------------------------------- /docs/img/chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/chart.png -------------------------------------------------------------------------------- /docs/img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/index.png -------------------------------------------------------------------------------- /docs/img/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/social.png -------------------------------------------------------------------------------- /docs/img/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/table.png -------------------------------------------------------------------------------- /docs/img/autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycob/pyvibe/HEAD/docs/img/autocomplete.png -------------------------------------------------------------------------------- /sample-apps/pypi-analytics/requirements.txt: -------------------------------------------------------------------------------- 1 | pyvibe 2 | pandas 3 | pycob 4 | gunicorn 5 | plotly -------------------------------------------------------------------------------- /sample-apps/websockets/requirements.txt: -------------------------------------------------------------------------------- 1 | pyvibe 2 | pycob 3 | flask 4 | flask-sock 5 | gunicorn 6 | -------------------------------------------------------------------------------- /sample-apps/websockets/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app 2 | -------------------------------------------------------------------------------- /sample-apps/pypi-analytics/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "lldb.library": "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/LLDB.framework/Versions/A/LLDB" 3 | } -------------------------------------------------------------------------------- /generator/docs/login.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import navbar, footer 3 | 4 | page = pv.Page("Login", navbar=navbar, footer=footer) 5 | 6 | page.add_header("Login") 7 | page.add_text("You can create your own login forms or link to third-party auth providers") -------------------------------------------------------------------------------- /generator/gallery/chart.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | import plotly.express as px 3 | 4 | df = px.data.gapminder().query("country=='Canada'") 5 | 6 | page = pv.Page("Chart Example") 7 | 8 | page.add_header("Chart Example") 9 | 10 | fig = px.bar(df, x='year', y='pop') 11 | 12 | page.add_plotlyfigure(fig) -------------------------------------------------------------------------------- /integration_tests/flask_test.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | import pyvibe as pv 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def index(): 8 | page = pv.Page('Home') 9 | page.add_header('Hello World') 10 | return page.to_html() 11 | 12 | if __name__ == '__main__': 13 | app.run(debug=True) -------------------------------------------------------------------------------- /generator/gallery/card.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | 3 | page = pv.Page("Card Example") 4 | 5 | page.add_header("Card Example") 6 | 7 | with page.add_card() as card: 8 | card.add_header("Card Header") 9 | card.add_text("This is a card. You can put most types of content in a card.") 10 | card.add_link("Learn more", "https://pycob.com") 11 | 12 | -------------------------------------------------------------------------------- /src/pyvibe/component_interface.py: -------------------------------------------------------------------------------- 1 | import json 2 | from abc import ABC, abstractmethod 3 | 4 | # Base class for all components 5 | class Component: 6 | @abstractmethod 7 | def to_html(self) -> str: 8 | pass 9 | 10 | def _repr_html_(self): 11 | return self.to_html() 12 | 13 | def to_json(self) -> str: 14 | return json.dumps(self, default=lambda o: o.__dict__, 15 | sort_keys=True, indent=4) 16 | 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pyvibe" 3 | version = "0.0.5" 4 | authors = [ 5 | { name="Zain Hoda", email="zain@pycob.com" }, 6 | ] 7 | description = "Easily create styled web pages with Python" 8 | readme = "README.md" 9 | requires-python = ">=3.7" 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | ] 15 | 16 | [project.urls] 17 | "Homepage" = "https://github.com/pycob/pyvibe" 18 | "Bug Tracker" = "https://github.com/pycob/pyvibe/issues" -------------------------------------------------------------------------------- /generator/gallery/grid.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | import plotly.express as px 3 | 4 | df = px.data.gapminder().query("country=='Canada'") 5 | df = df.sort_values(by="year") 6 | 7 | page = pv.Page("Grid Example") 8 | 9 | page.add_header("Grid Example") 10 | 11 | with page.add_container(grid_columns=2) as container: 12 | container.add_plotlyfigure(px.bar(df, x='year', y='pop')) 13 | container.add_plotlyfigure(px.line(df, x='year', y='lifeExp')) 14 | container.add_plotlyfigure(px.bar(df, x='year', y='gdpPercap')) 15 | container.add_plotlyfigure(px.bar(df, x='year', y='pop')) -------------------------------------------------------------------------------- /sample-apps/pypi-analytics/Makefile: -------------------------------------------------------------------------------- 1 | # define the name of the virtual environment directory 2 | VENV := venv 3 | 4 | # default target, when make executed without arguments 5 | all: venv run 6 | 7 | $(VENV)/bin/activate: requirements.txt 8 | python3 -m venv $(VENV) 9 | ./$(VENV)/bin/pip install -r requirements.txt 10 | 11 | # venv is a shortcut target 12 | venv: $(VENV)/bin/activate 13 | 14 | run: venv 15 | ./$(VENV)/bin/python3 main.py 16 | 17 | deploy: venv 18 | ./$(VENV)/bin/python3 -m pycob.deploy 19 | 20 | clean: 21 | rm -rf $(VENV) 22 | find . -type f -name '*.pyc' -delete 23 | 24 | .PHONY: all venv run clean deploy -------------------------------------------------------------------------------- /sample-apps/websockets/Makefile: -------------------------------------------------------------------------------- 1 | # define the name of the virtual environment directory 2 | VENV := venv 3 | 4 | # default target, when make executed without arguments 5 | all: venv run 6 | 7 | $(VENV)/bin/activate: requirements.txt 8 | python3 -m venv $(VENV) 9 | ./$(VENV)/bin/pip install -r requirements.txt 10 | 11 | # venv is a shortcut target 12 | venv: $(VENV)/bin/activate 13 | 14 | run: venv 15 | ./$(VENV)/bin/python3 main.py 16 | 17 | deploy: venv 18 | ./$(VENV)/bin/python3 -m pycob.deploy 19 | 20 | clean: 21 | rm -rf $(VENV) 22 | find . -type f -name '*.pyc' -delete 23 | 24 | .PHONY: all venv run clean deploy -------------------------------------------------------------------------------- /generator/gallery/table.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | import plotly.express as px 3 | 4 | df = px.data.gapminder().query("country=='Canada'") 5 | 6 | page = pv.Page("Table Example") 7 | 8 | page.add_header("Table Example") 9 | 10 | actions = [ 11 | # This button uses the year for the row as a parameter in the URL 12 | pv.Rowaction("Edit", "#/edit?year={year}", "edit", open_in_new_window=False), 13 | # This button uses the country for the row as a parameter in the URL 14 | pv.Rowaction("{iso_alpha}", "#/country?year={year}&country={country}", open_in_new_window=False) 15 | ] 16 | 17 | page.add_pandastable(df, action_buttons=actions) 18 | -------------------------------------------------------------------------------- /generator/gallery/form.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | 3 | page = pv.Page("Form Example") 4 | 5 | 6 | # Here we put the form inside a card so it is easier to see. 7 | with page.add_card() as card: 8 | card.add_header("Form Example") 9 | 10 | with card.add_form() as form: 11 | form.add_formtext(label="Name", name="name", placeholder="Enter your name") 12 | form.add_formemail(label="Email", name="email", placeholder="Enter your email") 13 | form.add_formpassword(label="Password", name="password", placeholder="Enter your password") 14 | form.add_formtextarea(label="Message", name="message", placeholder="Enter your message") 15 | form.add_formsubmit(label="Send") 16 | -------------------------------------------------------------------------------- /generator/docs/gallery.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import navbar, footer, marketing_banner, gallery_grid, all_layouts 3 | 4 | page = pv.Page("Gallery", navbar=navbar, footer=None) 5 | page.add_header("Gallery") 6 | 7 | page.add_text("Here are some examples of what you can create with PyVibe.") 8 | 9 | page.add_component(gallery_grid(all_layouts)) 10 | 11 | page.add_text("PyVibe was spun out of Pycob. We are in the process of transitioning Pycob apps to PyVibe.") 12 | page.add_link("See additional examples on Pycob", "https://www.pycob.com/gallery?tag=Featured") 13 | page.add_text("Note: Some of the examples on Pycob are not yet compatible with PyVibe but should give you some examples of the layout possibilities.") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zain Hoda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /generator/screenshot.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | 4 | # Get the current directory 5 | import os 6 | dir_path = os.path.dirname(os.path.realpath(__file__)) 7 | print("dir_path = ", dir_path) 8 | 9 | # Remove the last folder from the path 10 | dir_path = os.path.dirname(dir_path) 11 | print("dir_path = ", dir_path) 12 | 13 | global driver 14 | driver = None 15 | 16 | def take_screenshot(name: str): 17 | global driver 18 | if driver is None: 19 | options = Options() 20 | # options.add_argument("--headless") 21 | options.add_argument("window-size=1024,768") 22 | options.add_argument("--hide-scrollbars") 23 | # Here Chrome will be used 24 | driver = webdriver.Chrome('/usr/local/bin/chromedriver', options=options) 25 | 26 | # URL of website 27 | url = "file://" + dir_path + "/docs/gallery/" + name + ".html" 28 | 29 | # Opening the website 30 | driver.get(url) 31 | driver.save_screenshot(f"docs/img/{name}.png") 32 | 33 | # Get all the html files in the dir_path + "/docs/gallery" folder 34 | import glob 35 | files = glob.glob(dir_path + "/docs/gallery/*.html") 36 | for file in files: 37 | # Get the name of the file 38 | name = os.path.basename(file) 39 | # Remove the .html extension 40 | name = os.path.splitext(name)[0] 41 | print("screenshotting = ", name) 42 | take_screenshot(name) -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # define the name of the virtual environment directory 2 | VENV := venv 3 | 4 | all: venv docs/index.html server 5 | 6 | venv: $(VENV)/bin/activate 7 | 8 | flask: 9 | ./$(VENV)/bin/pip install flask 10 | ./$(VENV)/bin/python3 integration_tests/flask_test.py 11 | 12 | docs/index.html: src/pyvibe/__init.py___ generator/generate.py 13 | ./$(VENV)/bin/pip install pandas 14 | ./$(VENV)/bin/pip install plotly 15 | ./$(VENV)/bin/python3 generator/generate.py 16 | 17 | # pdoc: src/pyvibe/__init.py___ 18 | # ./$(VENV)/bin/pip install pdoc 19 | # ./$(VENV)/bin/pdoc pyvibe --logo https://cdn.pycob.com/pycob_hex.png --logo-link https://www.pyvibe.com --no-show-source -e pycob=https://github.com/pycob/pyvibe/tree/main/src/pyvibe/ -n --docformat google -o docs/pdoc/ 20 | 21 | # Build init.py when generate.swift changes 22 | src/pyvibe/__init.py___: generator/generate.swift 23 | swift generator/generate.swift 24 | 25 | # Rebuild the virtual environment when init.py changes 26 | $(VENV)/bin/activate: src/pyvibe/__init.py___ 27 | python3 -m venv $(VENV) 28 | ./$(VENV)/bin/pip install --upgrade pip 29 | ./$(VENV)/bin/pip install . 30 | 31 | screenshot: 32 | ./$(VENV)/bin/pip install selenium 33 | sudo ./$(VENV)/bin/python3 generator/screenshot.py 34 | 35 | server: 36 | open http://localhost:8000 37 | ./$(VENV)/bin/python -m http.server -d docs 38 | 39 | clean: 40 | rm -rf $(VENV) 41 | find . -type f -name '*.pyc' -delete 42 | 43 | .PHONY: all clean flask screenshot server 44 | -------------------------------------------------------------------------------- /generator/docs/index.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import footer, marketing_banner, gallery_grid, featured_layouts 3 | 4 | page = pv.Page("PyVibe: Easily create styled web pages with Python", navbar=None, footer=footer, image="https://www.pyvibe.com/img/social.png") 5 | 6 | container_outer = page.add_container(grid_columns=2) 7 | 8 | container_inner1 = container_outer.add_container(classes="text-center") 9 | container_inner1.add_header("PyVibe", size=9) 10 | container_inner1.add_image("https://cdn.pycob.com/pyvibe.svg", 'logo', classes='w-1/2 mx-auto') 11 | container_inner1.add_header("Easily create styled web pages with Python") 12 | container_inner1.add_link("Learn more", "about.html") 13 | 14 | container_inner2 = container_outer.add_container() 15 | container_inner2.add_code("""import pyvibe as pv 16 | 17 | page = pv.Page() 18 | 19 | page.add_header("Welcome to PyVibe!") 20 | 21 | page.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 22 | """, prefix="", header="Example") 23 | 24 | with container_inner2.add_card() as card: 25 | card.add_header("Welcome to PyVibe!") 26 | card.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 27 | 28 | page.add_header("Examples", 2) 29 | page.add_component(gallery_grid(featured_layouts)) 30 | 31 | page.add_html(marketing_banner) -------------------------------------------------------------------------------- /generator/docs/flask.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import navbar, footer, marketing_banner 3 | 4 | page = pv.Page("Flask", navbar=navbar, footer=footer) 5 | 6 | page.add_header("Flask") 7 | 8 | page.add_header("Simple Example", size=4) 9 | page.add_text("PyVibe can be used with Flask. This is a simple example of how to use PyVibe with Flask.") 10 | 11 | page.add_text("First, create a new file called app.py and add the following code:") 12 | 13 | page.add_code("""from flask import Flask 14 | import pyvibe as pv 15 | 16 | app = Flask(__name__) 17 | 18 | @app.route('/') 19 | def index(): 20 | page = pv.Page('Home') 21 | page.add_header('Hello World') 22 | return page.to_html() 23 | 24 | if __name__ == '__main__': 25 | app.run(debug=True) 26 | """, prefix="") 27 | 28 | page.add_text("Then, run the following command in your terminal:") 29 | page.add_code("python app.py", prefix="%") 30 | 31 | page.add_section("extended-example", "Extended Example") 32 | page.add_header("Extended Example", size=4) 33 | 34 | page.add_image("https://storage.googleapis.com/img.pycob.com/screenshot/pypi-analytics.png", "PyPi Analytics", classes="shadow-lg w-full md:w-2/5") 35 | page.add_link("Live App", "https://pypi-analytics.pycob.app") 36 | page.add_link("Source Code on GitHub", "https://github.com/pycob/pyvibe/blob/main/sample-apps/pypi-analytics/main.py") 37 | 38 | page.add_emgithub("https://github.com/pycob/pyvibe/blob/main/sample-apps/pypi-analytics/main.py") 39 | 40 | page.add_html(marketing_banner) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PyVibe 2 | 3 | ## Architecture 4 | The [`generator`](generator) directory contains the source assets. Everything else is generated from there. 5 | 6 | ```mermaid 7 | --- 8 | title: Code Generation Flow 9 | --- 10 | flowchart TD 11 | advanced(generator/includes/advanced.py.txt) --> generate.swift[[generator/generate.swift]] 12 | generate.swift --> spec(spec/spec.json) 13 | generate.swift --> init(src/__init__.py) 14 | spec --> generate.py[[generator/generate.py]] 15 | generate.py --> docs_html(docs/*.html) 16 | generate.py --> gallery_html(docs/gallery/*.html) 17 | gallery_html --> screenshot.py[[screenshot.py]] 18 | screenshot.py --> img(img/*.png) 19 | ``` 20 | 21 | Due to the architecture above, pull requests should be submitted only to files in the `/generator` subdirectory since everything else is generated from those files 22 | 23 | ## Why Code Generation? 24 | We want to minimize the need to have to search through documentation. Our goal is to be able to rely on autocomplete. To that end, we want to be able to attach `.add_*` methods to all appropriate components so that the list of available components in the current context is available. In order to minimize copy/paste, it's easiest to just generate the library code from a central definition. 25 | 26 | ## Why Swift? 27 | Very strongly typed languages with exhaustive enum checking makes code generation a lot more robust. Of the languages that would suit this criteria, Swift is probably the most readable for Python developers. -------------------------------------------------------------------------------- /generator/generate.py: -------------------------------------------------------------------------------- 1 | print("Generating from Python...") 2 | import pyvibe as pv 3 | from os.path import dirname, basename, isfile, join 4 | import glob 5 | from docs.common.components import navbar, footer 6 | 7 | modules = glob.glob(join(dirname(__file__)+"/docs", "*.py")) 8 | files = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] 9 | 10 | for file in files: 11 | print("Importing file: " + file) 12 | exec(f"from docs import {file}") 13 | 14 | # exec(f"{file}.page.add_header('Python Source')") 15 | # exec(f"{file}.page.add_text('This is the source code for the current page.')") 16 | # exec(f"{file}.page.add_emgithub('https://github.com/pycob/pyvibe/blob/main/generator/docs/{file}.py')") 17 | # exec(f"{file}.page.add_link('View Source', 'https://github.com/pycob/pyvibe/blob/main/generator/docs/{file}.py')") 18 | 19 | print(f"Writing to {file}.html") 20 | exec(f"with open('docs/{file}.html', 'w') as f: f.write({file}.page.to_html())") 21 | 22 | gallery_modules = glob.glob(join(dirname(__file__)+"/gallery", "*.py")) 23 | gallery_files = [ basename(f)[:-3] for f in gallery_modules if isfile(f) and not f.endswith('__init__.py')] 24 | 25 | for file in gallery_files: 26 | print("Importing file: " + file) 27 | exec(f"from gallery import {file}") 28 | 29 | exec(f"{file}.page.add_header('Python Source')") 30 | exec(f"{file}.page.add_text('This is the source code for the current page.')") 31 | exec(f"{file}.page.add_emgithub('https://github.com/pycob/pyvibe/blob/main/generator/gallery/{file}.py')") 32 | exec(f"{file}.page.add_link('View Source', 'https://github.com/pycob/pyvibe/blob/main/generator/gallery/{file}.py')") 33 | 34 | exec(f'pg = pv.Page({file}.page.title, navbar=navbar, components={file}.page.components)') 35 | 36 | print(f"Writing to {file}.html") 37 | exec(f"with open('docs/gallery/{file}.html', 'w') as f: f.write(pg.to_html())") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .DS_Store 131 | **/*api_key* 132 | **/venv 133 | **/tmp 134 | -------------------------------------------------------------------------------- /generator/docs/about.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import navbar, footer, marketing_banner, gallery_grid, featured_layouts 3 | 4 | page = pv.Page("PyVibe", navbar=navbar, footer=footer, image="./img/social.png") 5 | 6 | page.add_header("Easily create styled web pages with Python") 7 | 8 | page.add_code("""import pyvibe as pv 9 | 10 | page = pv.Page() 11 | 12 | page.add_header("Welcome to PyVibe!") 13 | 14 | page.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 15 | """, prefix="", header="Example") 16 | 17 | with page.add_card() as card: 18 | card.add_header("Welcome to PyVibe!") 19 | card.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 20 | 21 | page.add_link("See All Components", "component_reference.html") 22 | page.add_link("Interactive Playground", "playground.html") 23 | page.add_text("") 24 | 25 | page.add_header("What is PyVibe?", 3) 26 | page.add_text("PyVibe is a Python library for creating web pages. It is designed to be a quick way for Python developers to build front-ends.") 27 | page.add_text("PyVibe uses a component-based approach to building web pages. This means that you can create a page by combining components together.") 28 | 29 | page.add_header("How do I use PyVibe?", 3) 30 | page.add_text("PyVibe is a Python library that simplifies UI development for web apps by providing semantic Python components that compile into HTML and can be used with any web framework.") 31 | page.add_text("Fundamentally, PyVibe returns an HTML string that can be used with:") 32 | 33 | with page.add_list() as list: 34 | list.add_listitem("Static Pages: Like the one you're viewing now", is_checked=True) 35 | list.add_listitem("Flask: Inside a Flask function", is_checked=True) 36 | list.add_listitem("Pyodide: For dynamic client-side rendered pages", is_checked=True) 37 | 38 | page.add_text("") 39 | 40 | page.add_header("What can you build with PyVibe?", 3) 41 | page.add_component(gallery_grid(featured_layouts)) 42 | page.add_link("See more examples", "/gallery.html") 43 | 44 | page.add_header("Designed for Autocomplete", 3) 45 | page.add_text("PyVibe is designed to be used with autocomplete. This means that you can type page.add_ and autocomplete will show you all the components that you can add to your page along with documentation about the component.") 46 | page.add_html('Autocomplete') 47 | 48 | page.add_header("Themes", 3) 49 | page.add_text('PyVibe is meant to be a generic framework. While the default theme uses Flowbite, which are components that use TailwindCSS, we envision including many themes and CSS frameworks in the future.') 50 | 51 | page.add_header("How does PyVibe compare to Streamlit, Plotly Dash, Pynecone, Anvil, NiceGUI, etc?", 3) 52 | page.add_text("PyVibe is not a web server -- it produces styled HTML that can be used with any web server.") 53 | 54 | page.add_html(marketing_banner) -------------------------------------------------------------------------------- /sample-apps/websockets/main.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from flask import Flask, render_template 3 | from flask_sock import Sock 4 | 5 | app = Flask(__name__) 6 | sock = Sock(app) 7 | 8 | class WebSocketReceiverComponent(pv.Component): 9 | def __init__(self, path_to_websocket: str): 10 | self.path_to_websocket = path_to_websocket 11 | 12 | def to_html(self): 13 | return """ 14 |
15 | 32 | """ 33 | 34 | class WebSocketSenderComponent(pv.Component): 35 | def __init__(self, path_to_websocket: str): 36 | self.path_to_websocket = path_to_websocket 37 | 38 | def to_html(self): 39 | return """ 40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 | 55 | """ 56 | 57 | @app.route('/') 58 | def index(): 59 | page = pv.Page('Websocket Test', description='WebSocket proof of concept using Flask-Sock and PyVibe') 60 | 61 | page.add_header("Websocket Test") 62 | 63 | with page.add_container(grid_columns=2) as container: 64 | with container.add_card() as card: 65 | card.add_header('Send') 66 | card.add_component(WebSocketSenderComponent('/echo')) 67 | 68 | with container.add_card() as card: 69 | card.add_header('Receive') 70 | card.add_component(WebSocketReceiverComponent('/echo')) 71 | 72 | page.add_header("Source Code") 73 | page.add_emgithub("https://github.com/pycob/pyvibe/blob/main/sample-apps/websockets/main.py") 74 | 75 | return page.to_html() 76 | 77 | from threading import Timer 78 | 79 | @sock.route('/echo') 80 | def echo(sock): 81 | while True: 82 | data = sock.receive() 83 | 84 | alert_component = pv.AlertComponent(data, 'Received') 85 | 86 | sock.send(alert_component.to_html()) 87 | 88 | if __name__ == '__main__': 89 | app.run(debug=True) -------------------------------------------------------------------------------- /sample-apps/pypi-analytics/main.py: -------------------------------------------------------------------------------- 1 | # INITIALIZATION 2 | from flask import Flask, request 3 | import pyvibe as pv 4 | 5 | import pycob as cob 6 | import plotly.express as px 7 | 8 | app = Flask(__name__) 9 | pypi_projects_by_month = cob.fetch_pickle('pypi_projects_by_month.pkl') 10 | 11 | # PAGE FUNCTIONS 12 | @app.route('/') 13 | def home() -> str: 14 | page = pv.Page("PyPi Analytics") 15 | page.add_header("PyPi Analytics") 16 | page.add_text("PyPi analytics enables Python engineers to identify usage trends with Python packages as an input for picking the right package") 17 | page.add_link("See the source code for this app", "/view_source") 18 | page.add_text("") 19 | 20 | top_projects = pypi_projects_by_month.groupby('pypi_project').sum().sort_values('avg_downloads_per_day', ascending=False).reset_index().head(50) 21 | 22 | action_buttons = [ 23 | cob.Rowaction(label="Analytics", url="/project_detail?project_name={pypi_project}", open_in_new_window=True), 24 | cob.Rowaction(label="Project", url="https://pypi.org/project/{pypi_project}", open_in_new_window=True), 25 | ] 26 | 27 | with page.add_card() as card: 28 | card.add_header("Analyze a Project") 29 | with card.add_form(action="/project_detail") as form: 30 | form.add_formtext(name="project_name", label="Project Name", placeholder="Enter a project name") 31 | form.add_formsubmit("Analyze") 32 | 33 | page.add_text("") 34 | 35 | page.add_header("Top 50 Projects by Downloads", size=4) 36 | page.add_pandastable(top_projects, action_buttons=action_buttons) 37 | 38 | return page.to_html() 39 | 40 | @app.route('/project_detail') 41 | def project_detail() -> str: 42 | page = pv.Page("PyPi Analytics") 43 | 44 | project_name = request.args['project_name'] 45 | if 'compare_to' in request.args: 46 | compare_to = request.args['compare_to'] 47 | subtitle = request.args['compare_to'] 48 | else: 49 | compare_to = None 50 | subtitle = None 51 | 52 | page.add_header(f"PyPi Analytics for {project_name}") 53 | 54 | if not subtitle: 55 | with page.add_form(action="/project_detail") as form: 56 | form.add_formhidden(name="project_name", value=project_name) 57 | form.add_formhidden(name="compare_to", value=compare_to if compare_to else "") 58 | form.add_formtext(name="subtitle", label="Subtitle", placeholder="Enter a subtitle") 59 | form.add_formsubmit("Update Subtitle") 60 | else: 61 | page.add_header(f"{subtitle}", size=4) 62 | 63 | if compare_to: 64 | project_detail = pypi_projects_by_month[pypi_projects_by_month['pypi_project'].isin([project_name, compare_to])] 65 | else: 66 | project_detail = pypi_projects_by_month[pypi_projects_by_month['pypi_project'] == project_name] 67 | 68 | fig = px.line(project_detail.sort_values(["month", "pypi_project"]), x="month", y="avg_downloads_per_day", line_group="pypi_project", color="pypi_project") 69 | 70 | page.add_plotlyfigure(fig) 71 | 72 | if not compare_to: 73 | with page.add_card() as card: 74 | with card.add_form(action="/project_detail") as form: 75 | form.add_formhidden(name="project_name", value=project_name) 76 | form.add_formtext(label="Compare To", name="compare_to", placeholder="Enter a project name") 77 | form.add_formsubmit("Analyze") 78 | 79 | return page.to_html() 80 | 81 | @app.route('/view_source') 82 | def view_source() -> str: 83 | page = pv.Page("View Source") 84 | page.add_header("View Source") 85 | 86 | # Read the source code 87 | with open(__file__, 'r') as f: 88 | source = f.read() 89 | 90 | # Add the source code to the page 91 | page.add_codeeditor(source, language="python") 92 | 93 | return page.to_html() 94 | 95 | # RUN APP 96 | if __name__ == '__main__': 97 | app.run(debug=True) 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyVibe 2 | 3 | https://www.pyvibe.com/ | [Examples](https://www.pyvibe.com/gallery.html) | [Use With Flask](https://www.pyvibe.com/flask.html) | [Live Playground](https://www.pyvibe.com/playground.html) | [Component Reference](https://www.pyvibe.com/component_reference.html) | 4 | 5 | ![PyVibe logo with sample code](docs/img/social.png) 6 | 7 | ## Easily create styled web pages with Python 8 | 9 | 10 | ```python 11 | import pyvibe as pv 12 | 13 | page = pv.Page() 14 | 15 | page.add_header("Welcome to PyVibe!") 16 | 17 | page.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 18 | ``` 19 | 20 | [See all components](https://www.pyvibe.com/component_reference.html) 21 | 22 | [Interactive playground](https://www.pyvibe.com/playground.html) 23 | 24 | ## What is PyVibe? 25 | 26 | PyVibe is a Python library for creating web pages. It is designed to be a quick way for Python developers to build front-ends. 27 | 28 | PyVibe uses a component-based approach to building web pages. This means that you can create a page by combining components together. 29 | 30 | ## How do I use PyVibe? 31 | 32 | PyVibe is a Python library that simplifies UI development for web apps by providing semantic Python components that compile into HTML and can be used with any web framework. 33 | 34 | Fundamentally, PyVibe returns an HTML string that can be used with: 35 | 36 | - [Static Pages](https://github.com/pycob/pyvibe/blob/main/generator/docs/about.py): Using `.to_html()` 37 | - [Flask](https://www.pyvibe.com/flask.html): Inside a Flask function 38 | - [Pyodide](https://github.com/pycob/pyvibe/blob/main/generator/docs/playground.py#L124-L151): For dynamic client-side rendered pages (experimental) 39 | 40 | ## What can you build with PyVibe? 41 | 42 | 43 | 44 | 45 | 46 | [More Examples](https://www.pyvibe.com/gallery.html) 47 | 48 | ## Designed for Autocomplete 49 | PyVibe is designed to be used with autocomplete. This means that you can type `page.add_` and autocomplete will show you all the components that you can add to your page along with documentation about the component. 50 | 51 | ![Example showing how autocomplete works](docs/img/autocomplete.png) 52 | 53 | ## Themes 54 | PyVibe is meant to be a generic framework. While the default theme uses [Flowbite](https://flowbite.com/), which are components that use [TailwindCSS](https://tailwindcss.com/), we envision including many themes and CSS frameworks in the future. 55 | 56 | ## How does PyVibe compare to Streamlit, Plotly Dash, Pynecone, Anvil, NiceGUI, etc? 57 | PyVibe is not a web server -- it produces styled HTML that can be used with any web server. 58 | 59 | ## Getting Started 60 | - To get started with PyVibe, simply install the library using pip: 61 | 62 | ```bash 63 | pip install pyvibe 64 | ``` 65 | Once installed, you can begin creating UI components by creating a new Page object and adding components to it using the `.add_*` methods. You can then return the page as HTML by calling `page.to_html()`. 66 | 67 | For example, to create a new page with a header and a paragraph of text, you could use the following code: 68 | 69 | ```python 70 | import pyvibe as pv 71 | 72 | page = pv.Page() 73 | page.add_header("Welcome to PyVibe!") 74 | page.add_text("PyVibe is a Python library for creating UI components for web apps without the need to write HTML code.") 75 | print(page.to_html()) 76 | ``` 77 | 78 | This will output the following HTML (simplified for readability but it also includes the rest of the HTML document including a navbar, footer, head tag, etc): 79 | 80 | ```html 81 |
82 |

Welcome to PyVibe!

83 |

PyVibe is a Python library for creating UI components for web apps without the need to write HTML code.

84 |
85 | ``` 86 | 87 | ## Contributions 88 | PyVibe is an open-source library, and [contributions](CONTRIBUTING.md) are welcome! If you have an idea for a new feature or theme, or if you find a bug and want to submit a fix, feel free to open a pull request. 89 | 90 | Thanks for considering PyVibe! -------------------------------------------------------------------------------- /generator/docs/component_reference.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | import os 3 | import json 4 | import pandas as pd 5 | from .common.components import navbar, footer, marketing_banner 6 | 7 | def argument_value_with_quotes(argument_type, argument_value) -> str: 8 | if argument_value is None: 9 | return 'None' 10 | 11 | if argument_type == 'Untyped': 12 | return argument_value 13 | 14 | if isinstance(argument_value, str): 15 | return "\'" + argument_value.replace("'", "") + "\'" 16 | 17 | return str(argument_value) 18 | 19 | def get_argument_type(name, arguments) -> str: 20 | for arg in arguments: 21 | if name == arg['name']: 22 | return arg['type'] 23 | 24 | return None 25 | 26 | def example_to_arguments(example, arguments) -> str: 27 | # print(example) 28 | if example is None: 29 | return '' 30 | 31 | return ', '.join(map(lambda x: x['argumentName'] + ' = ' + argument_value_with_quotes( get_argument_type(x['argumentName'], arguments) , x['argumentValue']), example)) 32 | 33 | def example_to_pyvibe_code(element_type, example, attachableTo, arguments) -> str: 34 | return attachableTo + '.add_' + element_type + '(' + example_to_arguments(example, arguments) + ')' 35 | 36 | 37 | # Read spec.json into a dictionary 38 | with open(os.path.join("./spec/", 'spec.json')) as f: 39 | spec = json.load(f) 40 | 41 | sidebar = pv.Sidebar() 42 | 43 | page = pv.Page('Component Reference', navbar=navbar, footer=footer, sidebar=sidebar) 44 | 45 | category_order = [ 46 | 'Page', 47 | 'Basic HTML', 48 | 'Layout', 49 | 'Form', 50 | 'Table', 51 | 'Advanced', 52 | 'Advanced Layout', 53 | 'Internal', 54 | ] 55 | 56 | categories = {} 57 | 58 | for element in spec: 59 | category = element['category'] 60 | 61 | if category not in categories: 62 | categories[category] = [] 63 | 64 | categories[category].append(element) 65 | 66 | for category in category_order: 67 | page.add_section(category, category) 68 | sidebar_category = sidebar.add_sidebarcategory(category) 69 | page.add_header(category, 5) 70 | 71 | for element in categories[category]: 72 | page.add_section(element['elementType'], element['name'], level=2) 73 | sidebar_category.add_sidebarlink(element['name'], "#"+element['elementType']) 74 | if element['elementType'] != 'page': 75 | if len(element['attachableTo']) > 0: 76 | page.add_header(element['name'] + f" (.add_{element['elementType']})", 4) 77 | else: 78 | page.add_header(element['name'] + f" ({element['elementType']} = {element['name']}(...))", 4) 79 | else: 80 | page.add_header(element['name'], 4) 81 | page.add_text(element['description']) 82 | 83 | if len(element['attachableTo']) > 0: 84 | page.add_header("Use With", 3) 85 | 86 | for attachableTo in element['attachableTo']: 87 | page.add_link(attachableTo + f".add_{element['elementType']}", "#"+attachableTo) 88 | 89 | page.add_text("") 90 | page.add_header('Input', 2) 91 | 92 | table = pv.RawtableComponent() 93 | 94 | tablehead = pv.TableheadComponent() 95 | tablehead.add_tablecellheader("Name") 96 | tablehead.add_tablecellheader("Type") 97 | tablehead.add_tablecellheader("Default Value") 98 | tablehead.add_tablecellheader("Description") 99 | 100 | table.add_component(tablehead) 101 | 102 | tablebody = pv.TablebodyComponent() 103 | 104 | for argument in element['arguments']: 105 | row = pv.TablerowComponent() 106 | 107 | row.add_tablecellheader(argument['name']) 108 | row.add_tablecell(argument['type']) 109 | if 'defaultValue' in argument: 110 | if argument['type'] == "String": 111 | row.add_tablecell("'" + argument['defaultValue'] + "'") 112 | else: 113 | row.add_tablecell(argument['defaultValue']) 114 | else: 115 | row.add_tablecell("") 116 | row.add_tablecell(argument['description']) 117 | 118 | tablebody.add_component(row) 119 | 120 | table.add_component(tablebody) 121 | 122 | if len(element['arguments']) > 0: 123 | page.add_component(table) 124 | else: 125 | page.add_text("No Inputs") 126 | # for argument in element['arguments']: 127 | # if 'defaultValue' in argument: 128 | # page.add_html('

' + argument['name'] +': ' + argument['type'] + '. Default: ' + argument['defaultValue'] + '. ' + argument['description'] + '

') 129 | # else: 130 | # page.add_html('

' + argument['name'] +': ' + argument['type'] + '. ' + argument['description'] + '

') 131 | 132 | if element['category'] != 'Internal': 133 | if len(element['exampleCode']) > 0: 134 | page.add_header('Example', 2) 135 | 136 | for exampleWithSetup in element['exampleCode']: 137 | example = exampleWithSetup['arguments'] 138 | 139 | if 'card' in element['attachableTo']: 140 | attachableTo = 'card' 141 | page.add_code(example_to_pyvibe_code(element['elementType'], example, attachableTo, element['arguments']).replace('<', '<').replace('>', '>')) 142 | card = pv.CardComponent() 143 | if callable(getattr(card, "add_"+ element['elementType'], None)): 144 | setup = "" 145 | 146 | if 'setup' in exampleWithSetup: 147 | setup = '\n'.join(exampleWithSetup['setup']) + '\n' 148 | 149 | expression = setup + 'card.add_' + element['elementType'] + '(' + example_to_arguments(example, element['arguments']) + ')' 150 | 151 | print("Executing = ", expression) 152 | try: 153 | exec(expression) 154 | except Exception as err: 155 | print("Error = ", err) 156 | 157 | page.add_component(card) 158 | elif 'page' in element['attachableTo']: 159 | attachableTo = 'page' 160 | page.add_code(example_to_pyvibe_code(element['elementType'], example, attachableTo, element['arguments']).replace('<', '<').replace('>', '>')) 161 | if callable(getattr(page, "add_"+ element['elementType'], None)): 162 | eval('page.add_' + element['elementType'] + '(' + example_to_arguments(example, element['arguments']) + ')') 163 | elif 'form' in element['attachableTo']: 164 | attachableTo = 'form' 165 | card = pv.CardComponent() 166 | 167 | form = pv.FormComponent(action="") 168 | 169 | page.add_code(example_to_pyvibe_code(element['elementType'], example, attachableTo, element['arguments']).replace('<', '<').replace('>', '>')) 170 | if callable(getattr(form, "add_"+ element['elementType'], None)): 171 | eval('form.add_' + element['elementType'] + '(' + example_to_arguments(example, element['arguments']) + ')') 172 | 173 | card.add_component(form) 174 | 175 | page.add_component(card) 176 | else: 177 | if len(element['attachableTo']) > 0: 178 | attachableTo = element['attachableTo'][0] 179 | page.add_code(example_to_pyvibe_code(element['elementType'], example, attachableTo, element['arguments']).replace('<', '<').replace('>', '>')) 180 | else: 181 | # TODO: Need a function that doesn't require an attachableTo (i.e. call page = Page(...) etc) 182 | pass 183 | 184 | 185 | page.add_divider() 186 | 187 | page.add_html(marketing_banner) -------------------------------------------------------------------------------- /generator/docs/common/components.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | 3 | navbar = pv.Navbar("PyVibe") 4 | navbar.add_navbarlink("Gallery", "/gallery.html") 5 | navbar.add_navbarlink("Flask", "/flask.html") 6 | navbar.add_navbarlink("Playground", "/playground.html") 7 | navbar.add_navbarlink("Components", "/component_reference.html") 8 | navbar.add_navbarlink(' GitHub', "https://github.com/pycob/pyvibe", classes="inline-flex items-center") 9 | 10 | footer = pv.Footer() 11 | with footer.add_footercategory("About") as category: 12 | category.add_footerlink("About PyVibe", "/about.html") 13 | category.add_footerlink("PyVibe on GitHub", "https://github.com/pycob/pyvibe") 14 | category.add_footerlink("PyVibe on PyPI", "https://pypi.org/project/pyvibe/") 15 | 16 | with footer.add_footercategory("Learn") as category: 17 | category.add_footerlink("Gallery", "/gallery.html") 18 | category.add_footerlink("Interactive Playground", "/playground.html") 19 | category.add_footerlink("Flask", "/flask.html") 20 | category.add_footerlink("Component Reference", "/component_reference.html") 21 | 22 | marketing_banner = """ 23 | 28 |
29 |
30 | 31 | Pycob Logo 32 | PyVibe 33 | 34 |

Easily create styled web pages with Python

35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Get Help on Discord 45 | 46 | 47 | 48 | 49 | Star on GitHub 50 | 54 |
55 |
56 | """ 57 | 58 | def gallery_item(name: str) -> pv.Component: 59 | card = pv.CardComponent() 60 | 61 | card.add_image(f"./img/{name}.png", name, classes="w-full shadow-lg") 62 | card.add_header(name.replace("_", " ").title(), size=3, classes="mt-1") 63 | card.add_link("Preview with Source Code", f"/gallery/{name}.html") 64 | 65 | return card 66 | 67 | def gallery_grid(names: list[str]) -> pv.Component: 68 | grid = pv.ContainerComponent(grid_columns=4) 69 | 70 | for name in names: 71 | grid.add_component(gallery_item(name)) 72 | 73 | return grid 74 | 75 | featured_layouts = [ 76 | "card", 77 | "form", 78 | "chart", 79 | "table", 80 | ] 81 | 82 | import os 83 | dir_path = os.path.dirname(os.path.realpath(__file__)) 84 | print("dir_path = ", dir_path) 85 | 86 | all_layouts = [] 87 | 88 | # Add all files in the ../../gallery directory 89 | for file in os.listdir(os.path.dirname(os.path.dirname(dir_path)) + "/gallery/"): 90 | if file.endswith(".py"): 91 | all_layouts.append(file[:-3]) 92 | -------------------------------------------------------------------------------- /generator/docs/playground.py: -------------------------------------------------------------------------------- 1 | import pyvibe as pv 2 | from .common.components import navbar, footer 3 | 4 | page = pv.Page('Interactive PyVibe Playground', navbar=navbar, footer=footer) 5 | 6 | page.add_header("Welcome to the PyVibe Playground!") 7 | page.add_text("This playground uses Pyodide to run Python code in the browser. You can use it to experiment with PyVibe and learn how to use it. Note that this is experimental and may not work in all browsers.") 8 | 9 | page.add_code("pip install pyvibe", header="Install PyVibe to run locally", prefix="%") 10 | 11 | page.add_html(""" 12 | 78 | 92 | 93 | """) 94 | 95 | page.add_codeeditor("""import pyvibe as pv 96 | 97 | page = pv.Page('Test Page') 98 | 99 | with page.add_card() as card: 100 | card.add_header("Hello World") 101 | card.add_text("This content was generated with Python (from the browser)!") 102 | 103 | """) 104 | 105 | page.add_html(""" 106 | 107 | 172 | """) 173 | 174 | page.add_html(""" 175 | 184 | """) 185 | 186 | page.add_html("
A preview will appear here once you click run.
") 187 | 188 | page.add_link("Learn how to serve from Flask", "flask.html") 189 | -------------------------------------------------------------------------------- /docs/hero.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Hero 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 102 |
103 | 104 |
105 |
106 |
107 |

PyVibe

108 | logo 109 |

Easily create styled web pages with Python

110 |

111 | 112 | Learn more 113 | 114 | 115 |

116 |
117 |
118 |
119 |
120 | 121 | 122 | 123 | Example 124 |
125 |
126 | 127 | import pyvibe as pv 128 | 129 | page = pv.Page() 130 | 131 | page.add_header("Welcome to PyVibe!") 132 | 133 | page.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 134 | 135 |
136 |
137 |
138 |
139 |

Welcome to PyVibe!

140 |

PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.

141 |
142 |
143 |
144 |
145 |
146 |
147 | 172 | 173 | -------------------------------------------------------------------------------- /docs/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Login 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 | 138 |
139 | 140 |
141 |

Login

142 |

You can create your own login forms or link to third-party auth providers

143 |
144 |
145 | 172 | 173 | -------------------------------------------------------------------------------- /generator/includes/advanced.py.txt: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote 2 | import re 3 | import json 4 | 5 | def advanced_add_pandastable(self, df, hide_fields, action_buttons): 6 | cols_to_show = [] 7 | show_actions = False 8 | 9 | if action_buttons is None: 10 | action_buttons = [] 11 | 12 | if len(__get_action_buttons_to_add(action_buttons)) > 0: 13 | show_actions = True 14 | 15 | for col in df.columns: 16 | if col not in hide_fields: 17 | cols_to_show.append(col) 18 | 19 | # Pandas dataframe to html 20 | html = '''
''' 21 | 22 | html += '''
''' 23 | 24 | html += '''''' 25 | 26 | # Pandas DataFrame columns to 27 | html += '''''' 28 | 29 | html += "" 30 | 31 | # Get df index name 32 | if df.index.name is not None: 33 | html += '''" 34 | 35 | if show_actions: 36 | html += '''''' 37 | 38 | for column in df.columns: 39 | if column in cols_to_show: 40 | html += '''" 41 | 42 | html += "" 43 | 44 | html += "" 45 | 46 | # Pandas DataFrame rows to html 47 | html += "" 48 | i = 0 49 | for index, row in df.iterrows(): 50 | record = row.to_dict() 51 | 52 | if i % 2 == 0: 53 | html += '''''' 54 | else: 55 | html += '''''' 56 | 57 | if show_actions: 58 | html += '''''' 73 | 74 | for column in df.columns: 75 | key = column 76 | if column in cols_to_show: 77 | action_button = __find_key_in_action_buttons(key, action_buttons) 78 | 79 | value = __format_python_object_for_json(record[key]) 80 | 81 | if action_button is not None: 82 | hydrated_label = action_button.label.format(**record) 83 | hydrated_url = action_button.url.format(**record) 84 | if action_button.open_in_new_window: 85 | html += """""" 86 | else: 87 | html += """""" 88 | else: 89 | html += '''" 90 | 91 | html += "" 92 | i += 1 93 | 94 | html += "" 95 | 96 | html += "
''' + df.index.name + "Actions''' + column + "
''' 59 | 60 | action_buttons_to_add = __get_action_buttons_to_add(action_buttons) 61 | 62 | for button_to_add in action_buttons_to_add: 63 | hydrated_label = button_to_add.label.format(**record) 64 | hydrated_url = button_to_add.url.format(**record) 65 | 66 | if button_to_add.open_in_new_window: 67 | html += """""" + hydrated_label + """""" 68 | else: 69 | html += """""" + hydrated_label + """""" 70 | 71 | 72 | html += '''""" + hydrated_label + """""" + hydrated_label + """''' + format_input(row[column]) + "
" 97 | 98 | html += "
" 99 | 100 | html += "
" 101 | 102 | self.components.append(HtmlComponent(html)) 103 | return self 104 | 105 | 106 | """ 107 | A function that formats input into a human-readable string: 108 | Input: An arbitrary type that could be string, integer, floating point, a numpy object, a pandas datetime, or something else 109 | Output: String 110 | 111 | Dates should be formatted using ISO-8601. Numbers below 10 should include 2 decimal places. Numbers between 10 and 100 should have 1 decimal place. Numbers between 100 and 1000 should have 0 decimal places. Numbers between 1000 and 1000000 should have 0 decimal places and be formatted with a comma for the thousands separator. Numbers between 1000000 and 1000000000 should be formatted as X.Y million. Numbers above 1000000000 should be formatted as X.Y billion 112 | """ 113 | 114 | 115 | def format_input(input): 116 | is_int = "int" in type(input).__name__ 117 | is_float = "float" in type(input).__name__ 118 | 119 | if is_float or is_int: 120 | if input < 10: 121 | if is_float: 122 | return '{:.2f}'.format(input) 123 | else: 124 | return string_format_with_more(str(input), 10) 125 | elif input < 100: 126 | if is_float: 127 | return '{:.1f}'.format(input) 128 | else: 129 | return string_format_with_more(str(input), 10) 130 | elif input < 1000: 131 | return '{:.0f}'.format(input) 132 | elif input < 1000000: 133 | return '{:,.0f}'.format(input) 134 | elif input < 1000000000: 135 | return '{:.1f} million'.format(input / 1000000) 136 | # Check if it's nan 137 | elif input != input: 138 | return "N/A" 139 | else: 140 | return '{:.1f} billion'.format(input / 1000000000) 141 | elif callable(getattr(input, "isoformat", None)): 142 | iso = input.isoformat() 143 | 144 | if "T00:00:00" in iso: 145 | return iso[0:10] 146 | 147 | return iso 148 | else: 149 | formatted_string = str(input) 150 | return string_format_with_more(formatted_string, 100) 151 | 152 | def string_format_with_more(text: str, max_length: int) -> str: 153 | if len(text) > max_length: 154 | return text[0:max_length] + f'... ' 155 | else: 156 | return text 157 | 158 | def __format_python_object_for_json(t): 159 | if callable(getattr(t, "isoformat", None)): 160 | iso = t.isoformat() 161 | 162 | if "T00:00:00" in iso: 163 | return iso[0:10] 164 | 165 | return iso 166 | 167 | return t 168 | 169 | def advanced_add_emgithub(self, url): 170 | quoted_url = quote(url) 171 | 172 | emgithub = ''' 173 | 174 | ''' 175 | 176 | self.components.append(HtmlComponent(emgithub)) 177 | 178 | def __format_column_header(x: str) -> str: 179 | # Insert undersore between camel case 180 | x = re.sub(r'(?<=[a-z])(?=[A-Z])', '_', x) 181 | 182 | # Capitalize the beginning of each word 183 | x = x.title() 184 | 185 | # Replace underscores with spaces 186 | x = x.replace('_', ' ') 187 | 188 | return x 189 | 190 | def __get_action_buttons_to_add(action_buttons): 191 | action_buttons_to_add = [] 192 | 193 | for action_button in action_buttons: 194 | # If the label is not of the form "{key}", then add it to the list of action buttons to add 195 | if action_button.label[0] != '{' or action_button.label[-1] != '}' and '{' not in action_button.label[1:-1]: 196 | action_buttons_to_add.append(action_button) 197 | 198 | return action_buttons_to_add 199 | 200 | def __find_key_in_action_buttons(key: str, action_buttons): 201 | for action_button in action_buttons: 202 | if action_button.label == '{' + key + '}' : 203 | return action_button 204 | 205 | return None 206 | 207 | def __replace_text_with_button(record: dict, action_buttons) -> dict: 208 | new_record = {} 209 | 210 | for key in record.keys(): 211 | action_button = __find_key_in_action_buttons(key, action_buttons) 212 | 213 | value = __format_python_object_for_json(record[key]) 214 | 215 | if action_button is not None: 216 | hydrated_label = action_button.label.format(**record) 217 | hydrated_url = action_button.url.format(**record) 218 | if action_button.open_in_new_window: 219 | new_record[key] = """""" + hydrated_label + """""" 220 | else: 221 | new_record[key] = """""" + hydrated_label + """""" 222 | else: 223 | new_record[key] = value 224 | 225 | action_buttons_html = '' 226 | 227 | action_buttons_to_add = __get_action_buttons_to_add(action_buttons) 228 | for button_to_add in action_buttons_to_add: 229 | hydrated_label = button_to_add.label.format(**record) 230 | hydrated_url = button_to_add.url.format(**record) 231 | 232 | if button_to_add.open_in_new_window: 233 | action_buttons_html += """""" + hydrated_label + """""" 234 | else: 235 | action_buttons_html += """""" + hydrated_label + """""" 236 | 237 | new_record['Actions'] = action_buttons_html 238 | 239 | return new_record 240 | 241 | def advanced_add_datagrid(page, dataframe, action_buttons): 242 | if action_buttons is None: 243 | action_buttons = [] 244 | 245 | cols = list(map(lambda x: {'headerName': __format_column_header(x), 'field': x} , dataframe.columns.to_list())) 246 | 247 | if len(__get_action_buttons_to_add(action_buttons)) > 0: 248 | cols.append({'headerName': 'Actions', 'field': 'Actions'}) 249 | 250 | datagridHtml = ''' 251 | 293 |
294 | 295 |
296 |
297 | ''' 298 | 299 | page.components.append(HtmlComponent(datagridHtml)) 300 | 301 | -------------------------------------------------------------------------------- /docs/gallery/card.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Card Example 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 |
138 |
139 | 140 |
141 |

Card Example

142 |
143 |
144 |

Card Header

145 |

This is a card. You can put most types of content in a card.

146 |

147 | 148 | Learn more 149 | 150 | 151 |

152 |
153 |
154 |

Python Source

155 |

This is the source code for the current page.

156 | 157 | 158 | 159 |

160 | 161 | View Source 162 | 163 | 164 |

165 |
166 |
167 | 178 | 179 | -------------------------------------------------------------------------------- /docs/gallery/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Form Example 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 | 138 |
139 | 140 |
141 |
142 |
143 |

Form Example

144 |
145 |
146 | 147 | 148 |
149 |
150 | 151 | 152 |
153 |
154 | 155 | 156 |
157 |
158 | 159 | 160 |
161 | 162 |
163 |
164 |
165 |

Python Source

166 |

This is the source code for the current page.

167 | 168 | 169 | 170 |

171 | 172 | View Source 173 | 174 | 175 |

176 |
177 |
178 | 189 | 190 | -------------------------------------------------------------------------------- /docs/gallery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Gallery 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 | 138 |
139 | 140 |
141 |

Gallery

142 |

Here are some examples of what you can create with PyVibe.

143 |
144 |
145 |
146 | grid 147 |

Grid

148 |

149 | 150 | Preview with Source Code 151 | 152 | 153 |

154 |
155 |
156 |
157 |
158 | chart 159 |

Chart

160 |

161 | 162 | Preview with Source Code 163 | 164 | 165 |

166 |
167 |
168 |
169 |
170 | card 171 |

Card

172 |

173 | 174 | Preview with Source Code 175 | 176 | 177 |

178 |
179 |
180 |
181 |
182 | form 183 |

Form

184 |

185 | 186 | Preview with Source Code 187 | 188 | 189 |

190 |
191 |
192 |
193 |
194 | table 195 |

Table

196 |

197 | 198 | Preview with Source Code 199 | 200 | 201 |

202 |
203 |
204 |
205 |

PyVibe was spun out of Pycob. We are in the process of transitioning Pycob apps to PyVibe.

206 |

207 | 208 | See additional examples on Pycob 209 | 210 | 211 |

212 |

Note: Some of the examples on Pycob are not yet compatible with PyVibe but should give you some examples of the layout possibilities.

213 |
214 |
215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PyVibe: Easily create styled web pages with Python 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 102 |
103 | 104 |
105 |
106 |
107 |

PyVibe

108 | logo 109 |

Easily create styled web pages with Python

110 |

111 | 112 | Learn more 113 | 114 | 115 |

116 |
117 |
118 |
119 |
120 | 121 | 122 | 123 | Example 124 |
125 |
126 | 127 | import pyvibe as pv 128 | 129 | page = pv.Page() 130 | 131 | page.add_header("Welcome to PyVibe!") 132 | 133 | page.add_text("PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.") 134 | 135 |
136 |
137 |
138 |
139 |

Welcome to PyVibe!

140 |

PyVibe is an open source Python library for creating UI components for web apps without the need to write HTML code.

141 |
142 |
143 |
144 |
145 |

Examples

146 |
147 |
148 |
149 | card 150 |

Card

151 |

152 | 153 | Preview with Source Code 154 | 155 | 156 |

157 |
158 |
159 |
160 |
161 | form 162 |

Form

163 |

164 | 165 | Preview with Source Code 166 | 167 | 168 |

169 |
170 |
171 |
172 |
173 | chart 174 |

Chart

175 |

176 | 177 | Preview with Source Code 178 | 179 | 180 |

181 |
182 |
183 |
184 |
185 | table 186 |

Table

187 |

188 | 189 | Preview with Source Code 190 | 191 | 192 |

193 |
194 |
195 |
196 | 197 | 202 |
203 |
204 | 205 | Pycob Logo 206 | PyVibe 207 | 208 |

Easily create styled web pages with Python

209 |
210 | 229 |
230 | 231 |
232 |
233 | 260 | 261 | -------------------------------------------------------------------------------- /docs/gallery/chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Chart Example 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 | 138 |
139 | 140 |
141 |

Chart Example

142 |
143 | 147 |

Python Source

148 |

This is the source code for the current page.

149 | 150 | 151 | 152 |

153 | 154 | View Source 155 | 156 | 157 |

158 |
159 |
160 | 171 | 172 | -------------------------------------------------------------------------------- /docs/flask.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Flask 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 72 | 99 | 100 | 101 | 111 | 138 |
139 | 140 |
141 |

Flask

142 |

Simple Example

143 |

PyVibe can be used with Flask. This is a simple example of how to use PyVibe with Flask.

144 |

First, create a new file called app.py and add the following code:

145 |
146 |
147 | 148 | 149 | 150 | 151 |
152 |
153 | 154 | from flask import Flask 155 | import pyvibe as pv 156 | 157 | app = Flask(__name__) 158 | 159 | @app.route('/') 160 | def index(): 161 | page = pv.Page('Home') 162 | page.add_header('Hello World') 163 | return page.to_html() 164 | 165 | if __name__ == '__main__': 166 | app.run(debug=True) 167 | 168 |
169 |
170 |

Then, run the following command in your terminal:

171 |
172 |
173 | 174 | 175 | 176 | 177 |
178 |
179 | % 180 | python app.py 181 |
182 |
183 | 184 |

Extended Example

185 | PyPi Analytics 186 |

187 | 188 | Live App 189 | 190 | 191 |

192 |

193 | 194 | Source Code on GitHub 195 | 196 | 197 |

198 | 199 | 200 | 201 | 202 | 207 |
208 |
209 | 210 | Pycob Logo 211 | PyVibe 212 | 213 |

Easily create styled web pages with Python

214 |
215 | 234 |
235 | 236 |
237 |
238 | 265 | 266 | --------------------------------------------------------------------------------