Python Form Generator

Live on Streamlit

From raw JSON to Python Pydantic Model to Streamlit Input Form :zap:

Bootstrapping more Streamlit apps from within a Streamlit app :recycle:

Generate well-typed and validated Python forms from basic JSON example.


Demo Screencast

Full Walkthrough


GOAL: Initialize git repo with necessary file scaffold:

  • The main streamlit entry app
  • requirements.txt: Pin versions of Python packages so deployment and development is repeatable
  • This documentation
  • .gitignore: Prevent secrets or large files or unecessary things from going into github
  • LICENSE: Apache 2.0 same as Streamlit Terms
mkdir python-form-generator
cd python-form-generator/
touch LICENSE .gitignore requirements.txt
git init
git checkout -b main
git remote add origin
git add .
git commit -m "skeleton"


We’ll plan on 4 main packages for this:

  • streamlit
  • pydantic
  • streamlit-pydantic
  • datamodel-code-generator
python -m venv venv
. ./venv/bin/activate
pip install streamlit-pydantic datamodel-code-generator
pip list | grep "streamlit\|pydantic\|datamodel-code"

Note: This lets streamlit-pydantic to choose which versions of streamlit and pydantic work for the latest release.

Saving the output to requirements.txt in the format:


Saving the output to requirements.txt allows new users / deploys to install pinned verions with something like the following:

pip install -r requirements.txt

git add requirements.txt
git commit -m "install"

Hello World

Adding the following to

import streamlit as st

def main() -> None:
    st.header("Python Form Generator")
    st.subheader("Enter your JSON and get a free Pydantic model + Streamlit Input Form using it!")
    json_input = st.text_area('JSON example', height=200)

if __name__ == "__main__":

Then run with streamlit to check if basic input / processing frontend works.

streamlit run
# ctrl + c to stop
git add
git commit -m "input hello world"

Model Generation

We’ll draw from 3 sources for the meat and potatoes of this app:


Expand import section of to accomodate Streamlit Pydantic, Datamodel code generator, and GenSON

(Datamodel code generator is intended as CLI tool, so we have to dig a bit into it)

import json

from pydantic import Json, BaseModel
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
from genson import SchemaBuilder
import streamlit as st
import streamlit_pydantic as sp

Upgraded Input

We’ll use Streamlit Pydantic to get out of the box validation of the JSON that the user provides. Plus we get extensibility via the FormModel if we want to add more configuration options!

Instead of a plain text_input from streamlit, we utilize sp.pydantic_form and provide our model (which only has 1 input field for now!)

After getting some input JSON from the User it will pass off to converting to a model.

class FormGeneratorModel(BaseModel):
    model_schema: Json

def main() -> None:
    st.header("Python Form Generator")
        "Enter your JSON and get a free Pydantic model + Streamlit Input Form using it!"
    data = sp.pydantic_form(key="json_input", model=FormGeneratorModel)
    if data:


Following in the footsteps of JSON to Pydantic and Datamodel Code Generator, we use GenSON to build a JSONSchema representation from raw JSON data, then dump that into the Datamodel Code Generator parser.

We’ll handle the same error case Datamodel code generator does, otherwise assume the happy path and display the results!

def show_generated_code(schema: Json) -> None:
    model_code = json_to_pydantic(schema)

    if not model_code:
        st.error("Models not found in the input data")

def json_to_pydantic(input_text: str) -> str:
    builder = SchemaBuilder()
    schema = builder.to_schema()
    parser = JsonSchemaParser(

    return parser.parse()


Trying out a simple entry (even simpler than the littlest fullstack app) such as the following:

{"body": ":tada:", "username": ":cat:"}

Produces expected result

from __future__ import annotations

from pydantic import BaseModel

class Model(BaseModel):
    body: str
    username: str

Time to ship it and generate a Form!

git add
git commit -m "model generation"


Alright, we’ve got a nice Pydantic model, time to generate a Streamlit Pydantic scaffold from it and provide a download link.

Update Show Model

We’ll allow the user to download just the model into a .py file and hide the generated model unless they want to see it.

def show_generated_code(schema: Json) -> None:
    model_code = json_to_pydantic(schema)

    if not model_code:
        st.error("Models not found in the input data")
        with st.expander("Original Converted Model"):
            st.code(model_code, language="python")
        st.download_button("Download Generated Model Only", data=model_code, file_name="", mime="text/plain")

Inject Generated Code

We only need 2 features to really bootstrap these forms:

  • Added imports
  • Retrieve form data in Streamlit app

I’ll add a main() method to keep scope contained, but the Streamlit execution model embraces all Python scripting


def main() -> None:
    st.header("Model Form Submission")
    data = sp.pydantic_form(key="my_model", model=Model)
    if data:

if __name__ == "__main__":

def show_generated_form(model_code: str) -> None:
    code_lines = model_code.split('\n')
    code_lines.insert(2, "import streamlit_pydantic as sp")
    code_lines.insert(2, "import streamlit as st")

    code_lines.insert(-1, MAIN_TEMPLATE)

    full_code = '\n'.join(code_lines)

    st.subheader("Generated Streamlit Pydantic App")
    st.caption("Download it and run with `streamlit run`")
    st.download_button("Download Generated Form!", data=full_code, file_name="", mime="text/plain")
    st.code(full_code, language="python")


Testing out the download and run on our simple model yields great results!

git add
git commit -m "app generation"

Wrap Up

That’s it for the basic idea!

Next steps would be allowing configuration options such as “all Optional” and snake casing akin to JSON to Pydantic. Also more input forms, as Datamodel code generator handles OpenAPI, CSV data, and more.

git add
git commit -m "lgtm!"
git push origin main