Skip to content

Commit

Permalink
use script to generate streamlit apps
Browse files Browse the repository at this point in the history
  • Loading branch information
oceans404 committed Sep 17, 2024
1 parent 315fada commit b8c03dc
Show file tree
Hide file tree
Showing 18 changed files with 93 additions and 261 deletions.
1 change: 1 addition & 0 deletions generate-streamlit-app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys

STREAMLIT_APP_TEMPLATE = '''# This file was automatically generated by the generate-streamlit-app script.
# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_{program_name}.py`
import sys
import os
Expand Down
56 changes: 35 additions & 21 deletions streamlit_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,39 @@ def parse_nada_json(json_data):

return input_info, output_info

import streamlit as st

def create_party_inputs(input_info, input_values):
party_names = sorted(set(info['party'] for info in input_info.values()))
updated_input_values = input_values.copy()

if len(party_names) > 1:
# Create two columns if there's more than one party
columns = st.columns(2)
else:
# Create a single column if there's only one party
columns = [st.columns(1)[0]]

# Distribute parties between the columns
for i, party_name in enumerate(party_names):
with columns[i % len(columns)]:
st.subheader(f"{party_name}'s Inputs")
for input_name, value in input_values.items():
if input_info[input_name]['party'] == party_name:
input_type = input_info[input_name]['type']
if input_type == 'SecretBoolean':
updated_input_values[input_name] = st.checkbox(
label=f"{input_type}: {input_name}",
value=bool(value)
)
else:
updated_input_values[input_name] = st.number_input(
label=f"{input_type}: {input_name}",
value=value
)

return updated_input_values

def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# pass test name in via the command line
if nada_test_file_name is None:
Expand Down Expand Up @@ -156,32 +189,15 @@ def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
st.code(program_code, language='python')

# Display inputs grouped by party, alphabetized
updated_input_values = {}
# Get unique party names and sort them alphabetically
party_names = sorted(set(info['party'] for info in input_info.values()))

for party_name in party_names:
st.subheader(f"{party_name}'s Inputs")
for input_name, value in input_values.items():
if input_info[input_name]['party'] == party_name:
input_type = input_info[input_name]['type']
if input_type == 'SecretBoolean':
updated_input_values[input_name] = st.checkbox(
label=f"{input_type}: {input_name}",
value=bool(value)
)
else:
updated_input_values[input_name] = st.number_input(
label=f"{input_type}: {input_name}",
value=value
)
updated_input_values = create_party_inputs(input_info, input_values)

output_parties = list(set(output['party'] for output in output_info.values()))

should_store_inputs = st.checkbox("Store secret inputs before running blind computation", value=False)

# Button to store inputs with a loading screen
if st.button('Run blind computation'):
st.divider()
# Conditional spinner text
if should_store_inputs:
spinner_text = "Storing the Nada program, storing inputs, and running blind computation on the Nillion Network Testnet..."
Expand All @@ -203,8 +219,6 @@ def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# Call the async store_inputs_and_run_blind_computation function and wait for it to complete
result_message = asyncio.run(store_inputs_and_run_blind_computation(input_data, program_name, output_parties, nilchain_private_key, path_nada_bin, cluster_id_from_streamlit_config, grpc_endpoint_from_streamlit_config, chain_id_from_streamlit_config, bootnodes, should_store_inputs))

st.divider()

st.subheader("Nada Program Result")

st.text('Output(s)')
Expand Down
47 changes: 28 additions & 19 deletions streamlit_demo_apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,49 @@ To deploy Streamlit apps, they need to be written and committed to a public Gith
cp .streamlit/secrets.toml.example .streamlit/secrets.toml
```

### 1. Create an app file in the streamlit_demo_apps folder
### 1. Run the script to generate a new streamlit app for your program

Check out the addition app file example:
From the root folder of this repo, run the generate-streamlit-app script:

`app_addition.py`
```
python3 generate-streamlit-app.py
```

### 2. Follow the prompts to

### 2. Copy the compiled Nada program files from the target/ folder into the streamlit_demo_apps/compiled_nada_programs folder
- Select an existing program (from the src/ directory)
- Select an existing yaml test file for your program (from the tests/ directory)

Check out the compiled Nada program files for addition:
This will generate a Streamlit app file: streamlit*demo_apps/app*[your_program_name].py. The script will run the Streamlit app locally with this command

nada binary `addition.nada.bin`
nada json `addition.nada.json`
```
streamlit run streamlit_demo_apps/app_[your_program_name].py`
```

### 3. Update your app file with the corresponding program name and program test name
### 3. Test your Streamlit app locally

Check out the addition app file example:
View the app in your browser to make sure everything works as expected.

`app_addition.py`
### 4. Deploy your app

Add and commit your new streamlit app code to your forked Github repo. (Code must be connected to a remote, open source GitHub repository to deploy a Streamlit app.)

```
program_name = 'addition'
program_test_name = 'addition_test'
git add .
git commit -m "my new streamlit nillion app"
git push origin main
```

### 4. Test your Streamlit app locally
Once you've committed the open source code, you can click the "deploy" button within your local streamlit app. Sign in with Github and select the "Deploy Now" on Streamlit Community Cloud option to deploy the app for free.

Make sure the apps will work when deployed by testing this command from the root folder.
#### Deploy an app settings

```
streamlit run streamlit_demo_apps/[app_file_name].py
```
Set the main file path to your new app `streamlit_demo_apps/app_[your_program_name].py`

For example to make sure the addition app will work when deployed, run
Go to "Advanced settings" and in Secrets, copy in the contents of your .streamlit/secrets.toml file. At a minimum, make sure to add your secret private key:

```
streamlit run streamlit_demo_apps/app_addition.py
nilchain_private_key = "YOUR_FUNDED_PRIVATE_KEY"
```

Save and click "Deploy" to deploy your testnet Streamlit app!
12 changes: 8 additions & 4 deletions streamlit_demo_apps/app_addition.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
# This file was automatically generated by the generate-streamlit-app script.
# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_addition.py`

import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import streamlit_app
import streamlit_app

program_name = 'addition'
program_test_name = 'addition_test'
program_name = "addition"
program_test_name = "addition_test"

def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")

if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")

streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)

if __name__ == "__main__":
main()

1 change: 1 addition & 0 deletions streamlit_demo_apps/app_equality.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# This file was automatically generated by the generate-streamlit-app script.
# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_equality.py`

import sys
import os
Expand Down
122 changes: 8 additions & 114 deletions streamlit_demo_apps/app_list_scan_linear.py
Original file line number Diff line number Diff line change
@@ -1,132 +1,26 @@
import questionary
from pathlib import Path
import os
import yaml
import subprocess
import shutil

STREAMLIT_APP_TEMPLATE = '''# This file was automatically generated by the program-test-selector script.
# This file was automatically generated by the generate-streamlit-app script.
# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_list_scan_linear.py`

import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import streamlit_app

program_name = "{program_name}"
program_test_name = "{program_test_name}"
program_name = "list_scan_linear"
program_test_name = "list_scan_linear_test"

def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{{program_name}}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{{program_name}}.nada.json")
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")

if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{{program_name}}.nada.bin` to the compiled_nada_programs folder.")
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{{program_name}}.nada.json` to the compiled_nada_programs folder.")
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")

streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)

if __name__ == "__main__":
main()
'''

def get_programs(directory):
return sorted([f.stem for f in Path(directory).glob('*.py') if f.is_file()])

def get_test_files(directory, program_name):
matching_files = []
for file in Path(directory).glob('*.yaml'):
try:
with open(file, 'r') as f:
test_data = yaml.safe_load(f)
if test_data and 'program' in test_data and test_data['program'] == program_name:
matching_files.append(file)
except yaml.YAMLError:
print(f"Error reading {file}. Skipping.")
except Exception as e:
print(f"Unexpected error reading {file}: {e}. Skipping.")
return matching_files

def select_program_and_test():
programs = get_programs('src')
if not programs:
print("No Python programs found in 'src' directory.")
return None, None

selected_program = questionary.select(
"Select an existing program:",
choices=programs
).ask()

test_files = get_test_files('tests', selected_program)
if not test_files:
print(f"No test files found for '{selected_program}' in 'tests' directory.")
return selected_program, None

selected_test = questionary.select(
"Select a test file:",
choices=[f.name for f in test_files]
).ask()

return selected_program, selected_test

def build_nada_program(program_name):
try:
subprocess.run(['nada', 'build', program_name, '--mir-json'], check=True)
print(f"Successfully built {program_name}")
return True
except subprocess.CalledProcessError as e:
print(f"Error building {program_name}: {e}")
return False
except FileNotFoundError:
print("Error: 'nada' command not found. Make sure it's installed and in your PATH.")
return False

def copy_nada_files(program_name):
source_dir = Path('target')
dest_dir = Path('streamlit_demo_apps/compiled_nada_programs')

for ext in ['.nada.json', '.nada.bin']:
source_file = source_dir / f"{program_name}{ext}"
dest_file = dest_dir / f"{program_name}{ext}"

if source_file.exists():
shutil.copy2(source_file, dest_file)
print(f"Copied {source_file} to {dest_file}")
else:
print(f"Warning: {source_file} not found")

def create_streamlit_app(program_name, test_name):
app_content = STREAMLIT_APP_TEMPLATE.format(
program_name=program_name,
program_test_name=test_name
)

app_file_path = Path(f'streamlit_demo_apps/app_{program_name}.py')
with open(app_file_path, 'w') as f:
f.write(app_content)
print(f"Created Streamlit app file: {app_file_path}")

def main():
program, test = select_program_and_test()

if program:
print(f"Selected program: {program}")
if test:
print(f"Selected test file: {test}")

with open(os.path.join('tests', test), 'r') as file:
test_data = yaml.safe_load(file)
print("\nTest file contents:")
print(yaml.dump(test_data, default_flow_style=False))

if build_nada_program(program):
copy_nada_files(program)
create_streamlit_app(program, test.replace('.yaml', '') if test else '')
else:
print("No program selected.")

if __name__ == "__main__":
main()
22 changes: 0 additions & 22 deletions streamlit_demo_apps/app_multiplication.py

This file was deleted.

12 changes: 8 additions & 4 deletions streamlit_demo_apps/app_voting.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
# This file was automatically generated by the generate-streamlit-app script.
# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_voting.py`

import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import streamlit_app
import streamlit_app

program_name = 'voting'
program_test_name = 'voting_test'
program_name = "voting"
program_test_name = "voting_test"

def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")

if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")

streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)

if __name__ == "__main__":
main()

Binary file modified streamlit_demo_apps/compiled_nada_programs/addition.nada.bin
Binary file not shown.
Loading

0 comments on commit b8c03dc

Please sign in to comment.