-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from NillionNetwork/feat/questionary
Added script for turning any nada program and test into a streamlit app
- Loading branch information
Showing
18 changed files
with
284 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import questionary | ||
from pathlib import Path | ||
import os | ||
import yaml | ||
import subprocess | ||
import shutil | ||
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 | ||
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}" | ||
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() | ||
''' | ||
|
||
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 to create a streamlit app demo:", | ||
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 for starting input values:", | ||
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): | ||
try: | ||
app_content = STREAMLIT_APP_TEMPLATE.format( | ||
program_name=program_name, | ||
program_test_name=test_name | ||
) | ||
|
||
app_file_path = Path('streamlit_demo_apps') / f"app_{program_name}.py" | ||
print(f"Attempting to create file at: {app_file_path.absolute()}") | ||
|
||
# Ensure the directory exists | ||
app_file_path.parent.mkdir(parents=True, exist_ok=True) | ||
|
||
with open(app_file_path, 'w') as f: | ||
f.write(app_content) | ||
print(f"Created Streamlit app file: {app_file_path}") | ||
|
||
if app_file_path.exists(): | ||
print(f"Streamlit app file successfully created at {app_file_path}") | ||
return app_file_path | ||
else: | ||
print(f"Error: File creation verified failed for {app_file_path}") | ||
return None | ||
except Exception as e: | ||
print(f"Error creating Streamlit app file: {e}") | ||
return None | ||
|
||
def run_streamlit_app(app_path): | ||
try: | ||
print(f"Attempting to run Streamlit app: {app_path}") | ||
subprocess.run([sys.executable, '-m', 'streamlit', 'run', str(app_path)], check=True) | ||
except subprocess.CalledProcessError as e: | ||
print(f"Error running Streamlit app: {e}") | ||
except Exception as e: | ||
print(f"Unexpected error running Streamlit app: {e}") | ||
|
||
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) | ||
app_path = create_streamlit_app(program, os.path.splitext(test)[0] if test else '') | ||
if app_path: | ||
run_streamlit_app(app_path) | ||
else: | ||
print("No program selected.") | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,78 @@ | ||
# Deploying Streamlit Apps | ||
|
||
Deployed Streamlit apps live here in the streamlit_demo_apps folder. | ||
Follow the steps to deploy a live Streamlit app for your Nada program. The app will connect to the Nillion Testnet to store your Nada program, store secret inputs (or use computation time secrets), and run blind computation. | ||
|
||
## How to add a new Streamlit App | ||
|
||
### 0. Create a streamlit secrets file and add your nilchain private key within `.streamlit/secrets.toml` | ||
### 0. Fork this repo | ||
|
||
### 1. Create a streamlit secrets file | ||
|
||
Run this command to create a `.streamlit/secrets.toml` copied from the example. | ||
|
||
``` | ||
cp .streamlit/secrets.toml.example .streamlit/secrets.toml | ||
``` | ||
|
||
### 1. Create an app file in the streamlit_demo_apps folder | ||
|
||
Check out the addition app file example: | ||
Add your Nilchain private key to the .streamlit/secrets.toml file. The private key must be linked to a funded Nillion Testnet address that was created using a Google account (not a mnemonic). This allows you to retrieve the private key from Keplr. If you don’t have a Testnet wallet yet, you can learn how to create one here: https://docs.nillion.com/testnet-guides | ||
|
||
`app_addition.py` | ||
### 2. Run the script to generate a new streamlit app for your program | ||
|
||
### 2. Copy the compiled Nada program files from the target/ folder into the streamlit_demo_apps/compiled_nada_programs folder | ||
From the root folder of this repo, run the generate-streamlit-app script: | ||
|
||
Check out the compiled Nada program files for addition: | ||
|
||
nada binary `addition.nada.bin` | ||
nada json `addition.nada.json` | ||
``` | ||
python3 generate-streamlit-app.py | ||
``` | ||
|
||
### 3. Update your app file with the corresponding program name and program test name | ||
### 3. Follow the prompts to | ||
|
||
Check out the addition app file example: | ||
- Select an existing program (from the src/ directory) | ||
- Select an existing yaml test file for your program (from the tests/ directory) | ||
|
||
`app_addition.py` | ||
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 | ||
|
||
``` | ||
program_name = 'addition' | ||
program_test_name = 'addition_test' | ||
streamlit run streamlit_demo_apps/app_[your_program_name].py` | ||
``` | ||
|
||
### 4. Test your Streamlit app locally | ||
|
||
Make sure the apps will work when deployed by testing this command from the root folder. | ||
View the app in your browser to make sure everything works as expected. | ||
|
||
### 5. Commit your code to GitHub | ||
|
||
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.) | ||
|
||
``` | ||
streamlit run streamlit_demo_apps/[app_file_name].py | ||
git add . | ||
git commit -m "my new streamlit nillion app" | ||
git push origin main | ||
``` | ||
|
||
For example to make sure the addition app will work when deployed, run | ||
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. | ||
|
||
<img width="1000" alt="Streamlit Community Cloud" src="https://github.com/user-attachments/assets/74a70b4e-506c-41df-8d59-f949871c9a4e"> | ||
|
||
### 6. Deploy your app from Streamlit.io | ||
|
||
When you click "Deploy Now" from your local app, you'll be taken to streamlit.io and asked to log in with Github to create a new Streamlit app. Set the main file path to your new app `streamlit_demo_apps/app_[your_program_name].py` | ||
|
||
<img width="1000" alt="streamlit settings" src="https://github.com/user-attachments/assets/e3821aa4-44b6-4f16-8400-97e531dfef23"> | ||
|
||
#### Add your Nilchain Private Key using Advanced Settings > Secrets | ||
|
||
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" | ||
``` | ||
|
||
<img width="1000" alt="advanced settings" src="https://github.com/user-attachments/assets/6b48b225-60b7-41bd-8591-c04419131bf8"> | ||
|
||
Save and click "Deploy" to deploy your testnet-connected Streamlit app. | ||
|
||
### 7. Access Your Live Streamlit App | ||
|
||
Once deployed, you’ll get a live link to your Nillion Testnet Streamlit app! | ||
|
||
Example live Streamlit App: https://stephs-nada-multiplication-app.streamlit.app/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
Oops, something went wrong.