Skip to content

Commit 6fc5cf9

Browse files
authored
Merge pull request #1 from benchling/initial-example
Initial commit with an example for chem-sync-local-flask
2 parents ac4b376 + cef206e commit 6fc5cf9

40 files changed

Lines changed: 918 additions & 0 deletions

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.idea
2+
.vscode
3+
**/.venv/
4+
**/*.egg-info
5+
**/__pycache__
6+
**/.env
7+
.DS_Store
8+
**/.client_secret
9+
**/.pytest_cache
10+
.mypy_cache
11+
.ruff_cache

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Benchling App Python Examples
2+
3+
The `examples/` directory contains Benchling App samples written by Benchling.
4+
5+
## chem-sync-local-flask
6+
7+
Demonstrates creating a custom UI in Benchling allowing users to search for
8+
molecules from [PubChem](https://pubchem.ncbi.nlm.nih.gov/) and sync them into Benchling.
9+
10+
Uses [localtunnel](https://localtunnel.me/) and [Docker](https://www.docker.com/) to receive webhooks
11+
in a local development environment running [Flask](https://flask.palletsprojects.com/) with the
12+
[Benchling SDK](https://docs.benchling.com/docs/getting-started-with-the-sdk).
13+
14+
![image info](./examples/chem-sync-local-flask/docs/demo-short.gif)
15+
16+
**Code Includes:**
17+
* Benchling App Authentication via [Client Credentials](https://docs.benchling.com/docs/getting-started-benchling-apps#getting-credentials)
18+
* Custom UI via [App Canvas](https://docs.benchling.com/docs/introduction-to-app-canvas)
19+
* User Feedback via [App Status](https://docs.benchling.com/docs/introduction-to-app-status)
20+
* Data Mapping via [App Config](https://docs.benchling.com/docs/app-configuration)
21+
* Receiving and verifying [Webhooks](https://docs.benchling.com/docs/getting-started-with-webhooks)
22+
* Creating [molecule custom entities](https://benchling.com/api/reference#/Molecules/createMolecule)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM python:3.11
2+
3+
# Add non-root user for security
4+
ARG USERNAME=nonroot
5+
RUN groupadd --gid 1000 $USERNAME && useradd --uid 1000 --gid 1000 -m $USERNAME
6+
## Make sure to reflect new user in PATH
7+
ENV PATH="/home/${USERNAME}/.local/bin:${PATH}"
8+
USER $USERNAME
9+
10+
# Install pre-requirements
11+
RUN pip install pip~=23.3.2 setuptools~=69.0.3
12+
13+
COPY requirements.txt ./
14+
RUN pip install -r requirements.txt
15+
16+
COPY dev_requirements.txt ./
17+
RUN pip install -r dev_requirements.txt
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"build": {
3+
"dockerfile": "Dockerfile",
4+
"context": ".."
5+
},
6+
"remoteUser": "nonroot",
7+
"customizations": {
8+
"vscode": {
9+
"extensions": [
10+
"ms-python.python",
11+
"ms-python.mypy-type-checker",
12+
"charliermarsh.ruff"
13+
],
14+
"settings": {
15+
"python.testing.pytestArgs": [
16+
"."
17+
],
18+
"python.testing.unittestEnabled": false,
19+
"python.testing.pytestEnabled": true,
20+
"python.editor.defaultFormatter": "charliermarsh.ruff",
21+
"python.linting.mypy": true,
22+
"python.linting.enabled": true,
23+
"explorer.excludeGitIgnore": true
24+
}
25+
}
26+
}
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.idea
2+
.vscode
3+
**/.venv/
4+
**/*.egg-info
5+
**/__pycache__
6+
**/.env
7+
.DS_Store
8+
**/.client_secret
9+
**/.pytest_cache
10+
.mypy_cache
11+
.ruff_cache
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.11
2+
3+
# Install pre-requirements
4+
RUN pip install pip~=23.3.2 setuptools~=69.0.3
5+
6+
COPY requirements.txt ./
7+
RUN pip install -r requirements.txt
8+
COPY ./local_app /src/local_app
9+
WORKDIR /src/local_app
10+
CMD ["flask", "run", "--host", "0.0.0.0"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM node:21-alpine
2+
RUN npm install -g localtunnel@2.0.2
3+
4+
# Note: This might not work with some networks. For instance, random airplane WiFi
5+
CMD lt --port 5000 --local-host benchling-app --print-requests
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Benchling App Example: Chemical Sync for Local Development
2+
3+
An example Benchling App written in Python which allows users to search for chemicals
4+
via [PubChem](https://pubchem.ncbi.nlm.nih.gov/) and create them in Benchling.
5+
6+
![image info](./docs/demo-full.gif)
7+
_The App features branching flows and will also validate user inputs._
8+
9+
## Technical Prerequisites
10+
11+
This app is optimized as a minimal local development experience using [Docker](https://www.docker.com/) for reproducibility.
12+
13+
> ⚠️ **Development Only**: This example is not meant to be copied into production as-is. There are additional deployment, scale, and security concerns that should be addressed before deploying an app based on this example to production.
14+
15+
It relies on a few other tools that will be installed for you within Docker containers:
16+
* [Localtunnel](https://localtunnel.me/) - expose a public webhook URL and forward the results locally. ⚠️ *Not for production or real data!*
17+
* [Flask](https://flask.palletsprojects.com/) - A simple Python web application framework
18+
19+
## Getting Started
20+
21+
Create an empty placeholder file for Docker secrets. *nix example:
22+
23+
```bash
24+
touch .client_secret
25+
```
26+
27+
Start Docker:
28+
29+
```bash
30+
docker compose up --build -d
31+
```
32+
33+
Tip: You can omit the `-d` option if you want to run in the foreground. Otherwise, use `docker compose logs -f` to tail logs.
34+
35+
You can verify that Flask is up and running:
36+
37+
```bash
38+
curl localhost:8000/health
39+
```
40+
41+
If Flask is running, you should see `OK` printed.
42+
43+
Be sure to note the URL created for you by `localtunnel`. The log line should look something like this:
44+
45+
```
46+
app-workshop-local-tunnel-1 | your url is: https://brave-wombats-poke.loca.lt
47+
```
48+
49+
On *nix systems, you can easily obtain _just_ the URL via:
50+
51+
```
52+
docker compose logs local-tunnel | grep -o https://.* | tail -n 1
53+
```
54+
55+
Example Output:
56+
57+
```
58+
https://brave-wombats-poke.loca.lt
59+
```
60+
61+
> 💡 Don't forget to append `/1/webhooks`, making the full URL given to Benchling `https://brave-wombats-poke.loca.lt/1/webhooks`
62+
63+
## Setting Up Your App in Benchling
64+
65+
### Benchling Prerequisites
66+
1. Access to a Benchling tenant, like `https://my-tenant.benchling.com`
67+
2. Ensure you've been granted access to the [Benchling Developer Platform Capability](https://help.benchling.com/hc/en-us/articles/9714802977805-Access-the-Benchling-Developer-Platform).
68+
3. This example also requires a [Lab Automation](https://www.benchling.com/resources/benchling-lab-automation) license.
69+
4. [Molecule entities](https://help.benchling.com/hc/en-us/articles/9684254682893-Molecule-entity-overview) will need to be enabled on your tenant.
70+
71+
### Upload the App Manifest
72+
73+
Click the user icon in the bottom left corner to bring up the main menu. Select "Feature Settings" > "Developer Console"
74+
75+
Next, click the "Create app" button and choose "From manifest."
76+
77+
When prompted to upload a file, select `manifest.yaml` and click "Create."
78+
79+
![image info](./docs/create-app.gif)
80+
81+
### Update the Webhook URL
82+
83+
Every time we restart the `local-tunnel` Docker container, it will provision
84+
a new public webhook URL.
85+
86+
Update the Benchling App's Webhook URL in the UI with the new server and
87+
append the path our Flask route expects (see `local_app/app.py`).
88+
89+
For example, if our `localtunnel` generated URL is `https://hot-ideas-doubt.loca.lt`,
90+
the webhook URL in Benchling should be:
91+
92+
```
93+
https://hot-ideas-doubt.loca.lt/1/webhooks
94+
```
95+
96+
![image info](./docs/update-webhook-url.gif)
97+
98+
### Generating a Client Secret
99+
100+
Generate a client secret in Benchling and be sure to copy the secret.
101+
102+
![image info](./docs/generate-secret.gif)
103+
104+
One easy way to set these environment variables for Docker is to add a `.env` file.
105+
106+
```bash
107+
touch .env
108+
```
109+
110+
Open it in an editor of your choice and set the values with the plaintext client ID
111+
for your App. For example:
112+
113+
```
114+
CLIENT_ID=42a0cd39-0543-4dd2-af02-a866c97f0c4d
115+
```
116+
117+
Since the client secret is sensitive, it's handled a bit differently. It's
118+
registered as a `secret` in our `docker-compose.yaml` file, which will be looking
119+
for a file `./client_secret`.
120+
121+
We can create this file and paste in the secret plaintext value if we have the secret in our clipboard.
122+
On *nix:
123+
124+
```bash
125+
touch .client_secret
126+
pbpaste > .client_secret
127+
```
128+
129+
> ⚠️ **Security Note:** Be sure to avoid committing `.client_secret` to a source code repository.
130+
131+
You'll then need to restart _just_ the `benchling-app` Docker service to pick up the changes:
132+
133+
```
134+
docker-compose up -d
135+
```
136+
137+
If you restart both containers, be sure to update your App in Benchling with the new webhook URL from localtunnel.
138+
139+
> ⚠️ **Security Note:** In production, store the secret with a secure solution such as a secrets store (AWS Secrets Manager, as an example) or, if storing programmatically, encrypted using app-layer encryption. Avoid placing it in plaintext anywhere in code or configuration.
140+
141+
### Create App Registry Dependencies
142+
143+
If you examine the `configuration` section of `manifest.yaml`, you'll see our App
144+
expects a few configuration items:
145+
1. A folder
146+
2. A molecule entity schema with two decimal fields
147+
148+
The `features` section of `manifest.yaml` also states that our App will render
149+
its UI on an `ASSAY_RUN`. So we'll also need:
150+
1. An Lab Automation run schema
151+
152+
#### Folder
153+
154+
Create a new folder where the molecules created by the App will be placed.
155+
An existing folder can also be used, if the App has permissions to it.
156+
157+
![image info](./docs/create-folder.gif)
158+
159+
#### Molecule Entity Schema
160+
161+
Create the entity schema in the tenant's registry. If you do not have access to
162+
the registry, you can ask your tenant administrator to do this for you.
163+
164+
![image info](./docs/create-molecule-schema.gif)
165+
166+
The created molecule schema should look something like this:
167+
168+
![image info](./docs/schema-example.png)
169+
170+
_Note: The names can be different, and the schema is allowed to have additional fields.
171+
As long as it's for a `Molecule` entity, and has at least two `Decimal` fields._
172+
173+
#### Lab Automation Run Schema
174+
175+
Create a new lab automation run schema in the registry.
176+
177+
![image info](./docs/create-run-schema.gif)
178+
179+
### Updating the App's Configuration
180+
181+
App Configuration gives us a stable code contract for referencing data mapped in a Benchling tenant.
182+
The values of the data in Benchling can then be changed without updating App code.
183+
184+
Let's update our configuration to:
185+
1. Specify a folder for syncing sequences
186+
2. Link a molecule schema and fields for the synced chemicals
187+
3. Select an assay run schema to associate with our Benchling App
188+
189+
![image info](./docs/update-app-config.gif)
190+
191+
### Permission the App
192+
193+
By default, Benchling Apps do not have permission to any data in Benchling.
194+
Let's grant some access by adding the Benchling App to an organization.
195+
196+
![image info](./docs/permission-app.gif)
197+
198+
## Running the App - Syncing a Chemical
199+
200+
1. Create a new notebook entry
201+
2. Insert a run of the schema linked in App Config
202+
3. Create the Run
203+
4. Enter a valid chemical name to search for, such as `acetaminophen`
204+
5. Click "Search Chemicals"
205+
6. After reviewing the preview, click "Create Molecule"
206+
7. Click the linked entity to view it in Benchling
207+
208+
![image info](./docs/demo.gif)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Only for maintainers
2+
ruff~=0.1.11
3+
pytest~=7.4.4
4+
mypy~=1.8.0
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
version: "3.8"
2+
3+
services:
4+
benchling-app:
5+
build: .
6+
ports:
7+
- "8000:5000"
8+
volumes:
9+
- type: bind
10+
source: .
11+
target: /src
12+
environment:
13+
# Don't set debug in production!
14+
- FLASK_DEBUG=1
15+
# Enable debug logging for Python Benchling App. This is not native, specific to this example implementation
16+
- BENCHLING_APP_LOG_LEVEL=DEBUG
17+
# Client ID is not sensitive and is the same across all tenants, so could be hard-coded
18+
# You might choose to have two different Apps for dev vs prod, which would have different client IDs
19+
- CLIENT_ID
20+
# Client secret for the Benchling App, stored somewhere securely in production.
21+
# Injected here for convenience. Each Client ID will have its own Client secret
22+
- CLIENT_SECRET_FILE=/run/secrets/app_client_secret
23+
secrets:
24+
- app_client_secret
25+
26+
# FOR LOCAL DEVELOPMENT ONLY!
27+
# Free tool for providing a public URL to forward webhooks to our Benchling App running locally
28+
# Do not do this in production or use with any sensitive data.
29+
# Benchling has not vetted this tool for use in production or in sensitive systems.
30+
# Conduct your own due diligence before choosing a tool for production use.
31+
local-tunnel:
32+
build:
33+
context: .
34+
dockerfile: Dockerfile.localtunnel
35+
36+
secrets:
37+
app_client_secret:
38+
file: .client_secret

0 commit comments

Comments
 (0)