FluidFrameDev/tutorials/04-uplink-file-hash.md

6.1 KiB
Raw Permalink Blame History

04 — Uplink: Hash a Local File from the Cloud

Goal: Let your Anvil app call a local Python function (via Uplink) to compute a files SHA-256 hash. When Uplink is running, it works; when it isnt, the UI shows “Uplink offline.”


1) What Youll Build

  • A client UI with a FileLoader and a Label for status/result.
  • A server callable the client invokes (so calls always go client → server).
  • A local Uplink script that actually computes the hash.
  • Graceful error handling when the Uplink agent is offline.

2) Anvil App UI

On Form1 (client):

  • Add FileLoaderfile_loader_1 (text: “Choose file…”).
  • Add Labellabel_status (text: “Uplink: checking…”, foreground: muted).
  • Add TextAreatext_area_result (read-only, multi-line; for hash + meta).

Optional: add a Button “Copy hash” bound to text_area_result.text.


3) Server Module

Create or open ServerModule1 and add:

import anvil.server
from anvil import media

@anvil.server.callable
def hash_selected_file(file_media: media.Media):
  """
  Proxies the request to an Uplink-connected function 'hash_file'.
  Returns a dict with sha256, name, size, and error (if any).
  """
  try:
    if not file_media:
      return {"error": "No file provided."}

    # This calls the uplink-exposed function defined in the local script below.
    result = anvil.server.call('hash_file', file_media)
    # Expecting uplink to return a dict with sha256 and size (bytes).
    return {
      "name": getattr(file_media, 'name', 'unknown'),
      "size": getattr(file_media, 'size', None),
      **(result or {})
    }
  except anvil.server.TimeoutError:
    return {"error": "Uplink timeout — is the local agent running?"}
  except anvil.server.NoServerFunctionError:
    return {"error": "Uplink function not found. Did you name it 'hash_file'?"}
  except Exception as e:
    return {"error": f"Unexpected error: {e.__class__.__name__}: {e}"}

Why a server proxy? It keeps your client thin and lets you add auth/role checks later without touching the UI.


4) Client Code (Form1)

Open Code for Form1 and add:

from anvil import *
import anvil.server

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.label_status.text = "Uplink: checking…"
    self._check_uplink_status()

  def _check_uplink_status(self):
    """
    Calls a tiny ping if available; otherwise assume offline.
    You can skip this and just infer from failures, but UX is nicer with a badge.
    """
    try:
      # If you create a 'uplink_ping' callable in the uplink script, uncomment:
      # pong = anvil.server.call('uplink_ping')
      # self.label_status.text = f"Uplink: {pong}"
      # For now, well just set neutral.
      self.label_status.text = "Uplink: ready (if agent is running)"
    except Exception:
      self.label_status.text = "Uplink: offline"

  def file_loader_1_change(self, file, **event_args):
    """Called when a new file is loaded into this FileLoader"""
    if not file:
      return
    self.label_status.text = "Hashing…"
    self.file_loader_1.enabled = False
    try:
      res = anvil.server.call('hash_selected_file', file)
      if res.get("error"):
        Notification(res["error"], style="danger", timeout=4).show()
        self.label_status.text = "Uplink: offline or error"
        return

      name = res.get("name") or getattr(file, "name", "unknown")
      size = res.get("size") or getattr(file, "size", None)
      sha  = res.get("sha256")
      size_kb = f"{(size/1024):.1f} KB" if isinstance(size, (int, float)) else "n/a"

      self.text_area_result.text = (
        f"File: {name}\n"
        f"Size: {size_kb}\n"
        f"SHA-256:\n{sha}"
      )
      self.label_status.text = "Uplink: online ✓"
    finally:
      self.file_loader_1.enabled = True

Create a local Python file on your machine, e.g. uplink_hash.py. Install the Uplink package if needed:

pip install anvil-uplink

Script:

# uplink_hash.py
import hashlib
import anvil.server

# --- connect ---
# Replace with your key from Anvil (Settings -> Uplink)
anvil.server.connect("YOUR-UPLINK-KEY")

# --- functions ---
@anvil.server.callable("uplink_ping")
def uplink_ping():
  return "online ✓"

@anvil.server.callable("hash_file")
def hash_file(file_media):
  """
  file_media is an anvil.Media object coming from the client.
  We'll read it as bytes and return SHA-256.
  """
  # .get_bytes() gives a Bytes object; .get_bytes_io() gives a BytesIO
  b = file_media.get_bytes()
  h = hashlib.sha256(b).hexdigest()
  return {"sha256": h, "size": len(b)}

# --- stay alive ---
anvil.server.wait_forever()

Run it locally:

python uplink_hash.py

Leave the terminal open while you use the app. If you stop it, the app should show Uplink: offline behavior.


6) Security Notes (read these)

  • Never log or expose full file contents; we only compute a hash.
  • Uplink runs on your machine; it can read files if you write code to do so. Treat it like any local service.
  • If you add roles (Tutorial 05), gate the server proxy (hash_selected_file) behind a role like viewer or editor.

7) Troubleshooting

  • NoServerFunctionError: Function name mismatch. You must expose hash_file and call exactly that.
  • Timeout: Local script not running or blocked by network. Try a simple uplink_ping to confirm.
  • Huge files: This demo reads all bytes. For large files, stream in chunks or limit size on client.

8) Demo Asset

Record a 1015s GIF:

  1. Start uplink script.
  2. Choose a small file.
  3. Show hash result and “Uplink: online ✓”.
  4. Stop uplink; try again to show error state.

Save as: assets/uplink-hash-demo.gif

Reference here:

![Uplink Hash Demo](../assets/uplink-hash-demo.gif)

9) Takeaways

  • Uplink lets your cloud app do local work securely.
  • Keep a thin server proxy for auth + error shaping.
  • Detect offline state and tell the user why it failed.