Skip to main content

App Development Details

In this section, we'll dig into what actually happens inside an Edge application. A lot of the "heavy lifting" is done for you by Edge and the development tools. It's important to learn what those tools are and how they work so you can apply them correctly.

Things to watch out for

About the "edge-native-base" Docker image

The example app you built and deployed in the previous section has very little boilerplate code. This is partly because of a line like this at the top of the Dockerfile:

FROM quay.io/enthought/edge-native-base:1.1.0

The edge-native-base image is provided by Enthought and handles some of the "paperwork" involved with interfacing between your app and Edge. This includes:

  • Handling the app's side of the OAuth authorization flow, so that only the user who launched the app can access it.
  • Sending activity "pings" back to Edge as the app is used. This is so that Edge can eventually shut the app down if it isn't being used.
  • Proxying your app and serving it on the standard port (8888) so that Edge can find it.

The base image also includes the EDM deployment tool, which is used to install Python packages. pip is also available once you have created an EDM-based Python environment.

Using the Edge API (EdgeSession) inside your app

You can easily construct an EdgeSession object, by which your app can access APIs like data connectors or files. Because each user has their own running copy of your app, the EdgeSession will connect automatically under their account.

In your app's code, all you need to do is call the constructor with no arguments:

from edge.api import EdgeSession

mysession = EdgeSession()

That's it! The interface is ready to use. EdgeSession auto-configures by reading the user's API token, server location, etc., from the environment.

See the Streamlit example (or other examples in the online examples gallery) for tips on using EdgeSession locally in development mode. Briefly, when running your app outside of Edge you need to set some environment variables that will pass down your testing API token and a few other pieces of information.

Options for files and data storage

You can write to the local file system inside your app, but that local-disk storage is ephemeral... when the app is shut down and restarted, any changes will not be preserved. To persist data across application launches, you have a couple of options.

Store files on Edge

The first is to use the local drive as "scratch space", and save your data to Files on Edge or another external storage service. If you want to do this, create an EdgeSession object in your code and use the .files property to interact with the Edge file system. You can upload files from the local disk to Edge, and download files from Edge to process them. For example:

from edge import EdgeSession

mysession = EdgeSession()

# Upload a file
with open("my-local-file.bin", "rb") as f:
mysession.files.upload("my-remote-file.bin", f)

# Download a file
mysession.files.download("my-dataset.bin")

Store files in a persistent volume

The second option is to request that Edge allocate a persistent volume for your app. This approach is not currently exposed in the Edge user interface; you will need to specify the volume parameters when registering your app version via the Edge API. See the Programmatically publishing versions discussion below for how to use the Edge API to register an app version. You will need to include two additional fields in AppVersion constructor:

  • volume_mount_point: a string with the name of an (empty) folder in your Docker image where you want the volume to be mounted. Keep in mind that since your startup script is in the home directory, you should not put /home/app for this value. Instead, you can e.g. create a "workspace" folder and put /home/app/workspace as the mount point.

  • suggested_volume_size: integer with the capacity of the volume in gigabytes (not bytes). For cost efficiency, you should use the smallest volume size possible.

Ports and environment variables

When using the edge-native-base image, your app should bind to 127.0.0.1:9000. The edge-native-base machinery will then proxy your app to the standard port of 8888. Your app should use plain HTTP or websockets. Don't use SSL or certificates; Edge handles that for you.

A number of the standard JupyterHub environment variables are available, plus a few Edge-specific ones:

  • JUPYTERHUB_SERVICE_PREFIX: If defined, has the full route to your app. If you're using a framework like Flask or FastAPI, you should respond to requests that start with this prefix. For example:

    PREFIX = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")

    @app.route(PREFIX + "my-resource")
    def get_my_resource():
    ...
  • EDGE_API_TOKEN or JUPYTERHUB_API_TOKEN: Allows access to the Edge API. At least one of these will be defined when your app is running in Edge. EdgeSession automatically picks up this value, along with the following two variables.

  • EDGE_API_ORG: The "short name" (ID) of the user's organization.

  • EDGE_API_SERVICE_URL: The full URL of the Edge API root.

Continuous integration

Running preflight checks

Each example includes a python -m ci preflight command, which launches the app in a local JupyterHub environment, and checks that it's working properly. This is useful for interactive development.

There's also a Tools folder in the example gallery which contains a preflight.py test script. This is a standalone script you can use in e.g. GitHub Actions to run preflight checks as part of your continuous-integration (CI) process. See the README in that folder for instructions.

Programmatically publishing versions

You can automatically publish new versions of your app, for example in GitHub Actions as part of your continuous-integration workflow. To do so, create an EdgeSession within your build script and call the Edge "application" API:

from edge.api import EdgeSession
from edge.apps.application import Application
from edge.apps.app_version import AppKindEnum, AppVersion
from edge.apps.server_info import ServerInfo

# Create an Edge session.
edge = EdgeSession(
service_url="https://edge.enthought.com/services/api",
organization="<YOUR_ORGANIZATION>",
api_token="<YOUR_EDGE_API_TOKEN>"
)

# Register the application version.

# Icon is a base64-encoded PNG or JPEG image.
# It should be square, any size; 64x64 or 128x128 are common.
ICON = (
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAk1BMVEUAAA"
"AMfIYAdnwAdnwAd3wAc3kAdntYsb8AdnwAdn0AdXwAdXsAd30Ad3xYsb8A"
"d30AdntYsL8Ad30AdnsAd30AdnwAd3wAd30AdnxYssBYsb5Ysr9Zs70Adn"
"tYsr9Ysb9Ysb9Ysr8AdnxXs75Ysb4AdXsAd3wAdnxYsb8AeoAAfYMAf4Vb"
"t8Zfv84Ad31ixtVevMuy+odXAAAAJ3RSTlMABPr88Shd+4pmkXyDbfB0Xy"
"dWTy0h9TLnkYv+IJmX9uigNzOIf5LOQ2WlAAAC/ElEQVR42sXX7VLiMBiG"
"4aTBDwQVEJSPXdkF3dAi1fM/uoXp7DyzIzHEm7bvT6Dc16SlDQbObG5anf"
"nHx4tpcea7q6sdWQPeXyyIgPc3+7nazQwY3N8sWhLM9uu/+Sd4NmBQvxog"
"YH0g4P2qLMFvAwb0WxE8q9+CwJqX96W6muV7U9fB8NfbsRV4u1ubhubHQf"
"C5PzQNjZXg/741dY8EdxKAPhTQPhfQPhfQPheQPhcM9wLa54KG+jYoCPZt"
"M4Llspn+KlRZr0PvrM7Z/7m9DHVCr19ub87Zd4UEX436hZeA9zPnJTix7z"
"IJcN97CU7tey8B70twev9Mgj+HvgQJ/fMIbqq+BCl9CXhfgpS+BLwvQUpf"
"At6XIN7nAvWTBOpDgfppgm6uPhGonyaw00LHAIH6qYLJNA8KeD8usF8Irk"
"E/SdChAvXbEajfjkD9dgTqtyNQv0XB9aFPBRcRAexzQXo/q1MQ77uyqHEN"
"4v2smPby0qUKHoOC1H7eGZtB4VMF47AgtX+hrU4Ngmj/cWzs+QU2rW/qFc"
"T7lSA/o6BvbEq/+rpRuuApiwvi/doE0X72pA/VJYj36xXE+7UL+sf7Tn0s"
"WPncBQTh51/Z0fV3oiDvBtegU/rQ/eC1OIpz5XRirEkQOFfch/8ulEfNru"
"gZcx8QVI+AuECnoGtM8NEc6N8aY0OC7ESB+qdvDdS33xQ8aH9A+0igfj74"
"Zh8K9BMEfSxwPgd9KKj6o8S+V58KaJ8LXJk/gD4WeM/6XMD6XDCC558LYJ"
"8LYJ8LQB8IYJ8IeD9+eG9LBPH7PxTgPhbQPhfwPhek932gDwTRfnjLyAW0"
"zwW8H5+IAPa5gPa5gPa5gPa5gPa5gPa5gPa5gPa5gPa5APa5gPW5YDJBfS"
"6YTkmfC1xZukb6NiiAfS4AfSAAfSQoHOpzwes2Q30+fQla6VsJQB8LeJ8L"
"wv0B6AMB6AMB6HMB7XMB7XMB7XMB7XMB7XMB7XMB7XMB7XMB7XMB7XMB7H"
"NBjvpccH0L+38B6kvWH2wXIe8AAAAASUVORK5CYII="
)
version = AppVersion(
app_id="my-app-id", # Specified when the app is created
version="1.0.0", # or whatever version you have
title="My Application Title",
description="This Is An Example Edge Application",
icon=ICON,
kind=AppKindEnum.Native,
link="quay.io/enthought/YOUR_IMAGE_NAME_HERE:TAG",
recommended_profile="edge.medium"
)
edge.applications.add_app_version(version)