Go Beyond the Basics

You can use your Pico W to build complex apps with user authentication, database integration, email and more, by integrating with some of Anvil’s advanced features:

Table of Contents


User authentication

Anvil has built in user authentication supporting email/password, Google, Facebook, and even enterprise SAML authentication. You can use this authentication to restrict calls on your Pico W to only permit authorised users.

To do this, add the Users Service to your app, then add require_user=True to any @anvil.pico.callable definition. For example:

@anvil.pico.callable(require_user=True)
def toggle_led():
    # This call will only run if invoked by logged-in user
    led.toggle()

For an example of this, see the Morse code transmitter tutorial.

Require a specific user

You can also require that only a specific user may be logged in to call your function - calls from other users will be rejected. For example:

@anvil.pico.callable(require_user="ian@anvil.works")
def toggle_led():
    # This call will only run if invoked by ian@anvil.works.
    led.toggle()

Get the logged-in user

You can get the email address of the currently logged-in user by calling await anvil.pico.get_user_email(). This function returns None if nobody is logged in.


Async

If you want to use async code (uasyncio) in your function, then use @anvil.pico.callable_async instead of @anvil.pico.callable.

For example:

import uasyncio as a

@anvil.pico.callable_async
async def blink_led():
    for i in range(20):
        led.toggle()
        await a.sleep_ms(50)

HTTP endpoints

If you want to give your Pico W an HTTP API, you can! You’ll need to set up a Server Module with an HTTP endpoint that calls the relevant function on your Pico W.

For example, here’s an HTTP endpoint that calls toggle_led() from the Pico W:

# In a Server Module:

@anvil.server.http_endpoint("/blink")
def http_blink(**params):
    anvil.server.call("toggle_led")

And here is how the function is defined on your Pico W code:

# On your Pico W:

@anvil.pico.callable
def toggle_led():
    led.toggle()

Run code at startup

If you want to run code as soon as the Pico W connects to Anvil, then pass a task as an argument to anvil.pico.connect(). For example:

# Define a task to run after the Anvil Uplink has connected
async def flash_led_for(seconds):
    for i in range(seconds):
        led.toggle()
        await a.sleep_ms(100)
        led.toggle()
        await a.sleep(1)
    

# Connect the Anvil Uplink. In Micropython, this call will block forever.

anvil.pico.connect(UPLINK_KEY, flash_led_for(30))

Run code on every connection

If you want to run code each time the Pico reconnects to Anvil – rather than just once – specify on_every_connect= instead. This is useful if, for example, you’re using the Client Uplink and want to authenticate when your device connects.

Note: If you do this, you’ll have to create a Server Function to use the anvil.users module, as it isn’t available in Micropython. This is further explained in the Work Around Micropython’s Limitations section below.

Example: Authenticating a device at login

USERNAME="someone@example.com"
PASSWORD="1234"

async def do_login():
    await anvil.pico.call("authenticate_device", USERNAME, PASSWORD)

anvil.pico.connect(UPLINK_KEY, on_every_connect=do_login())

And then in a Server Module in your Anvil app:

import anvil.users

@anvil.server.callable
def authenticate_device(username, password):
    # Called each time a device connects
    anvil.users.login_with_email(username, password)

Work Around Micropython’s Limitations

Micropython on the Pico W is quite a limited environment, with few libraries and only a few kilobytes of RAM. Consequently, your Pico W code can’t directly access Anvil’s Data Tables, the Email Service, use Portable Classes, or define HTTP endpoints.

However, Anvil’s Server Modules can do all of those things, because they run in a proper Python environment! So when you want to do something that doesn’t fit on the Pico, define a Server Function to do it, then use anvil.pico.call() to invoke it from your Pico.

For example, you could write a Server Function that adds a row to a Data Table called “Visitors”, with “name” (string) and “when” (date/time) columns:

from datetime import datetime

@anvil.server.callable
def record_visitor(name):
    app_tables.visitors.add_row(name=name, when=datetime.now())

And you can call it from your Pico W:

async def register_visitor(name):
    await anvil.pico.call("record_visitor", name)