Skip to content

Events over webhooks example app #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

bowenwr
Copy link
Contributor

@bowenwr bowenwr commented Apr 4, 2024

Adds an example app demonstrating receiving a data event from Benchling and writing the results to an external PostgreSQL database.

Copies many of the files and patterns (e.g., localtunnel) from examples/chem-sync-local-flask.

demo-full

@bowenwr bowenwr marked this pull request as ready for review April 4, 2024 14:56

![image info](./docs/demo-full.gif)

> ℹ️ Events delivered by webhooks is in early access and unavailable by default. To use this example, please reach out to Benchling support to enable webhook delivery for events on your tenant.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Psterne @dgervais-benchling I didn't see any public docs on getting setup with Events over Webhooks. I also used "Events delivered by webhooks" as I feel like "events over webhooks" is our internal language and not necessarily intuitive to users.

Please suggest any alternative copy you'd like here or elsewhere in the README!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bowenwr Updated docs for Events (via Webhooks and Eventbridge) are WIP. We can link to it from here once those are live.

I agree that "Events delivered by webhooks" is better. At some point I'm hoping we can just call it "Webhooks"!

A database password can be created now. It can be any valid PostgreSQL password. One way to generate a random password for local development purposes on *nix systems might be:

```bash
date +%s | sha256sum | base64 | head -c 16 | cat > .database_password
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonbenchling or @stash-benchling for sec eng (Jason previously reviewed the last example App). Wanted to be sure this was reasonable guidance.

I'd actually almost prefer to disable passwords for postgres for this local example only, but I figured it was better to:

  1. Have the user set some password, even if it's weak
  2. Store it securely (e.g., via Docker secrets)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not use the current time for a source of entropy on it's own. Stakes are low here, this is a test/sample app, but we don't want people YOLOing that pattern into production 😅

Since we assume the user has python available (after all, it's a flask app), we can use a python one liner to simplify getting secure randomness. There's some bash gymnastics that need to be done to read directly from /dev/urandom and get usable output, so better to take advantage of python's wrapper IMO.

Something like:
python -c "import secrets; print(secrets.token_hex(64))" > .database_password would be preferable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonbenchling Thanks for the quick response. The Flask App runs its own container where Python is installed. I tried this in my own local terminal (where python is not installed globally) and it failed within my virtual environment 😅

I'm hoping to reduce the number of steps / requirements for developers, so I think the safest thing to do is just remove this "random" password generation and tell the user to edit in their own?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bowenwr That seems reasonable -- tell the end user to "generate and store a secure random password" or something along those lines.

openssl is another probably pretty common option to generate randomness, but that also might not always be present. Some of the internet suggested /dev/urandom based methods that work on linux boxes don't work on my mac, so that's probably not a great portable option either.

### Benchling Prerequisites
1. Access to a Benchling tenant, like `https://my-tenant.benchling.com`
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).
3. This example also requires Events delivered by Webhooks to be enabled on your tenant. Reach out to Benchling support to find out more about participating in early access.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Psterne @dgervais-benchling similar to above, let me know if you want different copy to express this requirement.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Is there a step 4 that's missing? Should Global Apps be step 4?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number skipped 4 and went to 5. Fixed now, thanks for catching!

if isinstance(webhook.message, EventCreatedWebhookV0Beta):
# Since we'll be receiving ALL entity registrations, add a filter to only
# work with events that meet our criteria (e.g., specific schema)
if _is_target_event(app, webhook.message):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edwin-benchling This filtering logic is one of the main differences from the other example App that would merit closer review.

from local_app.lib.postgresql import write_data


def sync_event_data(app: App, entity_registered: V2EntityRegisteredEventBeta) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edwin-benchling This is the second file that's "new" and could use closer review.

logger = get_logger()


def write_data(entity_name: str, benchling_api_id: str, field_value: str | None) -> str:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edwin-benchling This is the third and final file that is new and merits closer review.

Although I don't wanna focus too hard on teaching users how to connect to PostgreSQL in Python and intentionally kept this light.

@bowenwr
Copy link
Contributor Author

bowenwr commented Apr 4, 2024

FYI @benchling/app-ecosystem for broad awareness.

I tagged @edwin-benchling as DRI for engineering review, but feedback is welcome from everyone.

@bowenwr bowenwr requested a review from jasonbenchling April 4, 2024 17:20
echo.> .database_password
```

A database password can be created now. It can be any valid PostgreSQL password. Generate and store a secure password in `.database_password`. For local development, you could open the file and manually enter any valid password.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonbenchling Removed the "random generation" bash example and converted to general guidance.

Copy link

@jasonbenchling jasonbenchling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit re: pbpaste, but other than that, looks good


```bash
touch .client_secret
pbpaste > .client_secret

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pbpaste is just a MacOS thing, afaik

@bowenwr
Copy link
Contributor Author

bowenwr commented Apr 4, 2024

Nit re: pbpaste, but other than that, looks good

Ugh, we also have it in examples/chem-sync-local-flask. Nobody complained during the live workshop so I wonder if people just rolled with it. 😅

@bowenwr
Copy link
Contributor Author

bowenwr commented Apr 5, 2024

FYI @benchling/solutions-teams for an App code example using the new beta Events over Webhooks functionality.



def _connection() -> psycopg2.extensions.connection:
return psycopg2.connect(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it'd be easier to build a Repository pattern and implement it with an in-memory database and leave it as an exercise to the reader to provide their own implementation of a durable database, especially if the point of the example code is not really about persistence...

but since this is already done, I don't feel as strongly.



class TestPostgreSql:
# postgresql fixture provided by pytest-postgresql

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I'm not sure why, but this fixture did not kick in when running tests locally. I'd just get errors like

>           raise ExecutableMissingException(
                f"Could not found {self.executable}. Is PostgreSQL server installed? "
                f"Alternatively pg_config installed might be from different "
                f"version that postgresql-server."
            ) from ex
E           pytest_postgresql.exceptions.ExecutableMissingException: Could not found /usr/lib/postgresql/14/bin/pg_ctl. Is PostgreSQL server installed? Alternatively pg_config installed might be from different version that postgresql-server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants