NAV

Anvil Reference

Welcome to the Anvil Reference Guide.

If you're new to Anvil, you might want to start with the tutorials and how-to guides in the Learning Centre .

Or, if you've got the basics and you want specific information, browse through our reference guide.

Wherever you see the help symbol , you can click on it for more help.

Introduction

Anvil apps are written in the Python programming language. You don't need much Python experience to use Anvil - you can learn as you go along. That said, there are many good Python resources out there on the internet. Here are a few we'd recommend:

You do not need to know anything about HTML, Javascript or website development to use Anvil. Instead, Anvil apps are built out of components - like Buttons, Labels and TextBoxes. Using the Anvil editor, you can drag and drop components onto a "Form", which is like a page of an Anvil app. Arrange the components as you like, without writing any code.

Double-click any button or open the Code tab, however, and you will discover that every Form is also a Python class. You write Python methods that run when events occur (for example, when a particular button is clicked).

Anvil also gives you powerful and simple ways to use external services, such as Google Docs. Putting together an app to manage customer data, perform repetitive calculations, or draw a graph couldn't be easier.

So what are you waiting for? Let's start exploring...

Contents

This is a reference guide; so you probably don't want to read everything at once. Instead, we suggest working through the Getting Started guide . Once you have built a simple app, you can learn by exploring.

The reference guide is divided into sections:

First, we talk about the Anvil app editor. This is the nuts and bolts of creating an app, adding forms and designing their appearance.

Then, we talk about Forms and components. Forms are how you design your user interface - what your app looks like. We go through the details of available components (Buttons, Labels, TextBoxes, and so on), and how to use each of them.

The Anvil App Editor

The Anvil app editor is where you build your apps. This section will walk you through all the parts of the editor.

Sign In and App List

App list

Once you've signed in to Anvil, you will see all the apps saved for your account. If you click Create new app..., you can create a new, blank app, or pick from a set of example templates that demonstrate different Anvil features.

Anvil editor

Once you have selected (or created) an app, it will open in the Anvil editor.

App Browser

Screenshot of app browser

The app browser shows the building blocks of your project.

A project can contain Forms, Modules, Server Modules and Services.

There is also a section for customising your app's Theme.

app browser show/hide

Click the button at the top-left of the screen to show or hide the app browser.

Gear Icon

Screenshot of Gear Icon menu

If you click the gear icon in the App Browser, you can configure your app:

  • Rename changes your app's name.
  • Publish app lets you give users access to your running app over the internet.
  • Share app is where you can allow other developers to clone this app.
  • Version history lets you see and configure all versions of your app.
  • App logs lets you see all console (print) output from your app.
  • Uplink lets you configure the uplink for your app.
  • Dependencies lets you import other apps and make this app available as a library.
  • Delete deletes your app entirely.

Forms

Forms are the pages of your Anvil app. They have a visual design that describes how they look, and a Python class that describes how they behave.

The form with the symbol next to it is the startup form. This is the first page displayed when your Anvil app is opened.

Read more about forms

Modules

You can write Python modules and add them to your Anvil app. Python modules are files of Python code that you can import and use from forms, other modules, or server modules.

Read more about modules

Server Modules

Server modules are Python modules that execute on the Anvil server, rather than in your user's web browser. Server modules cannot be seen or tampered with by your users, so they are good for code that must be trusted, or contains secret information.

Read more about server-side modules

Services

Services are add-ons for your Anvil app:

Version History

Screenshot of version history dialog

The version history dialog shows you the history of your app. Each time you save a version, a new entry is made in this list. Setting a name for a version when you save makes it easier for you to remember it later.

You can go back to a previous version by clicking Restore this version. This lets you try changes, and then back them out if they don't work.

Publishing

You can also publish a particular version of your app, by clicking Publish. This means that anyone who visits your app sees that version, whatever you're doing in the editor. This lets you carry on working on an app, even while other people are using it.

Git export

Each Anvil app is represented by a Git repository. If you click the Clone with Git button, you will be able to clone your Anvil app onto your own machine, edit it, and even push new versions up to Anvil.

The master branch represents the version of the app in the Anvil editor. If a published version is selected, this is represented by the published branch.

Output Console

Output console

This is where the output of print statements go. Any errors or exceptions that are not caught by your app are also printed here.

Click the output show/hide button at the top-left of the screen to show or hide the output console.

Form Editor

Screenshot

The form editor has two modes.

In Design mode, it shows you how your form will look when your app is running. You can use the Toolbox to add components such as buttons and text boxes, and click and drag to move them around on the page.

Not every component that appears on your form is visible. Invisible components can still raise events, but they will not be displayed to the user. When in Design mode, invisible components are displayed in a special area beneath the form.

Screenshot

In Code mode, you see that each form is also a Python class. You can write any code you like in this class.

When you set an event handler in Design mode (for example the "button click" event), Anvil will create a corresponding method in your class. This method will be called whenever that event occurs.

Toolbox

Screenshot

The toolbox appears in Design mode. It contains components you can add to your forms.

Click on a component type to select it, and then click on your form to place it.

Property Table

Property table

The property table allows you to configure components on your Anvil form. It appears in Design mode.

Code snippets

Screenshot of code snippets

When you are in Code mode, in the right-hand column you will see code snippets.

Select a component to see a list of properties, then click on a property for more details and an example snippet for updating that property.

In this example, we have selected a component called btn_say_hello, and we are looking at the text property.

Forms

A Form can be thought of as a 'page' in Anvil. But a Form is not always the entire page. A Form can also be used within another Form, which means Anvil apps can be built in a modular way by putting Forms together.

The editor shows a list of Forms in the app browser. You can click on each Form to see it in the Design view, move components around on it and drop components into it.

In the Code view, a Form is represented by a Python class. Its attributes and methods define how the app behaves. You can write Python code that runs when you click a button, edit a text box, or interact with your app in any other way.

About components

Components are the things you add to your Form. These include Buttons, Labels, TextBoxes and similar widgets.

When you design a Form in the form editor, you place components on the Form by dragging and dropping them. When a new instance of your Form is created, Anvil automatically creates all of those components, places them visually on the Form, and creates variables (attributes) on the Form object that refer to those components.

This is why, when you add a Button to your Form in the designer, and give it the name button_1, the Button appears on the screen and you can refer to it within your code as self.button_1. The result is that you can write simple code like self.button_1.text = "Click me", and the display will be updated appropriately.

However, this isn't the only way to do it. Components are just Python classes, so you can construct new instances and add them to your Form, entirely in code. (We'll show you how in a moment.)

There are three major concepts that allow you to take control of components:

  • Properties. These are the attributes of the component.
  • Events. These are what the component does.
  • Data Bindings. These are a good way to define the relationship between a component's attributes, and data.

Properties

Components have attributes that determine how they look and how they behave - we call these properties.

While you're designing a form, you can see and change a component's properties in the property table.

print self.my_textbox.text

But you can also read and change them in your Python code, while your app is running. For example, TextBox components have a text property (the text in the box). If you have a TextBox called my_textbox, you can write code like this.

self.my_textbox.text = "Foo"
self.my_textbox.tag.foo = "bar"

Every component also has a property called tag, which is for you to store any extra data you like. By default, it's an empty object that you can store attributes on.

Events

Components raise events when something happens. For example, a Button raises a "click" event when it is clicked.

Handling events

def handle_click(**event_args):
  print "The button got clicked!"

If you select a component in the form designer, you can find a list of its events in the property table. By selecting an event in the property table, you can set up an event listener - a method or function that gets called when the event occurs on that component.

Event handlers location

The event_args argument

Useful event data is passed to the event listener as the **event_args argument. (The ** soaks up any extra keyword arguments that come with the event, and puts them all in a dict.)

event_args always contains:

  • event_name, for example 'click'
  • sender, the Python object that triggered the event. For example, a click event triggered by a Link will have sender as the Link object. You could use this to style the Link differently to show that it has been followed.

Other event_args may be present depending on the event, for example the precise mouse position. To discover what is available in **event_args for a particular event, run print(event_args) at the top of the event handler, run your app and take a look at the output console.

Programmatically listening for and raising events

self.b.set_event_handler("click", handle_click)

You can also listen for events programmatically, by calling set_event_handler(event_name, fn) on any component. If you're unsure of the event_name, you can figure it out from the **event_args of a similar event.

# Raise a custom event on this form
self.raise_event("x-my-event", some_param="some value")

You can raise events on any component (including a Form you've defined) by calling component.raise_event(event_name, extra_args...).

You're not limited to the built-in event names. If you want to raise a custom event, begin the name with x- to ensure the name doesn't conflict with any built-in Anvil events.

# This is a method to handle the event
def handle_my_event(self, some_param, **event_args):
  print "Event occurred: %s" % some_param

# Add a custom component to our form, and trigger the event
# handler when it fires a custom event
f = Form1()
self.add_component(f)
f.set_event_handler("x-my-event", self.handle_my_event)

Any keyword arguments you pass to raise_event() will be passed on to the event handler function. The sender argument will be set to the component on which raise_event() was called.

Raising an event on all children

To raise an event on all children of a container, call container.raise_event_on_children(event_name, extra_args...). This takes exactly the same parameters as component.raise_event, and is subject to the same constraints on event names. For example, it is especially useful when you want to signal to all children of a RepeatingPanel without already having explicit references to those children.

Data Bindings

Data Bindings are a way of keeping a component's properties in sync with the underlying data with minimal effort.

A Data Binding associates a property of a component with a single Python expression. This association is defined in a single place - the Properties dialog:

Data binding location

All bound properties for a particular Form are updated whenever:

  • self.refresh_data_bindings() is called, or
  • self.init_components(**properties) is called, or
  • the self.item for the Form is set.

Creating a Data Binding

To create a Data Binding, click "+ Add" in the Data Bindings section of the property table. You'll see something like this (assuming self.item is a dictionary with name, phone and address in it):

Create data binding

If you were to choose 'name', then the text property of this Button would be bound to self.item['name'].

With this Data Binding set up, every time self.refresh_data_bindings() is called, the text property would get set to self.item['name'].

Data Bindings can only access attributes and methods of self - they cannot access variables in the global scope. If you want to modify variables in the global scope, you can create accessor methods on self.

The item property

As well as properties that relate to UI such as spacing_above, components have a special property meant specifically for storing data of any kind - item.

This can be set to anything - a Data Tables row, a custom dictionary, any Python object. (Remember, in Python "everything is an object.")

It's useful for Data Bindings because it's a way for components to hold data and pass it around.

Whenever self.item changes, all Data Bindings for that Form are updated.

Two-way Data Bindings - writing to Python objects from component properties

For input components such as TextBox or DatePicker, Data Bindings can be two-way.

This means that when a user changes the property, the Python object it's bound to will be updated as part of the processing of relevant events.

For example, binding the text property of a TextBox to self.item['name'] means that self.item['name'] will be set to whatever the user has entered in the TextBox, right before the pressed_enter or lost_focus events are processed.

You need to tick the "Write back" checkbox to set this up:

Two-way data binding

Using Data Bindings to display Data Tables data

One common use of data bindings is to display the same Form repeatedly, once for each row in a Data Table.

for row in app_tables.my_table.search():
  self.column_panel_1.add_component(MyForm(item=row))

Create a Form that displays the row data how want by setting up Data Bindings on each of its components that use the self.item property of the Form.

Then you can iterate over the rows in the table, creating a new instance of your Form for each row. In the code example, we're adding the Form instances into a ColumnPanel using add_component.

You can save yourself the for loop, and do this automatically, with a RepeatingPanel. RepeatingPanels have a special property called items, and for each item in items, they instantiate a particular Form based on a template you specify. See the docs on RepeatingPanels for more detail.

Watch our tutorial on storing and displaying data for an example.

Why are Data Bindings useful?

To see why Data Bindings save effort, consider an app where the user enters a number N into a TextBox, presses a button, and a times table is displayed for that number (N is 4 in the image):

Data Binding times tables

  def button_1_click(self, **event_args):
  """This method is called when the button is clicked"""
  self.refresh_data_bindings()

If you want your times tables to go up to 12 x N, you can create 12 Labels and bind each of their text properties to 1 * self.item['N'] (for the first one) through 12 * self.item['N'] (for the last one).

To set what N is, you can bind the text of the TextBox to self.item['N'] with 'Write back' enabled. Then to update the Labels, just run self.refresh_data_bindings() when the Button is clicked.

This means your click handler is just one line. Without Data Bindings, this method would need 12 very similar lines like self.text_box_3.text = 3 * self.item['N']. Crucially, the definition of what the Labels mean is kept with the Label, rather than being defined in the Button's click handler.

On top of that, if you want to have multiple ways of updating the Labels, you don't have to reproduce the definitions of what the Labels mean. Let's say you wanted to make the times tables change when somebody sends an SMS to the app. Instead of copying all those self.text_box_3.text = 3 * self.item['N'] lines into the SMS handler, you can just make it do self.refresh_data_bindings() and everything will be updated appopriately.

Basic components

This is a list of the basic components available in Anvil.

To see the available properties and events of each component, create one in the designer and examine it in the property table. (Hover your mouse over the name of each property or event for more information.)

Button

# Create a button

b = Button(text="This is a button")

This is an Anvil button. Drag and drop onto your form, or create one in code with the Button constructor:

Screenshot

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • text - string - The text displayed on this component
  • align - string - The position of this button in the available space. (One of left, center, right or full; Default: center)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • icon - icon - The icon to display on this component. Either a URL, or a FontAwesome Icon, e.g. 'fa:user'.
  • icon_align - string - The alignment of the icon on this component. Set to 'top' for a centred icon on a component with no text. (One of left_edge, left, top, right or right_edge; Default: left)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • click - When the button is clicked
  • show - When the button is shown on the screen
  • hide - When the button is removed from the screen

Label

c = Label(text="This is my label")

Labels are useful for displaying text on a form. The user cannot edit text in a label.

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • icon - icon - The icon to display on this component. Either a URL, or a FontAwesome Icon, e.g. 'fa:user'.
  • icon_align - string - The alignment of the icon on this component. Set to 'top' for a centred icon on a component with no text. (One of left_edge, left, top, right or right_edge; Default: left)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the label is shown on the screen
  • hide - When the label is removed from the screen
c = Link(text="Go to Anvil",
         url="https://anvil.works")
self.add_component(c)

Links allow users to navigate to different parts of your app, or to other websites entirely. If a Link's url property is set, it will open that URL in a new browser tab.

c = Link(text="Click me")
c.set_event_handler('click', ...)

If a link's url property is not set, it will not open a new page when it is clicked. It will, however, still trigger the click event.

m = BlobMedia('text/plain', 'Hello, world!', name='hello.txt')
c = Link(text='Open text document', url=m)

If a link's url property is set to a Media object, it will open or download that media in a new tab.

def link_1_click(self, **event_args):
  """This method is called when the link is clicked"""
  form1 = Form1()
  open_form(form1)

You can link to another Form by setting the click event handler to run open_form on an instance of the form you want to open.

Properties

  • url - string - The target URL of the link. Can be set to a URL string or to a Media object.
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • icon - icon - The icon to display on this component. Either a URL, or a FontAwesome Icon, e.g. 'fa:user'.
  • icon_align - string - The alignment of the icon on this component. Set to 'top' for a centred icon on a component with no text. (One of left_edge, left, top, right or right_edge; Default: left)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • click - When the link is clicked
  • show - When the link is shown on the screen
  • hide - When the link is removed from the screen

CheckBox

c = CheckBox(text="Option 1")

A checkbox allows a boolean choice - on or off.

Screenshot

Properties

  • checked - boolean - The status of the checkbox
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • change - When this checkbox is checked or unchecked
  • show - When the checkbox is shown on the screen
  • hide - When the checkbox is removed from the screen

RadioButton

rb1 = RadioButton(text="Select this option")

Radio buttons force users to select exactly one option from a list.

Radio buttons can be divided into groups by setting the group_name property. The user can select one option from each group. (If you don't set group_name, then all your radio buttons will be in the default group.)

rb1.group_name = "mygroup"
rb1.value = "A"

rb2 = RadioButton(text="Or select this one", group_name="mygroup", value="B")

# Add these radio buttons to the form
self.add_component(rb1)
self.add_component(rb2)

rb1.selected = True
print rb1.get_group_value()  # prints A

rb2.selected = True # This will deselect the other button
print rb2.get_group_value()  # prints B

You can either find out each individual RadioButton's status by checking the selected property, or you can call the get_group_value() method to find out which button in that group is pressed. (get_group_value() will return the value property of the currently selected RadioButton in that group. If there are multiple RadioButtons in the same group with the same value, then you can't tell them apart this way!)

Screenshot

Properties

  • selected - boolean - The status of the radio button
  • value - string - The value of the group when this radio button is selected
  • group_name - string - The name of the group this radio button belongs to.
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • clicked - When this radio button is selected
  • show - When the radiobutton is shown on the screen
  • hide - When the radiobutton is removed from the screen

# Create a DropDown

b = DropDown(items=["Item 1", "Item 2"])

This is an Anvil drop-down. Drag and drop onto your form, or create one in code with the DropDown constructor:

Screenshot

If the items property can be a list of strings (["One", "Two", "Three"]), or a list of 2-tuples ([("First Option", 0), ("Second Option", 1)]).

If you use a list of strings, the selected_value property always returns the currently selected string.

# Assuming 'worksheet' is a Google Sheet with columns 'name' and 'age':

self.drop_down_1.items = [(r["name"],r) for r in worksheet.rows]


# Later, probably in the DropDown 'change' event handler:

row = self.drop_down_1.selected_value
self.lbl_name.text = row["name"]
self.lbl_age.text = row["age"]

If you use a list of 2-tuples ([("First Option", 0), ("Second Option", 1)]) then the first element of each tuple is displayed in the drop down box. When a value is selected, the second element of the tuple becomes the selected_value property. This is particularly useful if you wish to, for instance, choose from a list of spreadsheet rows:

Properties

  • items - text[] - The items to display in this dropdown. (Default: ``)
  • selected_value - object - The value of the currently selected item. Can only be set at runtime. (Default: None)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • align - string - The position of this dropdown in the available space. (One of left, center, right or full; Default: full)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • change - When an item is selected
  • show - When the dropdown is shown on the screen
  • hide - When the dropdown is removed from the screen

DatePicker

c = DatePicker()

The DatePicker allows users of your app to select dates and times from a drop-down calendar.

The date property allows you to get and set the displayed date. Whatever you set it to, it will always return a datetime.date or datetime.datetime object, depending on the value of the pick_time property. You may set the date property in any of the following ways:

c = DatePicker(format="%d %m %Y")

# Set to a string
c.date = "27 09 2016"

# Set to an ISO string
c.date = "2016-09-27"

# Set to a datetime.date
import datetime
c.date = datetime.date.today()

# Set to a datetime.datetime
c.pick_time = True
c.date = datetime.datetime.now()
  • To a string in the format specified by the format property
  • To an ISO8601-formatted date
  • To a datetime.date object
  • To a datetime.datetime object (if pick_time is True)
  • To the string "now", causing the DatePicker to display the current time when it is loaded.

The min_date and max_date properties can be set in the same ways.

When the pick_time property is True, date expects and returns a datetime.datetime object. Otherwise it expects and returns a datetime.date object.

When selecting times, the returned datetime.datetime object has its timezone (tzinfo) explicitly set to the timezone of the user's browser.

Properties

  • date - string - The date selected on this component.
  • format - string - The format in which to display the selected date.
  • pick_time - boolean - Whether the user should be able to select a time as well as a date (Default: false)
  • min_date - string - The minimum date the user can select.
  • max_date - string - The maximum date the user can select.
  • placeholder - string - A string to display when the DatePicker is empty.
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • change - When the selected date changes
  • show - When the datepicker is shown on the screen
  • hide - When the datepicker is removed from the screen

TextBox

c = TextBox(text="Some editable text")

Text boxes allow users to edit text on your forms.

A TextBox can only edit a single line of text. If you want to accept multiple lines of input, use a TextArea

Screenshot

Set a TextBox to have focus by calling its focus() method. Select all its text with the select() method.

The type property of a TextBox is a hint to the browser, which may constrain or suggest how to edit a text box (for example, only allowing digits in a number TextBox, or displaying a dial-pad when editing a tel TextBox).

When a TextBox's type property is set to "number", its text property will always return either a number or None.

The text property of a TextBox can trigger write-back of data bindings. This occurs before the lost_focus and pressed_enter events.

Properties

  • placeholder - string - The text to be displayed when the component is empty.
  • hide_text - boolean - Display stars instead of the text in this box (Default: false)
  • type - string - What type of data will be entered into this box? (One of text, number, email, tel or url; Default: text)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • change - When the text in this text box is edited
  • pressed_enter - When the user presses Enter in this text box
  • show - When the textbox is shown on the screen
  • hide - When the textbox is removed from the screen
  • focus - When the textbox gets focus
  • lost_focus - When the textbox loses focus

TextArea

c = TextArea(text="Some editable text\nacross multiple lines")

Text areas are text boxes that can contain multiple lines of text

Screenshot

Set a TextArea to have focus by calling its focus() method. Select all its text with the select() method.

The text property of a TextArea can trigger write-back of data bindings. This occurs before the lost_focus event.

Properties

  • placeholder - string - The text to be displayed when the component is empty.
  • auto_expand - boolean - If true, the text area will expand vertically to fit its contents (Default: false)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • change - When the text in this text area is edited
  • show - When the textarea is shown on the screen
  • hide - When the textarea is removed from the screen
  • focus - When the textarea gets focus
  • lost_focus - When the textarea loses focus

Timer

c = Timer(interval=0.1)

The timer raises its tick event repeatedly, waiting a specified length of time in between each event.

This allows you to run a particular piece of code repeatedly (for example, updating an animation).

The timer is an invisible component, meaning that users will not see it on the page when you run the app.

Properties

  • interval - number - The number of seconds between each tick. 0 switches the timer off. (Default: 0.5)

Events

  • tick - Every [interval] seconds. Does not trigger if [interval] is 0.
  • show - When this timer's form is shown on the screen (or it is added to a visible form)
  • hide - When this timer's form is hidden from the screen (or it is removed from a visible form)

Spacer

c = Spacer(height=50)

Spacers add empty space to a form. Use them to fill a column with blank space, or to make vertical space on your form.

Properties

  • visible - boolean - Should this component be displayed? (Default: true)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the spacer is shown on the screen
  • hide - When the spacer is removed from the screen

Image

c = Image(source="http://www.example.com/image.jpg")

Image components display an image on a form. To manipulate images, see the Image module.

Which image is displayed is determined by the source property. If this is set to a string, the image will be loaded from a URL given by that string.

c.source = anvil.google.drive.app_files.my_image_jpg

However, you can also work with images in code using Media objects. For example, Google Drive files are Media objects, so you can display an image from Google Drive directly in an Image component.

If you read the source attribute of an Image, you will always see a Media object, even if you set it to a string. (Anvil automatically generates a URLMedia object for that URL.)

Properties

  • display_mode - string - Determines how the image's size should be adjusted to fit the size of this Image component (One of shrink_to_fit, zoom_to_fill, fill_width or original_size; Default: shrink_to_fit)
  • vertical_align - string - Position the image vertically within this component (One of top, center or bottom; Default: center)
  • source - uri - The image source - set a string for a URL or a Media object in code
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the image is shown on the screen
  • hide - When the image is removed from the screen
  • mouse_enter - When the mouse cursor enters this component

    Parameters:

    • x - The x coordinate of the mouse pointer, within this component
    • y - The y coordinate of the mouse pointer, within this component
  • mouse_leave - When the mouse cursor leaves this component

    Parameters:

    • x - The x coordinate of the mouse pointer relative to this component
    • y - The y coordinate of the mouse pointer relative to this component
  • mouse_move - When the mouse cursor moves over this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
  • mouse_down - When a mouse button is pressed on this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
    • button - The button that was pressed (1 = left, 2 = middle, 3 = right)
  • mouse_up - When a mouse button is released on this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
    • button - The button that was released (1 = left, 2 = middle, 3 = right)

FileLoader

c = FileLoader()

A FileLoader allows you to load files from your computer or mobile device into an Anvil app.

if c.file != None:
    self.my_image.source = c.file

The currently selected file in a file loader can be accessed from the file attribute. It is a Media object, so you can use it to draw images, upload it to Google Drive, and so on.

The files attribute gives a list of files. If the multiple property is set, then multiple files may be loaded; otherwise this list will always either be empty or contain one element.

upload_folder = anvil.google.drive.app_files.uploads
for f in c.files:
    small_img = anvil.image.generate_thumbnail(f, 640)
    uploaded = upload_folder.create_file(f.name)
    uploaded.set_media(small_img)

This example uses the Google Drive API to upload images to an app folder. Before upload, they are resized using the Image module. For other things you can do with uploaded files, see the Media object documentation.

c.clear()

To reset the file-loader and make it ready to receive more files, call its clear() method.

Properties

  • multiple - boolean - If True, this FileLoader can load multiple files at the same time (Default: false)
  • show_state - boolean - If True, display a message describing selected files. (Default: true)
  • file - object - The currently selected file (or the first, if multiple files are selected). This is a Media object.
  • files - object - A list of currently selected files. Each file is a Media object.
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • icon - icon - The icon to display on this component. Either a URL, or a FontAwesome Icon, e.g. 'fa:user'.
  • icon_align - string - The alignment of the icon on this component. Set to 'top' for a centred icon on a component with no text. (One of left_edge, left, top, right or right_edge; Default: left)
  • enabled - boolean - True if this component should allow user interaction. (Default: true)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • change - When a new file is loaded into this FileLoader

    Parameters:

    • file - The first selected file. Set the 'multiple' property to allow loading more than one file.
    • files - A list of loaded files. Set the 'multiple' property to allow loading more than one file.
  • show - When the fileloader is shown on the screen
  • hide - When the fileloader is removed from the screen
  • focus - When the fileloader gets focus
  • lost_focus - When the fileloader loses focus

GoogleMap

map = GoogleMap()

map.center = GoogleMap.LatLng(52.2053, 0.1218)
map.zoom = 13

You can display interactive Google Maps on your Anvil form with the GoogleMap component.

marker = GoogleMap.Marker(
  animation=GoogleMap.Animation.DROP,
  position=GoogleMap.LatLng(52.2053, 0.1218)
)

map.add_component(marker)

GoogleMap components are containers. You can add various Map Overlay components to a map. In this example, we add a marker at a particular position and with a 'drop' animation.

def marker_click(sender, **event_args):
  i =GoogleMap.InfoWindow(content=Label(text="This is Cambridge!"))
  i.open(map, sender)

marker.set_event_handler("click", marker_click)

Markers (and other overlays) can respond to mouse events. Here we display an InfoWindow anchored to the marker when the marker is clicked.

InfoWindows have a content property, which can be set to a string or an Anvil Component such as a label or an entire form.

  p = GoogleMap.Polyline(
    path=[
      GoogleMap.LatLng(52.215, 0.14),
      GoogleMap.LatLng(52.195, 0.12),
      GoogleMap.LatLng(52.21, 0.10),
    ],
    stroke_color='blue',
    stroke_weight=2,
    icons=[
      GoogleMap.IconSequence(
        icon=GoogleMap.Symbol(
          path=GoogleMap.SymbolPath.FORWARD_OPEN_ARROW,
          scale=2
        )
      )
    ]
  )

To draw a line between positions on a map, add a GoogleMap.Polyline component to the map. In this example, we also add an arrow icon.

Here is a complete list of the available map overlay components, and their most important properties:

  • GoogleMap.Marker - Mark a particular point on the map. Draws a red pin by default.

    Properties

    • animation: GoogleMap.Animation - Specifies the animation of this marker. Set to GoogleMap.Animation.DROP, or GoogleMap.Animation.BOUNCE.
    • icon: GoogleMap.Symbol - Specifies the icon to display. If unset, a red pin is displayed.
    • label: GoogleMap.MarkerLabel | String - Describes the text label of this marker.
    • position: GoogleMap.LatLng - Specifies the position of this marker.

      Learn more about Markers in the Google Maps documentation

  • GoogleMap.InfoWindow - Display a popup on the map at a particular position.

    Properties

    • position: GoogleMap.LatLng - Specifies the position of the popup. Not required if this popup is anchored to a component (see the open method, below).
    • content: anvil.Component | string - The content of the popup. Can be a string, or an Anvil Component.

    Methods

    • open(map, [anchor]) - Display this InfoWindow on the specified map. If anchor is specified, the InfoWindow does not need to have its own position property set.
    • close() - Hide this InfoWindow. The user can also cause this to happen by clicking the close button in the top-right of the popup.

      Learn more about InfoWindows in the Google Maps documentation

  • GoogleMap.Polyline - Draw a line on the map.

    Properties

    • icons: list of GoogleMap.IconSequence - Specifies the icons to display along this line.
    • path: list of GoogleMap.LatLng - A list of points along the line.
    • geodesic: boolean - Whether this line should follow the curvature of the Earth. Default False.

      Learn more about Polylines in the Google Maps documentation

In addition to the properties specified above, all overlays have clickable, draggable, and visible properties. Overlays with outlines also have editable, stroke_color, stroke_weight and stroke_opacity properties. Those with area have fill_color and fill_opacity properties. See the official Google Maps documentation for more details.

Data Visualisation

map.map_data.add(GoogleMap.Data.Feature(
  geometry=GoogleMap.Data.Point(
    GoogleMap.LatLng(52.2,0.1))))

map.map_data.add(GoogleMap.Data.Feature(
  geometry=GoogleMap.Data.Point(
    GoogleMap.LatLng(52.21,0.12))))

map.map_data.add(GoogleMap.Data.Feature(
  geometry=GoogleMap.Data.Point(
    GoogleMap.LatLng(52.201,0.135))))

map.map_data.style = GoogleMap.Data.StyleOptions(
  icon=GoogleMap.Symbol(
    path=GoogleMap.SymbolPath.CIRCLE,
    scale=30,
    fill_color='red',
    fill_opacity=0.3,
    stroke_opacity=1,
    stroke_weight=1
  )
)

It is possible to use the Data Layer to visualise location-based data instead of Overlays. In this example, we add point features to the map_data object, then set a rendering styles for all features at once.

def get_style(feature):
  point = feature.geometry.get()
  color = 'red' if point.lng() < 0.12 else 'blue'

  return GoogleMap.Data.StyleOptions(
    icon=GoogleMap.Symbol(
      path=GoogleMap.SymbolPath.CIRCLE,
      scale=30,
      fill_color=color,
      fill_opacity=0.3,
      stroke_opacity=1,
      stroke_weight=1
    )
  )

map.map_data.style = get_style

We could also generate feature styles dynamically by assigning a 'styling function' to the map_data.style property. The function will be called once for every feature, and should return a corresponding style. In this example, we choose the color of the circle based on the longitude of the feature.

Here we used GoogleMap.Data.Point geometries, but there are several others available:

  • GoogleMap.Data.Point(lat_lng) - Features are respresented by a particular position.
  • GoogleMap.Data.MultiPoint([lat_lng_1, lat_lng_2, ...]) - Features are respresented by a set of positions.
  • GoogleMap.Data.LineString([lat_lng_1, lat_lng_2, ...]) - Features are respresented by a line with specified vertices.
  • GoogleMap.Data.MultiLineString([line_string_1, line_string_2, ...]) - Features are respresented by a multiple LineString geometries.
  • GoogleMap.Data.LinearRing([lat_lng_1, lat_lng_2, ...]) - Features are respresented by a closed loop of vertices.
  • GoogleMap.Data.Polygon([linear_ring_1, linear_ring_2, ...]) - Features are respresented by a set of closed loops. Additional loops denote 'holes' in the polygon.
  • GoogleMap.Data.MultiPolygon([polygon_1, polygon_2, ...]) - Features are respresented by multiple polygons.
  • GoogleMap.Data.GeometryCollection([geometry_1, geometry_2, ...]) - Features are respresented by an arbitrary set of geometries defined above.

See the official Google Maps documentation for more information.

Geocoding

results = GoogleMap.geocode(address="Cambridge, UK")

m = Marker(position=results[0].geometry.location)
map.add_component(m)

You can look up addresses from locations, and vice-versa, using the GoogleMap.geocode function. This function returns a GoogleMap.GeocoderResult array. In this example, we look up a location from an address, then display a marker.

results = GoogleMap.geocode(
  location=GoogleMap.LatLng(52.2053, 0.1218)
)

print results[0].formatted_address

In this example, we look up an address for a given lat/long.

There are several other properties available on the GoogleMap.GeocoderResult object. See the official Google documentation for details.

Utilities

# Calculate the length of the line.
GoogleMap.calculate_length([
  GoogleMap.LatLng(52.215, 0.14),
  GoogleMap.LatLng(52.195, 0.12),
  GoogleMap.LatLng(52.21, 0.10),
])

# Calculate the area of the polygon.
GoogleMap.calculate_length([
  GoogleMap.LatLng(52.215, 0.14),
  GoogleMap.LatLng(52.195, 0.12),
  GoogleMap.LatLng(52.21, 0.10),
])

The GoogleMap Component provides utilities for calculating length and area:

  • GoogleMap.calculate_length([pos_1, pos_2, ...]) - returns the length of the line going through the specified points.
  • GoogleMap.calculate_area([vertex_1, vertex_2, ...]) - returns the area of the polygon with the specified vertices.

The GoogleMap Component

Properties

  • map_data - object - Map data
  • background_color - string - Color used for the background of the Map div. This color will be visible when tiles have not yet loaded as the user pans.
  • center - object - The Map center.
  • clickable_icons - boolean - When false, map icons are not clickable. A map icon represents a point of interest (Default: true)
  • disable_default_ui - boolean - Enables/disables all default UI. (Default: false)
  • disable_double_click_zoom - boolean - Enables/disables zoom and center on double click. (Default: false)
  • draggable - boolean - If false, prevents the map from being dragged. (Default: true)
  • draggable_cursor - string - The name or url of the cursor to display when mousing over a draggable map. (Default: auto)
  • dragging_cursor - string - The name or url of the cursor to display when the map is being dragged. (Default: auto)
  • fullscreen_control - boolean - The enabled/disabled state of the Fullscreen control. (Default: true)
  • fullscreen_control_options - object - The display options for the Fullscreen control.
  • gesture_handling - string - This setting controls how gestures on the map are handled. (Default: auto)
  • heading - number - The heading for aerial imagery in degrees measured clockwise from cardinal direction North. (Default: 0)
  • keyboard_shortcuts - boolean - If false, prevents the map from being controlled by the keyboard. (Default: true)
  • map_type_control - boolean - The enabled/disabled state of the Map type control. (Default: true)
  • map_type_control_options - object - The display options for the Map type control.
  • map_type_id - object - The map type ID. Defaults to MapTypeId.ROADMAP
  • max_zoom - number - The maximum zoom level which will be displayed on the map. (Default: 18)
  • min_zoom - number - The minimum zoom level which will be displayed on the map. (Default: 0)
  • rotate_control - boolean - The enabled/disabled state of the rotate control. (Default: true)
  • rotate_control_options - object - The display options for the rotate control.
  • scale_control - boolean - The enabled/disabled state of the scale control. (Default: true)
  • scale_control_options - object - The display options for the scale control.
  • scroll_wheel - boolean - If false, disables scrollwheel zooming on the map. (Default: true)
  • street_view_control - boolean - The enabled/disabled state of the street view control. (Default: true)
  • street_view_control_options - object - The display options for the street view control.
  • zoom - number - The initial Map zoom level. (Default: 2)
  • zoom_control - boolean - The enabled/disabled state of the zoom control. (Default: true)
  • zoom_control_options - object - The display options for the zoom control.
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • tag - object - Use this property to store any extra information about this component

Events

  • bounds_changed - when the viewport bounds have changed.
  • center_changed - when the map center property changes.
  • click - when the user clicks on the map.

    Parameters:

    • lat_lng - The position that was clicked.
    • pixel - The position that was clicked.
  • dbl_click - when the user double-clicks on the map.

    Parameters:

    • lat_lng - The position that was double-clicked.
    • pixel - The position that was double-clicked.
  • drag - This event is repeatedly fired while the user drags the map.
  • dragend - when the user stops dragging the map.
  • dragstart - when the user starts dragging the map.
  • heading_changed - when the map heading property changes.
  • idle - when the map becomes idle after panning or zooming.
  • maptypeid_changed - when the mapTypeId property changes.
  • mousemove - whenever the user's mouse moves over the map container.

    Parameters:

    • lat_lng - The position of the cursor.
    • pixel - The position of the cursor.
  • mouseout - when the user's mouse exits the map container.

    Parameters:

    • lat_lng - The position of the cursor.
    • pixel - The position of the cursor.
  • mouseover - when the user's mouse enters the map container.

    Parameters:

    • lat_lng - The position of the cursor.
    • pixel - The position of the cursor.
  • projection_changed - when the projection has changed.
  • rightclick - when the user right-clicks on the map container.

    Parameters:

    • lat_lng - The position that was right-clicked.
    • pixel - The position that was right-clicked.
  • tilesloaded - when the visible tiles have finished loading.
  • tilt_changed - when the map tilt property changes.
  • zoom_changed - when the map zoom property changes.
  • data_addfeature - when the viewport bounds have changed.

    Parameters:

    • feature - The feature that was added.
  • data_click - for a click on the geometry.

    Parameters:

    • feature - The feature that was clicked.
    • lat_lng - The position that was clicked.
  • data_dbl_click - for a double click on the geometry.

    Parameters:

    • feature - The feature that was double-clicked.
    • lat_lng - The position that was double-clicked.
  • data_mousedown - for a mousedown on the geometry.

    Parameters:

    • feature - The feature the mouse is over.
    • lat_lng - The position of the cursor.
  • data_mouseout - when the mouse leaves the area of the geometry.

    Parameters:

    • feature - The feature the mouse left.
    • lat_lng - The position of the cursor.
  • data_mouseover - when the mouse enters the area of the geometry.

    Parameters:

    • feature - The feature the mouse is over.
    • lat_lng - The position of the cursor.
  • data_mouseup - for a mouseup on the geometry.

    Parameters:

    • feature - The feature the mouse is over.
    • lat_lng - The position of the cursor.
  • data_removefeature - when a feature is removed from the collection.

    Parameters:

    • feature - The feature that was removed.
  • data_removeproperty - when a feature's property is removed.

    Parameters:

    • feature - The feature whose property was removed.
    • name - The name of the property that was removed.
    • old_value - The old value of the property that was removed.
  • data_rightclick - for a right-click on the geometry.

    Parameters:

    • feature - The feature that was right-clicked.
    • lat_lng - The position that was right-clicked.
  • data_setgeometry - when a feature's geometry is set.

    Parameters:

    • feature - The feature that was removed.
    • new_geometry - The geometry that was set.
    • old_geometry - The geometry that was replaced.
  • data_setproperty - when a feature's property is set.

    Parameters:

    • feature - The feature whose property was set.
    • name - The name of the property that was set.
    • new_value - The new value of the property that was set.
    • old_value - The old value of the property that was set.
  • show - When the googlemap is shown on the screen
  • hide - When the googlemap is removed from the screen

Plot

# Create a plot

b = Plot()

This is a Plotly Plot. Drag and drop onto your form, or create one in code with the Plot constructor.

Plots are interactive - move the mouse over a plot to see the available tools.

from plotly import graph_objs as go

# Configure the plot layout
self.plot_1.layout.title = 'Simple Example'
self.plot_1.layout.xaxis.title = 'time'
self.plot_1.layout.annotations = [
    go.Annotation(
      text = 'Simple annotation',
      x = 0,
      xref = 'paper',
      y = 0,
      yref = 'paper'
    )
]

# Plot some data
self.plot_1.data = [
  go.Scatter(
    x = [1, 2, 3],
    y = [3, 1, 6],
    marker = go.Marker(
      color= 'rgb(16, 32, 77)'
    )
  ),
  go.Bar(
    x = [1, 2, 3],
    y = [3, 1, 6],
    name = 'Bar Chart Example'
  )
]

Plots are configured using utility classes from the plotly.graph_objs module. Display a plot by setting the data and layout properties of the Plot component. The data property should be set to a list of traces as described in the plotly documentation. The layout property should be set to a dictionary describing the layout of the plot.

Plots are interactive by default. Create a static plot by setting the interactive property to False.

An example plot

See the Plotly documentation for more details and examples.

Properties

  • data - object - Plot traces
  • layout - object - Plot layout
  • interactive - boolean - Whether this plot should be interactive (Default: true)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component. (Default: 450)
  • visible - boolean - Should this component be displayed? (Default: true)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the plot is shown on the screen
  • hide - When the plot is removed from the screen

YouTubeVideo

ytv = YouTubeVideo(youtube_id="cbP2N1BQdYc", autoplay=True)

You can display YouTube videos on your Anvil form with the YouTubeVideo component.

Properties

  • youtube_id - string - The ID of the YouTube video to play
  • autoplay - boolean - Set to true to play this video immediately (Default: false)
  • loop - boolean - Set to true to play this video repeatedly (Default: false)
  • current_time - object - Get or set the current playback position, in seconds.
  • volume - object - Get or set the current volume, from 0 - 100.
  • state - object - Get the current playback state of the video as a string. E.g. PLAYING
  • duration - object - Get the duration of the video in seconds.
  • mute - boolean - Set whether the video is muted or not. (Default: false)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component

Canvas

c = Canvas()

Canvas components allow you to draw graphics on your form. They are ideal for animations and visualisations.

Colours

There are several ways to specify colours on a Canvas. You can give a hexadecimal colour in the form "#RRGGBB" (where "#FF0000" would be bright red), or specify red, green, blue and alpha values separately: "rgba(255,0,0,1)". It is also possible to specify gradients and patterns - see HTML5 Canvas Docs for details.

Style Attributes

  • stroke_style - The colour of lines drawn on the canvas
  • fill_style - The colour of solid fill on the canvas

Shadow Attributes

  • shadow_offset_x, shadow_offset_y

    How far shadows should be displaced from drawn shapes

  • shadow_blur - Blur amount, in pixels, for shadows

  • shadow_color - The colour of shadows

Line Attributes

  • line_width

    buttroundsquare
    Butt Round Square
  • line_cap

    The style of line ends. Takes one of these string values:

  • line_join

    The style of line joins. Takes one of these string values:

    roundbevelmiter
    Round Bevel Miter
  • miter_limit - Used to prevent miter corners from extending too far.

Text Attributes

 self.canvas1.font = "10px sans-serif"
  • font - The font to use when rendering text.

  • text_align

    How text should be aligned horizontally. Can be any of start, end, left, right, and center.

  • text_baseline

    How text should be aligned vertically. One of top, hanging, middle, alphabetic, ideographic and bottom.

Utility Methods

  • get_width() - Returns the width of the canvas, in pixels
  • get_height() - Returns the width of the canvas, in pixels

Context Methods

  • reset_context() - Should be called after resizing the canvas.

Transformation Methods

  • save() - Saves any transformations applied to the canvas so that they can be restored later.

  • restore() - Restores any saved transformations.

  • translate(x, y) - Updates the current transform so that all subsequent drawing commands are offset by (x, y) pixels.

  • rotate(angle) - Updates the current transform so that all subsequent drawing commands are rotated by angle radians.

  • scale(x, y) - Updates the current transform so that all subsequent drawing commands are scaled by a factor of (x, y).

  • transform(a, b, c, d, e, f) - Applies the given transformation matrix to the current transform.

  • set_transform(a, b, c, d, e, f) - Sets the current transform to the matrix provided.

  • reset_transform() - Resets the current transform to the identity matrix.

Text Methods

  • fill_text(text, x, y)

    Renders the string text at position (x, y).

  • stroke_text(text, x, y)

    Renders an outline of the string text at position (x, y).

  • measure_text(text)

    Returns the width, in pixels, of the string text as it would be if rendered on the canvas in the current font.

Rectangle-Drawing Methods

  • clear_rect(x, y, width, height) - Clears a rectangle

  • fill_rect(x, y, width, height) - Fills a rectangle with the current fill_style.

  • stroke_rect(x, y, width, height) - Draws the outline of a rectangle with the current stroke_style.

Path-Drawing Methods

c = self.canvas1

## Draw an outlined triangle

c.begin_path()
c.move_to(100,100)
c.line_to(200,150)
c.line_to(150,200)
c.close_path()

c.stroke_style = "#BB7722"
c.line_width = 5
c.fill_style = "#8888FF"

c.fill()
c.stroke()
  • begin_path()

    Tells the canvas that you are about to start drawing a shape.

  • close_path()

    Connects the most recent edge of the shape to the start point, closing the path.

  • fill()

    Fills the shape you have drawn in the current fill_style.

  • stroke()

    Draws the outline of the shape you have defined in the current stroke_style.

  • clip()

    Clips all subsequent drawing operations to the defined shape.

  • move_to(x, y)

    Move to position (x, y) without drawing anything, ready to start the next edge of the current shape.

  • line_to(x, y)

    Draw a line to position (x, y).

  • arc(x, y, radius, start_angle, end_angle, anticlockwise)

    Draw an arc to position (x, y) with the specified radius.

Saving your canvas as an image

img = self.my_canvas.get_image()

self.image_1.source = img

f = anvil.google.drive.app_files.my_file

f.set_media(c.get_image())

When you've drawn something on your canvas, you can get the contents of your canvas as a Media object by calling get_image(). You can then display this image in an Image components, or upload it to Google Drive.

For more details, see the Media object documentation.

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • show - When the canvas is shown on the screen
  • hide - When the canvas is removed from the screen
  • mouse_enter - When the mouse cursor enters this component

    Parameters:

    • x - The x coordinate of the mouse pointer, within this component
    • y - The y coordinate of the mouse pointer, within this component
  • mouse_leave - When the mouse cursor leaves this component

    Parameters:

    • x - The x coordinate of the mouse pointer relative to this component
    • y - The y coordinate of the mouse pointer relative to this component
  • mouse_move - When the mouse cursor moves over this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
  • mouse_down - When a mouse button is pressed on this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
    • button - The button that was pressed (1 = left, 2 = middle, 3 = right)
  • mouse_up - When a mouse button is released on this component

    Parameters:

    • x - The x coordinate of the mouse pointer within this component
    • y - The y coordinate of the mouse pointer within this component
    • button - The button that was released (1 = left, 2 = middle, 3 = right)

RepeatingPanel

RepeatingPanels are a mechanism for displaying the same UI elements repeatedly on the page. Typical uses for RepeatingPanels might be: a TODO list, a list of tickets in a ticketing system, a series of profiles in a dating website.

A RepeatingPanel creates a form for each of a list of items. It uses a template to generate a form for each item in the list.

   self.repeating_panel = RepeatingPanel()

   # You can use a search iterator from data tables
   self.repeating_panel.items = app_tables.people.search()

   # You can use a flat list
   self.repeating_panel.items = ['a', 'b', 'c']

   # You can use a more complex data structure... anything that can be iterated over
   self.repeating_panel.items = (
     {'name': 'Joe', 'age': 14},
     {'name': 'Sally', 'age': 8}
   )

The list of items is specified by setting the RepeatingPanel's items property to any iterable.

self.repeating_panel_1.item_template = "PeopleTemplate"
self.repeating_panel_1.items = [
  {'name': 'Joe', 'age': 14},
  {'name': 'Sally', 'age': 8}
]
# self.repeating_panel_1 now contains two instances of
# the PeopleTemplate form. The first has its `item`
# property set to {'name': 'Joe', 'age': 14}
# (ready for use in data bindings); the second
# has its `item` property set to
# {'name': 'Sally', 'age': 8}

There are two ways to specify what to use as the template. When you create a new RepeatingPanel in the form designer, Anvil will automatically create a new form called something like ItemTemplate1. You can either modify ItemTemplate1 as you would any normal form, or double-click on the RepeatingPanel to drop components directly into it.

Alternatively, you can attach any form you like to a RepeatingPanel. For example, let's say we have a form called PeopleTemplate. If we set the RepeatingPanel's item_template to "PeopleTemplate", it will create a new instance of PeopleTemplate for every element in its items list.

Each form instance is created with its self.item set to its corresponding element in the list. You can use self.item in data bindings or in the code for the template form.

The template has access to its parent RepeatingPanel in the code via self.parent. This can be useful if you want actions in the template form to have an impact on the whole RepeatingPanel. Let's say you want to refresh the entire RepeatingPanel when a button is clicked in the template form - you could use set_event_handler to bind an event called x-refresh-panel to the RepeatingPanel, then call self.parent.raise_event('x-refresh').

self.repeating_panel_1.items = app_tables.people.search()

A common use of RepeatingPanels is to create a table, with one row for each row in a data table. For example, if you had a table called 'people' with columns 'name' and 'age', you could drop two labels into the RepeatingPanel and assign the text of the first label to self.item['name'] and the text of the second label to self.item['age']. The labels in each row line up, causing a column effect. To create column headers, you can drop a ColumnPanel above the RepeatingPanel and put labels in as appropriate.

Screenshot

Properties

  • item_template - form - The name of the form to repeat for every item
  • items - object - A list of items for which the 'item_template' will be instantiated. (Default: None)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Containers

Some components can contain other components. We call these containers. (A form is also a container.)

xyp = XYPanel(width=400, height=400)
self.add_component(xyp)

lbl = Label(text="Hello")
xyp.add_component(lbl, x=10, y=10)

btn = Button(text="Click me!")
xyp.add_component(btn, x=10, y=100)

You can add any component to a container in Python by calling the method add_component(component, container_properties...).

For example, the following code sets up an XYPanel that is 400x400 pixels, adds it to the current form, and then adds two components to it:

btn.remove_from_parent()

To remove a component from its parent container, call its remove_from_parent() method.

my_container.clear()

Alternatively, you can remove all components from a particular container by calling clear() on the container:

components = my_container.get_components()
print "%d components" % len(components)

To get a list of all components currently in the container, call get_components():

my_container.raise_event_on_children("x-custom-event", foo=42)

To raise an event on all children of a component without referencing the children directly, call raise_event_on_children():

Container Properties

Container properties in editor

Container Properties are properties that control the relationship between a component and the container it is in; the argument names and their meanings depend on the type of container. They are visible in the Propeties panel in the Editor. They can also be passed as keyword arguments to add_component.

self.data_grid_1.add_component(DataRowPanel(), pinned=True)

For example, Data Row Panels inside a Data Grid have a pinned property that selects whether the Data Row Panel is visible on every page.

ColumnPanel

This container allows you to drag and drop components into rows and columns. ColumnPanel is the default layout used for Anvil forms you create.

Components added at runtime using the add_component method will each be added to their own row. In this respect, the ColumnPanel behaves the same as the LinearPanel at runtime. There are no special arguments to pass to add_component.

Like the LinearPanel, the ColumnPanel will expand to fit its contents.

Components in a ColumnPanel have several container properties, which can be set in the Properties dialog:

  • full_width_row: When True, this row of components will stretch to the full width of the screen. (Default False)

  • row_background: Set to a CSS color for the background of this row. Note that the background stretches to the full with of the browser. (Default: none)

Properties

  • col_widths - string - Custom column widths in this panel
  • wrap_on - string - The largest display on which to wrap columns in this panel (One of never, mobile or tablet; Default: mobile)
  • col_spacing - string - Space between columns (One of none, tiny, small, medium, large or huge; Default: medium)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • show - When the columnpanel is shown on the screen
  • hide - When the columnpanel is removed from the screen

FlowPanel

This container allows you to display components in lines with wrapping. Each component will only take up the horizontal space that it needs.

fp = FlowPanel(align="center", spacing="small")
# A button determines its own width
fp.add_component(Button(text="Click me"))
# A TextBox gets sized explicitly
fp.add_component(TextBox(), width=100)

Some components, like Buttons, dictate their own width in FlowPanels, based on their content. For components like TextBoxes, that don't have a width specified by their content, you can drag the handle to set the width you require.

If a component will not fit next to the previous component, it will wrap onto a new line.

To control the space between components, set the spacing property.

Like many other containers, the FlowPanel will expand vertically to fit its contents.

fp = FlowPanel()
fp.add_component(Button(text="Button"))
# Display the label to the left of the button:
fp.add_component(Label(text="Click here:"), index=0)

By default, components are added at the end of a FlowPanel. If you pass an index parameter to add_component(), the component will be inserted at the specified position. Index 0 is the first element in the panel, 1 is the second, etc.

Properties

  • align - string - Align this component's content (One of left, center, right or justify; Default: left)
  • spacing - string - Space between components (One of none, tiny, small, medium, large or huge; Default: medium)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • tooltip - string - Text to display when you hover the mouse over this component

DataGrid

grid = DataGrid()

The DataGrid component is great for displaying tabular data from any source. You can configure it completely by dragging and dropping into the designer from the Toolbox, or you can create one in code.

grid.columns = [
  { "id": 0, "title": "Name", "data_key": "name" },
  { "id": 1, "title": "Address", "data_key": "address" },
]

To configure DataGrid columns, use the Property Table, or create columns manually in code, as in the example on the right.

  self.data_grid_1.columns[0]['width'] = 500
  self.data_grid_1.columns = self.data_grid_1.columns

Data Grid columns are updated when the columns attribute itself is set. The example on the right shows how to modify a column width in code. Once the width has been changed, the second line triggers the update of the Data Grid on the page.

data = [
  {"name": "Alice", "address": "1 Road Street"},
  {"name": "Bob", "address": "2 City Town"}
]

for person in data:
  row = DataRowPanel(item=person)
  grid.add_component(row)

Once you've created columns, add Data Rows using the DataRowPanel container or a RepeatingPanel. By default, the DataRowPanel component will automatically display data from its item property based on the data_key property of each column.

  for person in data:
    row = DataRowPanel(item=person)
    row.add_component(TextBox(text=person['name']), column=0)
    grid.add_component(row)

You can add components to particular columns of the DataRowPanel by specifying the column id as the column argument to add_component, as in the final example. Column ids can be any unique string or number (letters and numbers only).

Data Grids are paginated by default, meaning they will only display some of their rows at one time. Use the page controls in the bottom right of the component to navigate between pages of data. Edit the rows_per_page property to adjust how many rows to display at once. Setting it to 0 or None will disable pagination and display all rows.

Child DataRowPanels can be set to 'pinned' in the "Container Properties" section of their Property Table - this will cause them to be displayed on all pages of data, and is useful for headers or summary rows.

Learn more about Data Grids in our Data Grid Tutorials

Properties

  • columns - dataGridColumns - A list of columns to display in this Data Grid. (Default: None)
  • auto_header - boolean - Whether to display an automatic header at the top of this Data Grid. (Default: true)
  • show_page_controls - boolean - Whether to display the next/previous page buttons. (Default: true)
  • rows_per_page - number - The maximum number of rows to display at one time. (Default: 20)
  • auto_display_data - boolean - Whether to automatically display data in this row. (Default: true)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • show - When the datagrid is shown on the screen
  • hide - When the datagrid is removed from the screen

DataRowPanel

The DataRowPanel is a special Anvil container, designed to work with the DataGrid component. In particular, DataRowPanels understand the column-based layout of their parent DataGrids, so they can arrange their child components appropriately. There are two main features of DataRowPanels that make them different to other Anvil containers:

  • DataRowPanels have a 'slot' for each column of the table they are in, meaning other Anvil components can be dropped into particular columns. They also behave like LinearPanels, in that you can drop components below the column-specific slots to have them take up the full width of the table. This is useful for section headers and other advanced layouts.
  • DataRowPanels can automatically display data from their item property, based on the columns of their DataGrid. DataGrid columns each have a data_key, which is used to get the data from the item of each DataRowPanel.

For more information, see the documentation for the DataGrid component, or our DataGrid Tutorials

Properties

  • item - object - The data to display in this row by default. (Default: None)
  • text - string - The text displayed on this component
  • align - string - Align this component's text (One of left, center or right; Default: left)
  • font_size - number - The height of text displayed on this component in pixels (Default: None)
  • font - string - The font to use for this component.
  • bold - boolean - Display this component's text in bold (Default: false)
  • italic - boolean - Display this component's text in italics (Default: false)
  • underline - boolean - Display this component's text underlined (Default: false)
  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the datarowpanel is shown on the screen
  • hide - When the datarowpanel is removed from the screen

LinearPanel

lp = LinearPanel()
lp.add_component(Label(text="Hello"))
lp.add_component(Button(text="Click me"))

This container arranges its child components vertically, each filling the whole width of the panel by default.

It takes no extra arguments for add_component: each component is added to the bottom of the LinearPanel.

The LinearPanel will expand vertically to fit its contents.

# self.linear_panel_1 is a LinearPanel
lp.add_component(Button(text="Button"))
# This label will be added above the Button.
lp.add_component(Label(text="Label"), index=0)

By default, components are added at the end of a LinearPanel. If you add an index layout parameter to the add_component() call, the component will be added at that index. Index 0 is the first element in the panel, 1 is the second, etc.

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the linearpanel is shown on the screen
  • hide - When the linearpanel is removed from the screen

XY Panel

xy_panel = XYPanel(width=400, height=400)

btn = Button(text="Click me!")
xy_panel.add_component(btn, x=10, y=100)

This container allows its child components to be placed at any position inside. Positions are measured in pixels from the top-left of the panel. To help with positioning components, you can get the width of an XYPanel by calling the get_width() function.

Arguments to add_component:

  • x: How far across the component is from the left-hand edge
  • y: How far down the component is from the top edge

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • height - string - The height of this component.
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • align - string - Align this component's content (One of left, center or right; Default: center)
  • tooltip - string - Text to display when you hover the mouse over this component
  • tag - object - Use this property to store any extra information about this component

Events

  • show - When the xypanel is shown on the screen
  • hide - When the xypanel is removed from the screen

GridPanel

gp = GridPanel()

gp.add_component(Label(text="Name:"),
                 row="A", col_xs=0, width_xs=2)

gp.add_component(TextBox(),
                 row="A", col_xs=2, width_xs=6)

gp.add_component(Button(text="Submit"),
                 row="B", col_xs=3, width_xs=2)

This container lays out components on a grid. Each row has twelve columns, and a component can span multiple columns. Components occupying the same columns in different rows will be aligned.

Arguments to add_component:

  • row: The name of the row to which this component will be added. If this is the first component with this row name, a new row will be created at the bottom of the GridPanel.

  • col_xs: What's the first column this component occupies? (0-11, default 0)

  • width_xs: How many columns does this component occupy? (1-12, default 12)

Note: When adding multiple components to the same row in code, components must be added left-to-right.

Properties

  • spacing_above - string - The vertical space above this component. (One of none, small, medium or large; Default: small)
  • spacing_below - string - The vertical space below this component. (One of none, small, medium or large; Default: small)
  • background - color - The background colour of this component.
  • foreground - color - The foreground colour of this component.
  • border - string - The border of this component.
  • visible - boolean - Should this component be displayed? (Default: true)
  • role - themeRole - Choose how this component can appear, based on your app's visual theme. (Default: None)
  • tag - object - Use this property to store any extra information about this component
  • tooltip - string - Text to display when you hover the mouse over this component

Events

  • show - When the gridpanel is shown on the screen
  • hide - When the gridpanel is removed from the screen

Custom Components

from MyWidget import MyWidget

# When the button is clicked, add
# an instance of the 'MyWidget' form
def button_1_click(self, **kwargs):
  self.add_component(MyWidget())

Anvil Forms are just components - you can create them in code by importing the appropriate module and calling the constructor, just as you would do for any other component. For example, if you have a form called MyWidget then on any other form you can import MyWidget and then add an instance of your widget to your form. See the example on the right.

Use form as component

You can also make your MyWidget form appear as a custom component in the Toolbox. To do this, first select the MyWidget form in the App Browser, then choose "Use as component" from the drop-down menu:

Configure custom component

Now tick "Make this form available as a component". This will cause it to show up in the Toolbox under "Custom Components". In this dialog box you can also configure any custom properties and events your component should have. These will show up in the Property Table when you drop an instance of the component onto another form, and any property values that are set will be passed as keyword arguments to the constructor of your form. All custom properties will be set as attributes on self inside the custom component.

Users of your component can bind event handlers as with normal components. If you make an event the "default" event for a component, then double-clicking an instance of the component in the designer will automatically jump to the event handler for that event. For example, the built-in Anvil button component has a default event "click". To raise your custom events, just call self.raise_event('my_event_name') in the usual way, passing any keyword arguments you wish. See the documentation for more details about events.

class MyForm(MyFormTemplate):

    def __init__(self, **properties):
        # Values of custom properties will be passed 
        # in as keyword arguments to the init function.
        # The init_components method will cause these 
        # properties to be set as attributes, in this 
        # case triggering the setter below.

        self.init_components(**properties)

    # Custom properties also have their own attributes.
    # Use property descriptors to implement our own
    # getter and setter for the my_prop attribute.

    @property
    def my_prop(self):
        print "Getting my_prop: %s" % self._prop
        return self._my_prop

    @my_prop.setter
    def my_prop(self, value):
        print "Setting my_prop: %s" % value
        self._my_prop = value

Sometimes you will want to update your custom component when the value of a property changes. To do that, you can use the standard Python Property Descriptors. Simply define your own getter and setter methods for the property attribute, as in the example on the right.

Configure custom component

Users of your custom component can make use of normal data bindings to set its properties. When configuring custom properties in the "Custom Component Configuration" dialog, you can choose whether a particular property supports data binding write-back. For properties supporting write-back, you also choose which events should trigger that write-back using the drop-down box.

Alerts

You can display popup messages using the alert and confirm functions. They are in the anvil module, so will be imported by default.

Messages

alert("Welcome to Anvil")

The simplest way to display a popup message is to call the alert function. You must supply at least one argument: The message to be displayed. The alert function will return True if the user clicks OK, or None if they dismiss the popup by clicking elsewhere. The example on the right produces the following popup:

Alert popup

Confirmations

c = confirm("Do you wish to continue?")
# c will be True if the user clicked 'Yes'

If you want to ask your user a yes/no question, just call the confirm function in the same way. The confirm function returns True if the user clicks Yes, False if the user clicks No, and None if they dismiss the popup by clicking elsewhere (See dismissible keyword argument, below).

Alert popup

Custom popup styles

# Display a large popup with a title and three buttons.
result = alert(content="Choose Yes or No",
               title="An important choice",
               large=True,
               buttons=[
                 ("Yes", "YES"),
                 ("No", "NO"),
                 ("Neither", None)
               ])

print "The user chose %s" % result

You can customise alerts by passing extra named arguments to the alert (or confirm) function:

  • content - The message to display. This can be a string or a component (see below)
  • title - The title of the popup box
  • large - Whether to display a wide popup (default: False)
  • buttons - A list of buttons to display. Each item in the list should be a tuple (text,value), where text is the text to display on the button, value is the value to return if the user clicks the button,
  • dismissible - Whether this modal can be dismissed by clicking on the backdrop of the page. An alert dismissed in this way will return None.

Alert popup

Custom popup content

t = TextBox(placeholder="Email address")
alert(content=t,
      title="Enter an email address")
print "You entered: %s" % t.text

You can display custom components in alerts by setting the content argument to an instance of a component instead of a string.

For complex layouts or interaction, you can set the content to an instance of one of your own forms. To close the alert from code inside your form, raise the x-close-alert event with a value argument:

self.raise_event("x-close-alert", value=42)

The alert will close and return the value 42.

Notifications

You can display temporary notifications by creating Notification objects.

Notification screenshot

n = Notification("This is an important message!")
n.show()

To show a simple notification with some content, call the show() method. By default, it will disappear after 2 seconds.

with Notification("Please wait..."):
  # ... do something slow ...

If you use a notification in a with block, it will be displayed as long as the body of the block is still running. It will then disappear.

n = Notification("This is an important message!",
                 timeout=None)
n.show()

# Later...
n.hide()

You can specify the timeout manually (in seconds), or set it to None or 0 to have the notification stay visible until you explicitly call its hide() method.

Notification("A message",
             title="A message title",
             style="success").show()

As well as a message, notifications can also have a title. Use the style keyword argument to set the colour of the notification. Use "success" for green, "danger" for red, "warning" for yellow, or "info" for blue (default).

Using Forms

Navigation

An Anvil app always has a startup form. (You can choose which form is your startup form from the app browser).

When the app is opened, a new instance of the startup form is created, and displayed to the user.

from anvil import *

class Form1(Form1Template):
  [...some code omitted..]

  def btn1_click(self, **event_args):
    open_form('Form2')

You can switch to displaying a different form by calling the function open_form() (from the anvil module). When you do this, the new form takes over entirely - the old form is no longer visible.

If you pass any extra parameters you pass into open_form(), they are passed to the new form's constructor.

from anvil import *
from Form2 import Form2

# ...Somewhere in the code...
frm = Form2()
open_form(f)

As well as specifying a form by name, you can also create an instance of the form yourself and pass i in.

(Note that each form has its own module, so you need to explicitly import any other forms you use. The module has the same name as the form; hence from Form2 import Form2.)

Modifying the top-level form

# The top-level form has a component called
# column_panel. Clear it and put a new Form2() panel there:

new_panel = Form2()

get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(new_panel)

Sometimes, you don't want to replace the currently open form entirely. For example, you might have a top-level form with a menu bar that needs to be visible all the time, and display all other app content as panels within that page.

You can use get_open_form() to get a reference to the currently open Form object. You can then use that to change what is displayed in the top-level form, even from deep within a nested form.

How the designer works

When you first start using Anvil, forms you have designed can seem a bit magical. You click and drag buttons and text boxes, and suddenly you have all these variables in self. What gives?

Well, if you look into the source code of an Anvil form, you will see that each form class inherits from a template class. The template class is automatically generated by the Anvil designer. It has the necessary __new__ implementation to create all the components specified in the designer, set their properties, and set up all the named attributes on self that make them easily accessible.

Apart from that, a form is a normal class and Anvil Container and can be treated as such throughout your app.

Themes

When you create an Anvil app, you're prompted to choose a theme (such as "Material Design"). Themes contain assets (HTML templates, CSS stylesheets and images) that can be used to set and customise the look and feel of your forms. They also contain template forms, so you have some options to pick from when creating a new form. For example, the Material Design theme contains form templates for a card-based dashboard layout and a single-column page with header and footer.

The Material Design tutorial is a good place to start learning about customising themes.

Using theme assets

All forms (apart from those created from the "Blank Panel" template) have an html property. This is where the look and feel of the form is defined, and you can write custom HTML here if you wish. Alternatively, you can choose to use one of the theme assets as the HTML of your form - this allows you to share HTML templates between forms in your app. To use a particular HTML template from the theme, just set the html property of your form to the required template in the drop-down box.

Theme editor

Customising the theme

You can add, remove and edit theme assets by clicking on "Assets" in the App Browser. Choose the asset you wish to edit from the drop-down box, and make any necessary changes. You can rename or delete the asset by clicking the buttons in the top-right. To add a new asset, simply choose "Upload file..." from the drop-down box.

Theme editor

Custom HTML templates

This feature is optional - you do not need to know HTML to use Anvil!

However, if you do know HTML, or employ a web designer who does, this reference will allow you or them to create a beautiful template into which you can easily drag-and-drop your Anvil components.

This feature is only available to Anvil Pro users.

When creating a form, you can choose whether to make it a blank panel (a ColumnPanel), or choose a design template. A template form allows components to be dragged and dropped into several different places within the design: For example, a particular template might have a navigation bar, a title/heading, and a main body.

Anvil Pro users can edit or create their own custom templates using HTML. This reference will show you how to build these templates

Slots and templating

<!-- Each component in the "default" slot goes
     in a new div with a red border -->
<div anvil-slot-repeat="default"
     style="border: 2px solid red">
</div>

An Anvil HTML template has one or more named "slots" into which components can be dropped. A slot can have any name - this example declares a slot called default.

The simplest way to create a slot is to use the anvil-slot-repeat attribute on any element of your HTML. This element will not appear on your page when the form is blank, but for every component added to that slot, a new copy of that element will be created, and the component will be added to the end of it. In this example, we create a box with a red border for each element:

<!-- Each component in the "default" slot goes
     in a green-bordered div, inside a red-
     bordered div. -->
<div anvil-slot-repeat="default"
     style="border: 2px solid red">
  <div style="border: 2px solid green;
              margin: 2px">
    <div anvil-slot></div>
  </div>
</div>

You can also specify deeper wrapping. If there is an element with the attribute anvil-slot inside your anvil-slot-repeat element, the component will go there rather than at the outside of the repeat. In this example, we create a box with a red border for each element, inside which is a box with a green border, inside which is our component:

Drop zones

When you drag and drop a component onto your form, Anvil uses the position of your mouse pointer to work out which slot the component will land in. It does this by examining whether the pointer is over a drop zone you have declared.

If you have not declared any drop zones, or the pointer is not over a drop zone, a dragged component will land in the "default" slot. You can change a component's slot in the Container Properties section of its Properties. However, editing each component's slot by hand is awkward. If you want to make your template as easy to use as the Anvil sample templates, you need to declare your own drop zones.

Each of the following explanations is accompanied by an example piece of HTML. Try copying and pasting this HTML into the html property of a template form, then drag and drop some components onto the form and see what they do.

Simple drop zones (anvil-drop-slot)

<div anvil-slot-repeat="my-list"
     style="border: 2px solid red">
</div>

<!-- If you drop a component onto this element,
     it will be added to the "my-list" slot -->

<div anvil-drop-slot="my-list" class="anvil-designer-only">
 + Add to nav bar
</div>

If you add the attribute anvil-drop-slot="foo" to an element, then if a component is dragged and dropped over that element, it will be added to the end of the foo slot. While dragging over this element, the element itself will be highlighted.

In this case, if you drag a component over the + Add to nav bar text, that text will be highlighted. If you drop the component on that text, it will appear at the end of the "my-list" elements, with a red box around it.

Adding the anvil-designer-only class to an element makes it invisible when the app is run. This allows you to include prompts to the designer (such as "+ Add to nav bar") that do not appear in the final app.

Default drop zone (anvil-drop-default)

<!-- If you drop a component almost anywhere,
     it will be added to the "my-list" slot -->

<div anvil-slot-repeat="my-list"
     style="border: 2px solid red">
</div>

<div anvil-drop-default
     anvil-drop-slot="my-list"
     class="anvil-designer-only">
 + Add to nav bar
</div>

Normally, if the user drags and drops over an element that not a drop zone (that is, it has none of the anvil-drop-xxx attributes discussed here), the component will be added at the end of the "default" slot. The whole form will be highlighted while dragging.

This is normally not what you want, so you can override this behaviour by designating a drop zone element with anvil-drop-default. You may then specify any other drop-zone attribute on this element. In this small modification of the last example, we default to the "+ Add to nav bar" drop zone.

Note that the anvil-drop-default must be a top-level element (not the child of any other element). You will therefore often want to use it with anvil-drop-redirect (see below).

Drop into sequence (anvil-drop-here)

<!-- Allow components to be inserted into a list
     by dropping them in a drop-zone above each element -->
<div anvil-slot-repeat="my-list">
  <div anvil-drop-here
       style="border-top: 10px solid red;">
  </div>

  <div anvil-slot></div>
</div>
<div anvil-drop-slot="my-list"
     anvil-drop-default
     style="border-top: 10px solid green">
</div>

If you add the anvil-drop-here attribute to an element, then if a component is dropped on that element, it will be inserted into the same slot as the drop zone, at the same index. (Anvil scans upwards in the document until it sees an anvil-slot-repeat attribute, then uses that slot.) When dragging, the element with the anvil-drop-here attribute will be highlighted.

This example creates a <div> for each component in the nav-bar slot. In each repeat, above the component is a 10-pixel-thick red bar - if you drop a new component on that red bar, it will be inserted above that component. In addition, a green bar at the bottom allows dropping components at the end of the list.

Snap to nearest drop zone (anvil-drop-redirect)

<!-- Wherever you hover over this container,
     you snap to the nearest element with
     the 'my-drop-zone' class -->

<div anvil-drop-redirect=".my-drop-zone"
     anvil-drop-default>
    <div anvil-slot-repeat="my-list">
      <div anvil-drop-here class="my-drop-zone"
           style="border-top: 10px solid red;"></div>
      <div anvil-slot></div>
    </div>
    <div anvil-drop-slot="my-list" class="my-drop-zone"
         style="border-top: 10px solid green">
    </div>
</div>

This example is the same as the last one, except that everything is wrapped in an element with the attribute anvil-drop-redirect=".my-drop-zone". This means that, wherever you drag and drop over this element, Anvil will act as though you are dragging or dropping over to the nearest drop zone matching the CSS selector ".my-drop-zone". This means that insertion will "snap" to the nearest point in the list, in a natural way.

(Note - if you include a container element in your list - such as a ColumnPanel or a Link - then components can be dragged and dropped into that container as normal. The drop zones only affect drag-and-drops outside a container.)

(Note also that you can anvil-drop-redirect to any sort of drop zone: anvil-drop-slot, anvil-drop-here, anvil-drop-container, or even to another anvil-drop-redirect.)

Snap to nearest container (anvil-drop-container)

<!-- This is a simple "card-style" layout. The idea is that
     you drop ColumnPanel components into this layout, and
     put your content inside them. Unless you drag something
     precisely onto one of the red separators, Anvil will
     put this component into the nearest panel within this
     div. -->

<div anvil-drop-container
     anvil-drop-default
     style="background: lightgrey">

  <div anvil-slot-repeat="cards">
      <div anvil-drop-here
           style="border-top: 10px solid red">
       </div>

       <div anvil-slot
            style="background: white;
                   margin: 5px auto;
                   max-width: 600px;
                   box-shadow: 2px 2px darkgrey">
       </div>
  </div>

  <div anvil-drop-slot="cards"
       style="border-top: 10px solid green">
    <div style="background: white;
                margin: 5px auto;
                max-width: 600px;
                box-shadow: 2px 2px darkgrey;
                padding: 10px;
                text-align: center">
      + Drop a ColumnPanel here to make a new card
    </div>
  </div>
</div>

If you set the anvil-drop-container attribute on an element, then when you drag over that element, Anvil will find the nearest container within that element, and act as though you are dragging or dropping on that container.

This is convenient if your layout expects to use a small number of container components (such as ColumnPanel) to give you finer drag-and-drop control over the arrangement of your components. Normally, an empty panel is a very small target to hit with drag-and-drop, but the anvil-drop-container attribute allows you to offer a large target area, so users naturally drop new components inside those containers.

You can also set anvil-drop-container="<css selector>". This will drop into the nearest Anvil container that matches the specified CSS selector (rather than the nearest container inside the element on which anvil-drop-container is set). This CSS selector must end with .anvil-container in order to match an Anvil container.

Fallback

An element can have multiple different anvil-drop-xxx attributes. If one does not match (eg there is no matching container for an anvil-drop-container, or no match for the CSS selector in an anvil-drop-redirect), we fall through to the next attribute.

The attributes take effect in the following order:

  1. anvil-drop-container
  2. anvil-drop-redirect
  3. anvil-drop-here
  4. anvil-drop-slot

If there are no more attributes, we keep looking upwards in the HTML (going to the parent of each element in turn). If we hit the top of the HTML, we look for an anvil-drop-default element. If we can't match that element, we default to highlighting the whole form and dropping a component at the end of the default slot.

<!-- This is a simple "card-style" layout. The idea is that
     you drop ColumnPanel components into this layout, and
     each of them creates a card. You then drop components
     inside them. Unless you drag something precisely onto
     a dotted separator, Anvil will put each component into
     the nearest panel within this div. -->

<div class="cards"
     style="background: lightgrey;
            padding: 5px">

  <div anvil-slot-repeat="cards">
      <div anvil-drop-here
           class="anvil-designer-only"
           style="padding: 7px;
                  border: 1px dashed darkgrey;">
       </div>

       <div anvil-slot
            style="background: white;
                   margin: 10px auto;
                   max-width: 600px;
                   box-shadow: 2px 2px darkgrey">
       </div>
  </div>

  <div anvil-drop-slot="cards"
       class="new-card anvil-designer-only"
       style="border-top: 10px solid green">
    <div style="background: white;
                margin: 5px auto;
                max-width: 600px;
                box-shadow: 2px 2px darkgrey;
                padding: 10px;
                text-align: center">
      + Drop a ColumnPanel here to make a new card
    </div>
  </div>
</div>

<div anvil-drop-default
     anvil-drop-container=".cards .anvil-container"
     anvil-drop-redirect=".new-card">
</div>

Putting it all together

Here is an example that puts together everything we've learnt so far:

  • We have a repeating element (anvil-slot-repeat) with a drop-shadow effect for each component in the cards slot.

  • In each repeat, there is a drop zone for inserting a new card at that position (anvil-drop-here).

  • At the bottom, there is a drop zone for adding a new card at the end of the list (anvil-drop-slot).

  • If we drop anywhere else (the zero-size anvil-drop-default element), we drop elements inside the nearest container in our card stack (anvil-drop-container), or if there is no matching container, we redirect to the "new card" drop zone (anvil-drop-redirect, following the fallback rules).

  • When we run the app, all our guidelines disappear (class="anvil-designer-only").

Summary

As well as using pre-built templates, Anvil Pro lets you use HTML to design your app exactly as you want it, then drop in standard Anvil components.

You don't need to know HTML to use Anvil, but if you do (or employ a web designer who does), the sky is the limit for building your app.

Using Javascript with Anvil

This feature is optional - you do not need to know Javascript to use Anvil!

However, if you do know Javascript, and want to integrate with a low-level browser API or an existing Javascript library, this reference will allow you to access all those low-level details from Anvil.

This feature is only available for paid Anvil users.

Calling Javascript from Python

Every HTML form (that is, any form except a Blank Panel form) has a call_js method that will invoke a Javascript function.

<script>
function showJsAlert(message) {
  alert(message);
  return 42;
}
</script>

Here is a simple Javascript function, that can be placed in an HTML template or a Custom HTML form.

def button_1_click(self, **event_args):
  # This method is called when the button gets clicked
  self.call_js('showJsAlert', 'Hello, world!')

Here is the code to call that function from Python.

You can pass arguments to this function (any JSON-compatible value is supported: strings, numbers, lists, dicts and None). Likewise, you can return any JSON-compatible value from a Javascript function.

(Note: This is just an example. If you want to show a pop-up alert in your app, you should use Anvil's alert() function instead!)

Referring to the form from Javascript

// (in Javascript:)
function setColor(color) {
  $(this).css("background-color", color)
}

When you call a Javascript function from an Anvil form, this is set to the (jQuery-wrapped) DOM node that represents the form itself.

(Note: This is just an example. If you want to change the background colour of something on your page, change the background property of a component instead!)

Asynchronous Javascript

// (in Javascript:)
function sleepForSomeTime(duration) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() { resolve(42); }, duration)
  })
}

If you call a Javascript function from an Anvil form, and it returns a Promise, Python execution will block until that promise resolves (in which case, the resolved value is returned) or rejects (in which case, the reject value is thrown with an ExternalError exception).

# When the button is clicked, it will wait 5 seconds
# before printing '42' to the Output console:
def button_1_click(self, **event_args):
  r = self.call_js('sleepForSomeTime', 5000)
  print r

Further tips

If you want to run Javascript on form startup, do not call_js() from your form's __init__ method. A form's constructor runs before it is added to the page, and Javascript in the form's HTML template is not executed until it is added to the page. Run Javascript initialisation from the show event handler, which executes after the form has been added to the page.

Calling Python from Javascript

def my_method(self, name):
  alert("Hello, %s!" % name)
  return 42

Any method on your form can be called from Javascript.

// (in Javascript:)
// Pop up a "Hello, World" Anvil alert when my-link
// is clicked.
$('.my-link').on('click', function(e) {
  var linkElement = this;
  anvil.call(linkElement, "my_method", "World").then(function (r) {
    console.log("The function returned:", r);
  });
  e.preventDefault();
});

To call into Python from Javascript, call anvil.call(). The first argument is a DOM element (optionally jQuery-wrapped) that indicates which form instance you would like to call the function on. It is not necessary to specify the exact DOM element of the form; any child element will work. It is an error to pass a first argument that is not an Anvil HTML form or a child of an HTML form.

The second argument is a method name, and all other arguments are passed to the Python method.

anvil.call() returns a Promise, which resolves to the method's return value if it returns, or rejects with an exception object if it throws an exception. If the method throws an exception, the exception stack trace will be captured in the Anvil logs, regardless of the Javascript's behaviour.

Javascript environment

Anvil pages execute within the Anvil runtime, which loads various Javascript components into the namespace. If your Javascript is acting weird, it might be because you have overwritten something important. For example, it is a bad idea to load jQuery or Bootstrap Javascript explicitly from an HTML template — it will overwrite the versions Anvil has already loaded. Here is a partial list of Javascript libraries Anvil loads:

  • jQuery version 3 (minor versions may be upgraded without warning)
  • Bootstrap version 3.3
  • The Skulpt Python-to-Javascript compiler

Modules

You can add Python modules to an Anvil app. You create and name them in the app browser, and you can import them from any other module in your app (including forms and server-side modules).

You cannot currently define heirarchical modules in your app (import x.y.z).

Anvil Modules

Anvil provides several modules of its own that you may wish to import into your app.

Anvil Module

The anvil module is imported by default in Forms, and must be referred to explicitly in server modules.

All the components described in these documents are classes in the anvil module. In addition, this module contains some utility functions:

from anvil import *

print "Our URL hash is: " + repr(get_url_hash())
  • get_url_hash() gets the decoded hash (the part after the '#' character) of the URL used to open this app.

    If the first character of the hash is a question mark (eg https://myapp.anvil.app/#?a=foo&b=bar), it will be interpreted as query-string-type parameters and returned as a dictionary (eg {'a': 'foo', 'b': 'bar'}).

Image Module

import anvil.image

# Load an image from a FileLoader 
# or Google Drive here

img = ...

# Get its size
width, height = anvil.image.get_dimensions(img)

# Resize the image to have a maximum dimension of 640px.

small_img = anvil.image.generate_thumbnail(img, 640)

The Image module allows you to manipulate images in your Anvil app. Begin by importing the anvil.image module.

To generate a thumbnail of an image (for uploading, for example) use the generate_thumbnail method.

# Rotate the image by 30 degrees.

rotated_image = anvil.image.rotate(img, 30)

To rotate an image clockwise by some number of degrees, use the rotate method.

HTTP Module

import anvil.http

The HTTP module allows you to make standard HTTP requests from your Anvil app. It is available for both client code and server code, for both free and paid users.

Begin by importing the anvil.http module.

## A minimal request includes only the 'url' parameter

resp = anvil.http.request("https://api.mysite.com/foo")

## We may also specify method, data and headers

resp = anvil.http.request(url="https://api.mysite.com/foo",
                    method="POST",
                    data="Data to post",
                    headers= {
                      "Authentication": "my-access-key",
                    })
print "Response MIME type: " + resp.content_type

Once you have imported the anvil.http module, you make web requests using the anvil.http.request function. This function can take several named arguments:

  • url (required) - The URL to request. This should include the protocol (e.g. http://).
  • method - The HTTP method to use, as a string. Defaults to GET, but other valid methods include POST, PUT, DELETE, etc.
  • json - Is this a JSON request? If True, JSON-encode the data parameter (if present), set the Content-Type header to application/json (unless it is already set), and return a JSON-decoded object rather than Media.
  • data - What to place in the body of the HTTP request. This would normally only be used in PUT and POST requests.
    • If json is set to True, it will be JSON-encoded before sending. Otherwise:
    • If data is string, it will be used as-is.
    • If data is a dict, it is transformed into a URL-encoded form submission (application/x-www-form-urlencoded).
  • headers - A dict of headers to include in the request. This is commonly used to pass authentication details to remote APIs.
  • username - A username to use with basic HTTP authentication
  • password - A password to use with basic HTTP authentication
resp = anvil.http.request("http://ip.jsontest.com", json=True)
print "My IP address is %s" % resp["ip"]

If the HTTP status code is successful (200-299), the anvil.http.request function returns the response body of the request. This will be a Media object, unless the json parameter is set to True, in which case a JSON-decoded object will be returned.

# This code will print "Error 404":
try:
  anvil.http.request("https://anvil.works/404")
except anvil.http.HttpError as e:
  print "Error %d" % e.status

If the HTTP status code is an error code, an anvil.http.HttpError will be raised. HttpError has two attributes:

  • status - The HTTP status code (a number)
  • content - The response body (JSON-decoded as above if possible, or a Media object if JSON decoding has failed)
# Prints "Hello%20there"
print anvil.http.url_encode("Hello there")

# Prints "Hello there"
print anvil.http.url_decode("Hello%20there")

The HTTP module also contains functions for URL-encoding and URL-decoding strings. This is useful when constructing links to other sites, or URL-encoding URL hash parameters in an Anvil app.

Browser HTTP restrictions

Web browsers restrict the HTTP requests you can make from form code. When the web browser disallows a request, an HttpError is raised with a status of 0. The easiest solution is to make your request from a server module instead.

A client-side HttpError with status 0 is usually for one of three reasons:

  1. The URL is inaccessible (cannot connect to host).

  2. You're requesting an unencrypted (http://) URL. All Anvil apps are served over encrypted links (HTTPS), so the browser does not allow unencrypted requests from client-side code.

  3. The URL doesn't obey the cross-origin rules. (It needs to respond to an OPTIONS request, have the right CORS headers, etc.)

There are two possible ways to remedy this error:

  1. Make the URL behave correctly. (Make sure it's accessible, serve it over HTTPS, give it the right CORS headers, and so on. This will require some expertise with web serving, and requires you to control the URL you are requesting. You may need to open up your browser's developer tools to work out precisely what the problem is.)

  2. Make the request from a server module instead. Server modules don't run in the web browser, so they don't have any of the browser's limitations.

Python libraries and limitations

Anvil supports many standard Python modules. If you want to use a module, try importing it - there's a good chance it will work.

However, there are some limitations on the modules you can import in client and server code:

Python libraries in Forms

All client code (that is, Forms and Modules) executes in the user's web browser. Some standard Python modules are not available in the web browser.

If there's a library you want, and it is not available in Form code, you should try importing it in a server module instead.

For more details, you can read about Anvil's security model. We compile Form and and Module code to Javascript using a compiler based on the open-source Skulpt project, and not all of the Python standard library can be compiled to Javascript.

Python libraries in server modules

Server modules run in a traditional Python interpreter, and have access to many more modules.

Anvil Pro users can request extra libraries to be installed for their server modules. If you are an Anvil Pro user, please contact pro-support@anvil.works.

If you do not have Anvil Pro, you will not have access to some libraries. There are also tighter limits on the CPU and memory resources your server modules may consume. This is to prevent other people's apps from interfering with yours.

If you want access to more Python libraries from your Anvil apps, please upgrade to Pro.

Services

Services are add-ons for your Anvil app:

Data Tables Service

The Data Tables service stores tables of information for your Anvil app. You can think of them a bit like spreadsheets.

To add Data Tables to your app, first add the Data Tables service in the app browser.

Editing Data Tables in the IDE

From the Data Tables page, you can create a new, empty table, or you can add tables from any of your other apps.

Each table has its own columns. For example, if we are making a table of people for our organisation, we might have columns for name, age and employment status. Each row in the table represents an item - in this case, a person.

Here's an example table, as it looks in the app editor:

An example table

Each column has a type of value that it can contain. For example, the Name column is always a string, Age is always a number, and Employed is a boolean value (either True or False). Any column can also be empty (None in Python).

Click to edit any data in the table (just like a spreadsheet). To add a new row, click in the blank row at the bottom of the table, and start typing! You can add more columns with the button.

To change a table's title, click on it (in the tabs at the top). Each table also has a Python name you can use to refer to it in your code. Editing the table's title does not affect the Python name, and vice versa. (If multiple apps share a table, they may all have different Python names for it, but you will see the same title for the table in each app.)

If you delete your app, its tables will be deleted as well (if no other apps are using it).

Data Types

All columns of a data table have fixed types. You choose the type of a column when it is created or, if it does not already exist, when a value is first set using add_row. The available types are listed below, along with their corresponding python types.

  • Text - A Python str
  • Number - Any Python number
  • True/False - A Python boolean, True or False
  • Date - A Python datetime.date
  • Date and Time - A Python datetime.datetime
  • Simple Object - Can hold Python strings, numbers, dicts or lists
  • Media - Binary data (a Media object)
  • Link - A row (or list of rows) from another table

Permissions

Without the Share link for your app, nobody can access your app's tables.

But often, even people with the share link should not be able to see or edit all of your app's data. So we can restrict access each data table:

Table permissions

Because the code for Forms executes in the user's browser, it is under the user's control. A determined attacker can access anything that your Forms are allowed to access.

Therefore, by default, your Forms are not given any access to your data tables. You can instead access the data by writing server modules and exposing views.

Server modules are not under the user's control. So you can trust them, eg, not to return table data to authorised users.

You can return table rows (or search() results) from server module functions. The (client-side) Form code will be able to read them, but not update them. If you pass a row object into a server function, the server module can update it.

Relaxing permissions

You may want to relax these restrictions. For example, if your app is a blog, it's OK for every visitor to be able to view your blog posts (but not edit them). Or if you will only send the Share link to a small group of co-workers, it's OK that they can all edit your data.

You can choose from three levels of permission, for either Forms or server modules:

No access
Your code will not be able to search, update, or edit the table. If it gets a row from this table (eg returned from a server function), it can read that row and any linked rows. But it cannot update or delete that row.

Can search table
Your code can call search() and get() on this table, and read all the data in its rows (and linked rows). However, it cannot add new rows, or update or delete existing rows.

Can search, edit and delete
Your code can perform any operation on this table.

More notes

  • You can also restrict what server modules can do. For example, you can make the table read-only for your app by making sure even server modules can't write to the table.

  • If multiple apps share the same table, the permissions are per-app. This means you can, eg, have one app for displaying data to the public (with Can search permissions), and one app for updating your data (with Can edit and delete permissions - you'll want to keep this one's Share link secret!).

  • Uplink code can do everything that server modules can. This includes accessing and updating tables.

Sharing Data Tables Between Apps

Data Tables can be shared between apps. When you click the '+' button in the Data Tables Service, you will be given a list of tables from your apps to add to the current app.

Adding tables from other apps

Modifications to the table data and schema made in one app will be available from the other apps that use it.

Table permissions are on a per-app basis: if you make a table client-writable in one app, other apps that use it will not be affected. The 'Python name' of a table is also per-app.

A table that is included in several apps will only be deleted when it is removed from all apps that use it.

Using Data Tables in Python

Adding Data

from anvil.tables import app_tables

app_tables.people.add_row(Name="Jane Smith",
                          Age=7,
                          Employed=False)

You can add a new row to a data table with the add_row() method.

Use keyword arguments to specify the value for each column. If you name a column that does not exist, a new column will be created.

Remember that in Python, "Name" and "name" are two different strings, and they will create two different columns if you mix them up.

Row Objects

james_row = app_tables.people.add_row(Name="James Smith")
james_row["Age"] = 12

The add_row() method returns a row object, which you can use to get or set column values.

Searching a Table

for row in app_tables.people.search():
  print "%s is %d years old" % (row["Name"], row["Age"])

You can list the contents of a table with the search() method.

people_called_dave = app_tables.people.search(Name="Dave")

Use keyword arguments to search for a particular value in a particular column.

oldest_first = app_tables.people.search(tables.order_by("Age", ascending=False))

Pass in positional arguments to customise the search. For example, use tables.order_by() to sort the rows returned. This takes the name of the column to sort by, and a keyword argument ascending, which defaults to True.

ten_oldest = app_tables.people.search(tables.order_by("Age", ascending=False))[:10]

Slice a table search to return just a portion of the results.

adults = [person for person in app_tables.people.search()
            if person["Age"] >= 18]

If you want to perform a more complex search, use Python's list comprehensions.

search() returns a search object, which is a iterable - you can use it in a for loop or a list comprehension. If your search returns lots of results, Anvil only loads a few at a time.

print "There are %d people" % len(app_tables.people.search())

You can call len() on a search object to efficiently find out how many rows it would return (without loading them all!).

# Make a Python list from this search
people = list(app_tables.people.search())
people.sort(key = lambda person: person['Name'])

first_person = people[0]
print "First alphabetical name: %s" % first_person['Name']

Anvil makes no guarantee about the order in which rows are returned from a search(). If you want to sort the returned rows, or access them by index, you can use the list() function to turn a search object into a Python list.

# Print the names of all the adults:

adult_names = " and ".join((p['Name'] for p in app_tables.people.search
                                      if p['Age'] >= 18))

print adult_names
# ^^ Prints "Tom and Dick and Harry"

You can use Python's normal sequence operations to work with search results.

Getting One Row from a Table

zaphod_row = app_tables.people.get(name="Zaphod Beeblebrox")

Sometimes, you only want a single row. The get() method returns a single row that matches the keyword arguments, or None if no such row exists. If more than one row matches, it raises an exception.

zaphod_row = (app_tables.people.get(name="Zaphod Beeblebrox")
               or app_tables.people.add_row(name="Zaphod Beeblebrox", age=42))

You can use this neat trick to fetch a row if it exists, or create a new one if it does not.

Because get() returns None if a row does not exist, we use "short circuiting" of the or operator to make sure we only run the add_row() if no such row already exists.

Updating and Deleting Rows

# Increase Jane Smith's age (from 7 to 8)
jane_row = app_tables.people.get(Name="Jane Smith"):
jane_row["Age"] += 1

You can set new values for a row. This updates the database, not just the data on the client side.

# Set Jane Smith's age to 9
jane_row.update(Age=9, Name="Jane E. Smith")

You can set the value of multiple columns at once by calling the update() method on a row. Again, this updates the database.

# Remove Jane Smith from the table
jane_row = app_tables.people.get(Name="Jane E. Smith"):
if jane_row is not None:
  jane_row.delete()

You can also delete a row by calling its delete() method. The row is removed from the database.

# Delete all rows in the table
app_tables.people.delete_all_rows()

You can delete all rows in a table by calling the table's delete_all_rows() method. All data is cleared from the database for this table!

# Get Jane Smith's row from the table
jane_smith = app_tables.people.get(Name="Jane Smith")

# Add a row to the 'notes' table, referencing her
n = app_tables.notes.add_row(Person=jane_smith,
                             Text="Jane is a good kid")

# Print the name of the person this note is linked to
# (prints "Jane Smith")
print n["Person"]["Name"]

# Search by reference to a row. Eg,
# How many notes do we have about Jane?
janes_notes = app_tables.notes.search(Person=jane_smith)
print "There are %d notes about Jane." % len(janes_notes)

You can store a reference to one table in another table, using a link column. In this example, we have a people table, and a notes table containing notes about a person. The notes table has a "Person" column, which links to a row from the people table.

Once we have a row object representing Jane Smith's row in the people table, we can add a new row to the notes table that refers to her, by passing that row object into add_row(). We can also search for all notes that refer to this person, by passing her row object into search().

Note that we use the len() function here - len() runs very efficiently over search iterators.

zaphod = app_tables.people.get(Name="Zaphod Beeblebrox")

zaphod["Friends"] = [jane_smith, john_smith]

You can also create columns that link to multiple rows from another table. In this example, the "Friends" column links to multiple rows from the "People" table. We set it to a list of row objects.

friends_of_jane = app_tables.people.search(Friends=[jane_smith])

mutual_friends = app_tables.people.search(Friends=[jane_smith, john_smith])

You can search using a "link to multiple rows" column. If you pass a list of row objects to search(), it will return only rows that link to all of those rows. (If you specify multiple rows, the order doesn't matter - it matches anything that links to both of them.)

Searching Simple Objects

A Simple Object column can store strings, numbers, None, lists of any simple object, and dictionaries whose keys are strings and whose values are any simple object. (In other words, it's the values that can be represented as JSON.)

# This finds all rows where object_col is a
# dict and contains a key 'the_answer' with
# the value '42' - no matter what other keys
# are in the dict.
r = app_tables.my_table.search(object_col={'the_answer': 42})

When you search on a Simple Object column, you search by object specifying an object pattern. The rules are:

  • If you specify a dictionary, all specified keys must be present in the column, and all values must match the pattern specified in the dictionary.

  • If you specify a list, the object you're matching must be a list, and it must contain elements matching all elements you specify. Order doesn't matter, though, so if you specify [1,3] as your search you will match a column containing [3,2,1].

  • If you specify a string, number or None (a "primitive value"), the object you're matching must be exactly the same, or be a list containing that primitive value.

(The Simple Object search semantics are derived directly from the Postgres JSON containment rules.)

zaphod = app_tables.people.get(Name="Zaphod Beeblebrox")
ford_prefect = app_tables.people.get(Name="Ford Prefect")

zaphod["Friends"] += ford_prefect

Values in Multiple Link columns are represented as lists in Python. Lists from Simple Objects are also Python lists.

To add a new item to a Multiple Link or a Simple Object, use the += operator.

zaphod = app_tables.people.get(Name="Zaphod Beeblebrox")
ford_prefect = app_tables.people.get(Name="Ford Prefect")

zaphod["Friends"] = [r for r in zaphod['Friends'] if r != ford_prefect]

To remove rows, remove the item from the list and re-assign it using the = operator.

NB: the append operator will not update the database, it will simply update the list in-memory. You must use assignment (=) if you want to update the database.

Row IDs

# 'jane_smith' is a Row object from the 'people' table
janes_id = jane_smith.get_id()

r = app_tables.people.get_by_id(janes_id)

# r and jane_smith are equal, as they both
# point to the same row.
# (jane_smith == r)

Each row of each table has unique ID, which is a string. You can get the ID of any row, and use it to retrieve that row later.

This is useful if you want to store a reference to a table row somewhere else (for example, in a file).

Note: If you just want to refer to one row in a data table from another row in a data table, don't mess with IDs -- use link columns instead!

CSV export

# In a Form:
self.link_1.url = app_tables.people.to_csv().url

If you want to provide a download from a data table, you can call to_csv() to obtain a downloadable Media object.

janes_notes = app_tables.notes.client_readable(person=jane_smith)
self.link_1.url = janes_notes.to_csv().url

You can also call to_csv() on table views. This can be used to provide downloads of only part of a data table.

Transactions

# Server code only
import anvil.tables

@anvil.server.callable
@anvil.tables.in_transaction
def do_update():
  jane_smith = app_tables.people.get(Name="Jane Smith")
  jane_smith['age'] = 21
  app_tables.notes.add(Person=jane_smith,
                       Text="She's really grown up")

If multiple users are accessing data at the same time, you might want to place it in a transaction. All table accesses within a transaction will only become visible when the transaction completes. Even if they're happening at the same time, it's as if every transaction block was executing one after another.

If an exception is raised before that happens, all changes are discarded. If two transactions conflict - that is, they've written to the same row of a data table, or one transaction writes into a row another is reading from - one or both of them will be aborted.

The easiest way to run an operation in a transaction is to call a function decorated with @anvil.tables.in_transaction. If an @anvil.tables.in_transaction function aborts due to conflict, it will automatically be tried again (after a short timeout) up to 5 times before throwing an exception.

Transactions are only available from server code (server modules and the Uplink).

(Database expert note: Anvil provides full serialisable transaction isolation.)

import anvil.tables

with anvil.tables.Transaction() as txn:
  jane_smith = app_tables.people.get(Name="Jane Smith")
  jane_smith['age'] += 10

You can also create transactions by creating a with anvil.tables.Transaction(): block. This will not automatically retry conflicting transations: If a transaction is aborted because of a conflict, a tables.TransactionConflict exception is thrown. You can also abort a transaction manually by calling txn.abort().

If you observe a TransactionConflict, it is generally safe to retry. You could write a loop that detects a conflict and tries again, but this is such a common pattern that it is usually preferable to use @anvil.tables.in_transaction (which retries the transaction automatically, up to 5 times, before throwing the TransactionConflict as normal.)

You can specify @anvil.tables.in_transaction after @anvil.server.callable to make a server function transactional. Make sure to specify @anvil.tables.in_transaction after @anvil.server.callable. Python applies function decorators from "inside" to "outside", and you want the in_transaction decorator applied before you make the function callable.)

Some more notes:

  • The search() method returns an iterable Python object, not a list. This is so, if your search returns a lot of rows, you don't have to fetch them all before you can start processing them. If you want a list, pass the search result to Python's list() function.
  • You can list all the columns in a Data Table by calling list_columns() on the table object. This returns a list of strings.

Exposing Data Tables to Client Code

If you don't want to give every visitor access to a data table, you'll want to use server modules to control access to them. Server modules can return views on a data table to client code, with extra permissions or restrictions.

Data tables have three view methods, which offer the client code different levels of access:

  • client_readable() allows the client to read this table, and any rows linked to from rows in this table.
  • client_writable() allows the client to update rows in this table, and add new rows. Client code can also read any linked rows.
  • client_writable_cascade() allows the client to write to this table, and update any rows linked to in this table.

A table view is just like a table - you can call search(), add_row(), and so on on it.

@anvil.server.callable
def get_data():
  if user_is_authorised():
    return app_tables.my_table.client_writable()

This is a simple example: This server function returns a client_writable view on this table, but only for authorised users.

This pattern can be used to implement simple applications where all users must log in, but any logged-in user may access all the data in a table.

@anvil.server.callable
def get_data_for_logged_in_user():
  email = anvil.google.auth.get_user_email()
  if email is not None:
    return app_tables.my_table.client_writable(owner = email)

View methods can also take keyword parameters, just like the search() function. This returns a restricted view, that only contains matching rows. In this case, we use Google authentication to return a view that can only access rows whose owner column matches the email address of the current logged-in user. (If no user is logged in, it returns None.)

my_data = anvil.server.call('get_data_for_logged_in_user')

# This adds a row with a message and my email address
my_data.add_row(message = "Hello, world!")

# This raises an error, because the 'email'
# column is fixed by the view, and we cannot
# override it.
my_data.add_row(email = "bob@example.com",
                message = "Hello!")

If you call add_row on a restricted view, the fixed columns (email in this example) are filled in automatically. If you attempt to specify a fixed column in add_row(), the request will fail.

In this example, this means that any row a user adds to this table will always have their email address attached to it.

# This also raises an error, because fixed
# columns are not accessible from code:
for row in my_data.search():
  # Raises an error:
  print row['email']

Fixed columns are not accessible in code. This means that the client does not gain read or write access to rows referenced in a fixed column.

Columns cannot be automatically added to a restricted view.

Data Tables and SQL

Developers more familiar with traditional database systems may prefer to access their data using SQL (a specialised database query language).

Users on Dedicated Hosting or Enterprise plans can access the data in their Data Tables using SQL directly. To get started, contact support@anvil.works to get your dedicated database credentials.

# Assuming your password is stored in the Anvil Secrets Service as 'db_pw'

import psycopg2
conn = psycopg2.connect("""host=anvil-db 
                           port=5432 
                           user=anvil 
                           dbname=anvil 
                           password=%s""" % anvil.secrets.get_secret('db_pw'))
cur = conn.cursor()
cur.execute("SELECT * FROM data_tables.table_4851")
...

Anvil Data Tables are stored in PostgreSQL, and you have access to writeable views of these tables through SQL. Every table created in your account can be accessed through the data_tables schema in the database. The example on the right shows a typical query for a table with ID 4851.

conn = psycopg2.connect("""host=anvil-db 
                           port=5432 
                           user=anvil 
                           dbname=anvil 
                           password=%s 
                           options='-c search_path=data_tables'""" % anvil.secrets.get_secret('db_pw'))
cur = conn.cursor()
cur.execute("SELECT * FROM table_4851")
...

You can also choose to set the default schema to 'data_tables' to simplify your queries, as in the example on the right.

Every row in these views has an _id column, which is a unique identifier for the row. Columns that link to other tables show the ID of the linked row, so you can use standard JOIN queries if you wish.

The data_tables.* views are writeable, meaning you can update the data in your tables in the usual way with INSERT, UPDATE and DELETE queries. Note that you cannot update Media objects or links between tables via SQL right now - you must use the Python APIs for that.

In order to prevent malformed queries from harming performance or security for other users, we do not permit users to execute SQL queries against data tables for apps hosted on our shared hosting plans. If you are used to using SQL, there are three options available:

  1. Use our Python APIs. It is often sufficient to use Python list comprehensions instead of complicated SELECT statements.

  2. Connect to an existing, external database. Anvil's Full Python runtimes provide many modules, including libraries for connecting to popular databases such as PostgreSQL, MySQL, MongoDB, Microsoft SQL Server, and Oracle. If you require a driver for an external database we don't support yet, please email contact@anvil.works and we can usually get it installed within 24 hours.

  3. Purchase a dedicated or on-site installation of Anvil. With a dedicated or private installation, you will receive full SQL access to all of your data tables. Please email sales@anvil.works for more details.

Users service

It's common for apps to have multiple users who log into your app. They sign in with a username or password

Perhaps you use an existing authentication framework - such as Active Directory or PKI certificates, or even with an external service like Google.

Anvil makes it easy to add users to your application. The Users service takes care of sign-up, log-in, email verification, password reset, and much more.

You must add the Users service to your app in the App Browser in order to use anything discussed in this section.

Basic Usage

To start logging users into your app, you need two things:

1. Log in / Sign Up

Call anvil.users.login_with_form() to launch a login form.

By default, users can sign in with their email address and password, or sign up for a new account. By default, they will need to confirm their email address by clicking on a link. All of this is configurable in the Users service.

You can see all registered users when you open the Users service:

2. Get the logged-in user

@anvil.server.callable
def get_secret_data():
  user = anvil.users.get_user()

  if user['email'] == 'john.smith@example.com':
    return app_tables.secret_table.client_readable()

You can get the current logged-in user with anvil.users.get_user(). Do this when they are about to do something for which they need to be authorised (e.g. access private data).

This function returns a user object, which is a Data Table row, or None if no user is logged in. In this example, we check the user's email address. If this is an authorised user, we give them access to our secret data (by returning a client-readable view on a data table).

You can pass an optional keyword argument allow_remembered to get_user(), which is True by default. To un-remember a user's login session, delete the contents of the remembered_logins column for that user.

Setting this to False will prevent get_user from returning a User who logged in in a previous session and selected "Remember me".

anvil.users.get_user() can be used from Server Modules and from client code. To be sure that the results are correct, you should call it from a Server Module. The user has ultimate control of anything running in their browser, but the server environment is secure.

Calling anvil.users.get_user() accesses the database if a cache miss occurs, so making the call from client code causes a spinner to be displayed.

That's it!

That's all you need to use basic authentication with Anvil. The next sections will go more deeply into the advanced features of the Users service.


Other authentication methods

The Users service supports the following sign-in methods:

Email + Password

Users sign in with an email address and a password. These will be stored in the email and password_hash columns of the Users table. The password is hashed with the industry standard bcrypt algorithm, which means that knowing the password_hash does not tell you the password. (You should still keep the hashes secret, to avoid brute force attacks on weak passwords.)

If email confirmation is enabled (the default), a user cannot use their account until they confirm ownership of their email address by clicking a confirmation link.

A user can reset their password by confirming ownership of their email address. If authentication fails, the built-in log-in form (login_with_form()) will offer a password reset option. If you are not using the built-in log-in form, see below for instructions on using anvil.users.send_password_reset_email().

Email + password authentication is enabled by default.

Sign in with Google

Users sign in with a Google account. Their identity is stored in the email column of the Users table. (This means that a user who registers with "Email + Password" can then sign in with Google if the email address is the same.)

Google authentication is disabled by default. If you enable Google authentication, you also need to add the Google service to your app.

Sign in with Facebook

Users sign in with a Facebook account.

Facebook authentication is disabled by default. If you enable Facebook authentication, you also need to add the Facebook service to your app.

Active Directory

Anvil can authenticate users against your organisation's Active Directory. This means you can easily re-use your existing security investments for your business applications.

This is available on our Enterprise plans. Please email contact@anvil.works for more information.

Client certificates

Anvil can authenticate users using X.509 client certificates or PKI (Public Key Infrastructure). This means you can easily re-use your existing security investments for your business applications.

This is available on our Enterprise plans. Please email contact@anvil.works for more information.


Configuration options

As well as choosing the available authentication methods, there are various other options that can be customised.

Require Secure Passwords

When this option is selected, users signing up with an email address and password will be required to choose a password that is at least 8 characters long and that has not been previously leaked online. This check is performed without sharing the password with any third-party services.

Remember login between sessions

When this option is selected, users will see a "Remember me" checkbox on the login form. If they leave this checked (the default), the fact that they are logged in will be stored in an Anvil cookie for a configurable time (30 days is the default).

Which cookie is used depends on the "share login status" option, below. If a user visits an app where they are already logged in, anvil.users.get_user() will return that user without the need for them to log in again. You can still allow them to log in as a different user by calling anvil.users.login_with_form() as usual. Calling anvil.users.logout() will remove their login status from the cookie as well as logging them out in the current session.

This logic also applies to manual calls to login_with_email(), force_login(), and similar functions. They each accept remember= keyword argument to control whether to remember this user's login.

Share login status with other apps

Selects which Anvil cookie is used to store the user's login status. If not selected (the default), user login status will only be remembered for this app and will not affect (or be affected by) user behaviour on any other apps. When selected, users' login status is stored in the shared Anvil cookie, meaning that they will be automatically logged into any other apps sharing the same Users table and on the same top-level domain (anvil.app by default).

Python API

Basic API

user = anvil.users.login_with_form()

The simplest way to use the Users service is to call anvil.users.login_form() on the client. This function shows a graphical login form, including all the options configured in the Users service (eg password, Google, and new-account registration). If login succeeds, anvil.users.login_form() returns the user object; if it is cancelled, it returns None.

anvil.users.signup_with_form()

You can also explicitly bring up the new-user registration form by calling anvil.users.signup_with_form(). If signup succeeds, this function returns the new user object; if cancelled, it returns None.

Note that, even if a new user has been created, they will not be logged in if their account is not yet enabled. For example, if the user needs to confirm their email address, anvil.users.signup_with_form() can return a user object, but anvil.users.get_user() will still return None because they are not yet logged in. If no confirmation is required, the new user will be logged in immediately.

user = anvil.users.get_user()

To get the currently logged-in user, call anvil.users.get_user(). This returns the currently logged-in user, or None if no user is logged in.

Call this function in a server module before doing anything you need to protect: Remember, form code cannot be trusted, because a malicious user can change it to do whatever they want!

anvil.users.logout()

To log a user out, call anvil.users.logout(). After this function returns, anvil.users.get_user() will return None.

Logging in manually

As well as using the built-in login and sign-up forms, you can create your own forms and call the following functions yourself. If they fail, they will raise an anvil.users.AuthenticationFailed exception. There are a few subclasses if you wish to handle them separately:

  • anvil.users.UserExists - You have attempted to sign up with an email address which is already in the Users table.

  • anvil.users.EmailNotConfirmed - You have attempted to log in with an email address that has not yet been confirmed. (Only occurs with Email + password login, when email confirmation is enabled)

  • anvil.users.AccountIsNotEnabled - This account is disabled (the enabled box is not ticked in the Users table). This happens if an administrator has manually disabled an account, or if the Users service is configured not to enable newly-registered accounts.

  • anvil.users.AuthenticationFailed - This is the parent class of all these exceptions. It indicates that authentication has failed for some reason.

All of the manual login and signup functions take an optional keyword argument remember, which is False by default (for login_with_form and signup_with_form, the argument is called remember_by_default). If set to true, and the "Remember login between sessions" configuration option is enabled, the user's login status will be remembered between sessions for the configured amount of time.

Email + password
user = anvil.users.login_with_email("abc@example.com",
                                    "<password>")

To log in manually with an email and password, call anvil.users.login_with_email(), passing the email address and password as arguments. This function returns the newly-logged-in user, or raises an AuthenticationFailed exception if the login failed.

This function can be called from client or server code.

anvil.users.send_password_reset_email("abc@example.com")

To reset a user's password, call anvil.users.send_password_reset_email(). If the specified email address is in the Users table, Anvil will send them an email with a link where they can set a new password for their account.

This function can be called from client or server code.

anvil.users.signup_with_email("abc@example.com",
                              "<password>")

To register a new account with an email and password, call anvil.users.signup_with_email(). If signup succeeds, this function returns the newly-created user object. The new user will also be logged in if email confirmation is not required, and new-user registrations are automatically enabled.

This function can be called from client or server code, if the "allow signups" option is enabled in the users service configuration. Otherwise, it can only be called from server code.

Google authentication
user = anvil.users.login_with_google()

To log in with Google, call anvil.users.login_with_google(). This function will prompt the user to log into Google, then attempt to authenticate that user with the Users service. It will return a user object, or None if the Google login is cancelled, or raise an AuthenticationFailed exception.

This function may only be called from client code (eg an event handler on a form).

anvil.users.signup_with_google()

To sign up a new user with Google, call anvil.users.signup_with_google(). This will prompt the user to log into Google, then attempt to register this user with the Users service. It will return a user object, or None if the Google login is cancelled, or raise a UserExists exception. If signup succeeds, the user is automatically logged in.

This function may only be called from client code (eg an event handler on a form).

The Google Service must be enabled for Google authentication to work.

Build your own authentication flow
@anvil.server.callable
def login_with_password(username, password):
  user = app_tables.users.get(username=username)
  if user is not None and \
       bcrypt.hashpw(password, user['password_hash']) == user['password_hash']:
    anvil.users.force_login(user)
    return user

You can implement your own login mechanism. To log a user in, pass a row from the users table into anvil.users.force_login() in server code. anvil.users.force_login() returns the now-logged-in user. Passing None is the equivalent of anvil.users.logout(), and will return None.

This example shows a simple implementation of username/password login. (This is approximately the logic used by anvil.users.login_with_email().)

The Users table

The table of users is a data table. Although we create a new table called Users for you by default, you can configure which table the service uses, or even share user data between several apps!

When you add the Users service to your app, it looks for a table called Users in your app. If no such table exists, it creates a new one. However, if you delete the Users table in the data tables configuration page, the Users service will prompt you to select a new one. You can select an existing table in your app - or a table from any other app in your account! By sharing one Users table between many apps, you can provide single-sign-on for all your apps simultaneously.

You can also add columns to your Users table to store extra information about a user (eg "is this user an administrator?").

Columns

The following columns will be automatically added to the Users table. Feel free to use or manipulate them yourself, or add more:

  • email - The user's email address

  • password_hash - A bcrypt hash of the user's password (password-based authentication only)

  • enabled - A boolean value indicating whether this user is allowed to log in. Uncheck to lock an account.

  • signed_up - The date and time when this user registered

  • last_login - The date and time when this user most recently logged in

  • confirmed_email - A boolean value indicating whether this user has confirmed their email address (password-based authentication only; only if email confirmation is enabled)

  • confirmation_key - A secret value used in the confirmation and password-reset links (password-based authentication only)

Email Service

Your apps can send and receive email using the built-in Email Service.

Start by adding the Email service to your app in the App Browser:

Sending Email

anvil.email.send(
  to="customer@example.com",
  from_address="support",
  from_name="MyApp Support",
  subject="Welcome to MyApp",
  html="<h1>Welcome!</h1>"
)

To send an email, use the anvil.email.send function, passing in data as keyword parameters. This can only be called from a Server Module. The available parameters are:

  • to - The address (or comma-separated addresses) to deliver this email to. Can also be a list of strings if sending to multiple addresses. If no address is given, the email will be sent to the owner of the app.
  • cc - The address (or comma-separated addresses) to copy this email to. Can also be a list of strings if copying to multiple addresses.
  • bcc - The address (or comma-separated addresses) to blind-copy this email to. Can also be a list of strings if blind-copying to multiple addresses.
  • from_address - The address to send this email from. If it does not contain an "@", the domain of your app will be used. Defaults to "no-reply", meaning emails will come from no-reply@YOUR_APP_ID.anvil.app or no-reply@YOUR_CUSTOM_DOMAIN if your app has a custom domain. If you do specify a full email address, the domain must be a valid domain for your app, or it will be replaced with a valid domain.
  • from_name - The name that will be displayed in the "From" field of this email. Defaults to the empty string "".
  • subject - The subject line of this email.
  • text - The plain text content of this email.
  • html - The HTML content of this email.
  • attachments - A list of Media objects to attach to this email.

Receiving Email

@anvil.email.handle_message
def handle_incoming_emails(msg):

  msg.reply(text="Thank you for your message.")

  msg_row = app_tables.received_messages.add_row(
              from_addr=msg.envelope.from_address, 
              to=msg.envelope.recipient,
              text=msg.text, 
              html=msg.html
            )
  for a in msg.attachments:
    app_tables.attachments.add_row(
      message=msg_row, 
      attachment=a
    )

To receive emails to your app, register a server function to handle incoming messages using the @anvil.email.handle_message decorator. Any emails sent to anything@YOUR_APP_ID.anvil.app (or anything@YOUR_CUSTOM_DOMAIN) will be passed to the decorated function.

The example on the right stores incoming email messages and attachments in Data Tables and sends a reply to each message.

The msg argument is of type anvil.email.Message, which has the following attributes:

  • envelope - An object with two attributes, from_address and recipient. E.g. msg.envelope.from_address is the simplest way to get the sender of an email.
  • addressees - An object containing the parsed addressees of this message. See below.
  • dkim - An object with two attributes, valid_from_sender (a boolean, true if this message was signed by the domain in its envelope from_address) and domains (a list of DKIM domains that signed this message).
  • text - The plain text part of the incoming email, if any.
  • html - The HTML part of the incoming email, if any.
  • get_header(header_name) - A function that returns the value of the named header (returning only the first value if there are several).
  • list_header(header_name) - A function that returns all the values of the named header as a list.
  • headers - A list of tuples, one for each header in the message. E.g. [("From", "me@here.com"),("To", "you@there.com"), ...]
  • attachments - A list of attachments in the message, as Media objects.
  • reply(**kwargs) - A function that takes all the same keyword arguments as anvil.email.send to send a reply to the sender of this message. By default, replies to message.envelope.from_address with the same subject, but this can be overridden by setting those keyword arguments.

Addressees

  @anvil.email.handle_message
  def handle_incoming_emails(msg):

    msg.reply(text="Hi %s, thank you for your message." %
      msg.addressees.from_address.name
    )

The msg.addressees object contains all the parsed addressees of the message. This saves you from having to parse addresses like "John.Doe <john.doe@domain.com>" manually. The addressees object has three attributes:

  • from_address - A parsed address from the FROM header.
  • to_addresses - A list of parsed addresses from the TO headers.
  • cc_addresses - A list of parsed addresses from the CC headers.

Parsed addresses have three attributes:

  • address - The plain email address of this addressee, e.g. john.doe@domain.com.
  • name - The display name of this addressee, e.g. John Doe.
  • raw_value - A single string containing both address and display name, e.g. John Doe <john.doe@domain.com>.

In the example on the right, we use the parsed name of the sender to greet them by name in the reply.

  @anvil.email.handle_message
  def handle_incoming_emails(msg):

    raise anvil.email.DeliveryFailure("Delivery failed for some reason")

To reject an incoming email, raise an anvil.email.DeliveryFailure exception. This will return an SMTP failure response (code 554) to the sender of the message.

Configuration Options

The Email Service has two configuration options:

  • Test Mode

    When test mode is enabled, all outgoing email will be sent to the owner of the app instead of the specified recipients. This allows you to test changes to your app without accidentally emailing your real users.

  • Custom Mail Server

    You can configure the Email Service to use your own SMTP server if you wish. Simply tick "Use Custom Mail Server", then enter your SMTP connection settings. The password will be encrypted and stored using the same mechanism as used by the Secrets Service. After entering the settings, click "Test settings" to check that they work.

Quotas

Outgoing email is subject to a monthly quota, depending on your account plan. If you exceed your monthly quota, all outgoing emails will be re-routed to the app owner, just like in Test Mode. Your monthly quota resets on the 1st of each month. Email sent through a custom SMTP server does not count towards your monthly quota.

Trusting Incoming Email

Email is easy to spoof - to give it an inaccurate From address. Therefore, it's important not to do drastic things (eg release sensitive data) just because you got an email claiming to be from someone.

Use a secret address

One way to verify that a message is genuine is to have a secret email address (for example 8317bb91@myapp.anvil.app). This functions like a password - only genuine users would know to send an email to that address!

A variation is to send an email from a secret address, and tell the user to reply to it. The real user's replies will go to the secret address, but nobody else knows the secret address.

Verify the sender with DKIM

Anvil has built-in support for DKIM, which lets the sending domain prove that an email is genuine. For example, every mail from someone@gmail.com is signed by GMail, so you can prove it's genuine.

@anvil.email.handle_message(require_dkim=True)
def handle_message(msg):
  print("This message is definitely from %s"
           % msg.envelope.from)

If you specify @anvil.email.handle_message(require_dkim=True), then you will only allow messages with valid DKIM signatures for the domain in msg.envelope.from. So, if msg.envelope.from is "someone@gmail.com", the email must have been signed by gmail.com.

@anvil.email.handle_message
def handle_message(msg):
  if msg.dkim.domains is not None and 
      "gmail.com" in msg.dkim.domains:
    print("This message was signed by GMail")

  elif msg.dkim.domains == []:
    print("This message wasn't signed at all")

You can also check this by hand. msg.dkim.domains is a list of all the domains that have signed this email (sometimes there can be more than one, but usually there are none or one).

msg.dkim.valid_from_sender is True if one of those domains is the domain of the sending address (msg.envelope.sender).

Technical notes for experts

Anvil's DKIM support accepts only signatures that cover the entire message (no l= parameter).

msg.dkim.domains is a list of the d= fields of all acceptable DKIM signatures.

msg.dkim.valid_from_sender is True only if the SMTP envelope from address ends in @<domain>, where domain is the d= field in an acceptable DKIM signature.

App Secrets Service

When your web app is handling sensitive information — be it API credentials, database passwords, or sensitive personal data — it's important to keep it protected. It certainly shouldn't be sitting in your source code, for anyone to see. It should be kept encrypted, until the time it's needed. This is called encryption at rest.

The App Secrets service provides easy-to-use encrypted storage of secrets (eg passwords) and encryption keys (which can be used to encrypt your data). Add it in the app browser:

Configuration

When you select the App Secrets service, you see a list of your app's secrets and encryption keys, which you can add, edit or delete:

Each secret has a name, and a value (a text string). The value is stored, encrypted, in the application source code. It is possible to extract the value from this service, but if you do so, an email will be sent to the application's owner.

Set Value - Set the value of a secret, overwriting any previous value. Use this to store known values, such as API credentials or the connection string for an external database.

Generate Value - This generates a strong random value (currently a 128-bit value expressed as base64). Use this to generate a new value (eg for a password).

You can also add, remove, or reset encryption keys. If you reset an encryption key, you replace it with a newly-generated key with the same name. Encryption keys are also stored, encrypted, as part of your application's source code.

Using secrets from code

token = anvil.secrets.get_secret('github_token')

To get the value of a secret, call anvil.secrets.get_secret().

This function can only be called from server module code (not client code, or even uplink code).

Using encryption keys from code

def encrypt_value(plaintext):
  return anvil.secrets.encrypt_with_key('encryption_key',
                                        plaintext)

def decrypt_value(ciphertext):
  return anvil.secrets.decrypt_with_key('encryption_key',
                                        ciphertext)

You can encrypt or decrypt strings, using an encryption key.

If the string you attempt to decrypt is not a valid ciphertext (ie it was not encrypted with the same key you are using for decryption, or it has been tampered with), an anvil.secrets.SecretError will be raised.

Secrets versioning

Secrets are versioned along with the code.

If you've published a particular version of your app using the Version History window, the published app will use the secrets as they were at that version. This ensures that the secrets and the code are always consistent. To update the secrets in your published app, you will need to save a new version and publish that.

If you are not pinning the published app to a particular version, but simply using the latest version as the published app, the latest secrets will be used.

Encryption architecture

App secrets are stored, encrypted, with the source code to your app. They are encrypted with an app-specific key, meaning that the encrypted secret cannot be copied into another app and used there.

All encryption is performed with 128-bit AES-GCM. This standard encryption operation avoids many common cryptographic pitfalls.

Threat model

The App Secrets service protects you against the following threats:

  • Database disclosure - Encrypting your data at rest means that, if your application contains a security vulnerability that discloses data, the data will remain encrypted and therefore useless to an attacker. (Successful breach would then require a second vulnerability that exposed decryption functionality. This is an example of defence in depth.)

  • Editor visibility - It prevents secrets from being visible "over-the-shoulder" while developers are working on the application, or when they have the application checked out with Git.

  • Cryptographic misuse - Cryptographic primitives are commonly misapplied, even by experts, leading to vulnerabilities. Anvil exposes a simple set of safe cryptographic operations that prevent common errors.

Security feedback

We welcome engagement from the security community. If you need to know more, have questions, or need to report a security vulnerability, please get in touch by email at security@anvil.works.

Customised audit

Anvil supports customised audit capabilities that enhance traceability of material from the app secrets service, protecting against insider threats. For more information on these enterprise features, please get in touch with us at security@anvil.works.

Google Service

Add the Google service to your project to use Google Drive, Google Sheets, GMail or Google authentication in your app.

You must add the Google service to your app in the App Browser in order to use anything discussed in this section. Watch our tutorial for a walkthrough.

Authenticating users

import anvil.google.auth

email_addr = anvil.google.auth.login()
print "User logged in as %s" % email_addr

To allow your users to log into your app with Google, call anvil.google.auth.login().

(Remember: You don't need the user to log in if you only use app files or send email. Those belong to the app, not the user.)

If the login succeeds, anvil.google.auth.login() will return the user's email address.

If the login fails, or the user cancels, anvil.google.auth.login() will raise an exception.

import anvil.google.auth

# This can run on the server:
email_addr = anvil.google.auth.get_user_email()
print "%s is now logged in" % email_addr

To find out who is currently logged in, call anvil.google.auth.get_user_email(). If nobody is logged in, this returns None.

The get_user_email function can be called from a server module, or uplink code, to check whether a user is authorised to perform the requested action.

Sending email with GMail

import anvil.google.mail

anvil.google.mail.send(
    to = "Sarah <sarah@example.com>",
    subject = "Meeting today",
    text = "See you at 5pm")

You can use Anvil to send email via your GMail account. Here is a simple example, sending a text-only email to one address.

Beware: GMail is not intended for heavy usage. If you send more than ~100 messages per day through Anvil, consider using the Email Service.

anvil.google.mail.send(
   from_address = "Al <al@example.com>",
   to = ["sarah@example.com",
         "Bob <bob@example.com>"],
   cc = "jane@example.com",
   bcc = "mark@example.com",
   subject = "Meeting today",
   text =
   """
   Dear all,

   There will be a meeting today at 5pm.

   Best Regards,
   Joseph
   """,
   html =
   """
   Dear all,

   <p>There will be a meeting today at <b>5pm</b>.

   <p>Best regards,
   <p>Joseph
   """)

Here is a more complex example, illustrating all the possible parameters to anvil.google.mail.send:

  • from_address - The email address from which this email is sent. Must be a string. You can add a name to the From field with a string like "Name <email@address.com>". (You can specify any name, but the email address must be an address registered to this GMail account.)

  • to - The email address(es) this email is sent to. Can be a string or a list of strings.

  • cc - The email address(es) to which this email is CCed. Can be a string or a list of strings.

  • bcc - The email address(es) to which this email is Blind CCed. Can be a string or a list of strings.

  • subject - The subject line of the email

  • text - The plain text of the email. You must specify either text or html (or both).

  • html - An HTML-formatted version of the email. You must specify either text or html (or both).

Google Drive

This service allows you to integrate Google Drive functionality into your Anvil app. You can select "app files" to which all users have access, or ask users to log in and then create, update and delete files in their own Google Drives.

To add the Google Drive service to your app, click the plus sign () next to Services in the App browser.

App files

from anvil.google.drive import app_files

f = app_files.hello_txt
print "File contents: " + f.get_bytes()

App files are files or folders from your Google Drive that are available to any user of the app. Your users do not have to log in with Google; these files can always be accessed by this app.

From the Google configuration page (under services in the App browser), you can click Add app files to add a file or folder from your Google Drive.

Each app file (or folder) has a Python identifier derived from its filename. You can access these files as app_files.<python-identifier>.

Permissions

App files can have three levels of permission, indicated by symbols in the Google configuration page:

Client can read and write
By default, any code in your app can read or modify an app file. This is convenient, but it also means anyone who can access your app could read or modify that file. (This is because the code in your forms - the client code - runs in the user's browser, so a malicious person could change that code to do whatever they like.) This is fine if you only share the app with people you trust - or if the app file is something you deliberately want the world to have access to.

Client can read
You can also make app files "read-only" for clients. This means that all of your code can read this app file, but only your server modules can change it.

No client access
Finally, you can make your app file entirely private. This means only your server modules can read or write it.

Files

from anvil.google.drive import app_files

f = app_files.hello_txt
f.set_bytes("My name is Bob.")

If you have an object representing a Google Drive file, you can get or set its contents as a string with the get_bytes() and set_bytes() functions.

from anvil.google.drive import app_files

f = app_files.my_file
f.set_media(self.file_loader_1.file)

You can also upload a Media object (for example, from a FileLoader component) to a Google File.

from anvil.google.drive import app_files

f = app_files.my_image

self.image_1.source = f

You can use a Google Drive file as a Media object. Here we use a Google Drive file as the source of an Image component:

from anvil.google.drive import app_files

folder = app_files.my_folder

new_file = folder.create_file("new_file.txt")

To create files, use the Folder.create_file method.

Calling create_file with a single file name argument creates a new file with MIME type text/plain.

new_file = folder.create_file("new_file.txt",
                              "Hello, world!")

new_image = folder.create_file("new_file.jpg",
                               file_loader_1.file)

You can also pass an optional second argument, providing the initial content of the file. This can either be a string, or a Media object. If it is a Media object, the new file is automatically created with the correct MIME type.

If possible, you should provide initial content when creating a file, rather than creating an empty file and then uploading its content. This way, if something goes wrong during upload then you won't end up with a new empty file.

Folders

from anvil.google.drive import app_files

folder = app_files.my_folder

for f in folder.list_files():
  print f["title"]

If you have an object representing a Google Drive folder, you can list the files in that folder with list_files().

list_files() returns an iterator, so you can loop over it in a for loop.

l1 = folder.files
l2 = folder.folders
print "This folder has %s files and %s folders in it" % (len(l1), len(l2))

If you want to do more than just loop through the files, you can request a full list of files directly. Bear in mind that this might be slow if there are many files in the folder.

Remember, a folder can contain other folders as well as files.

my_file = folder.get("my_file.txt")
my_folder = folder.get("My Subfolder")

You can even get an item by its title:

f = folder.create_file("new_file.txt")

id = f.id

# ... later ...

my_file = folder.get_by_id(id)

Every file has an ID, and you can get a file from a folder by its ID too, with get_by_id

File management

from anvil.google.drive import app_files

my_file = app_files.my_file
folder = app_files.my_folder

my_file.move(folder)

You can move a Drive item (file or folder) from one folder to another by calling move(<destination-folder>).

new_file = folder.create_file("new_file.txt")
new_folder = folder.create_folder("new_folder")

You can create a new file or folder, by calling create_file(<title>) or create_folder(<title>) on a folder.

file1 = folder.create_file("new_file.txt")
file2 = folder.create_file("new_file.txt")

saved_id = file1.id

# (... some time later ...)

# This will retrieve file1, not file2
f = folder.get_by_id(saved_id)

Google Drive permits you to create many files or folders with the same title. You can tell them apart by their id property, which is a unique string. If you want to store a reference to a file, you should use the id.

new_file.trash()
new_folder.delete()

You can put a Drive item in the trash by calling trash(), or delete it forever by calling delete() (this cannot be undone).

Google Sheets

db = app_files.my_sheet

Google Sheets make ideal databases for Anvil apps. You can access sheets using Google Drive in the same way you access files.

ws = db["Sheet 1"]

Once you have an object representing a Google Sheet, you can look up its individual worksheets. (It is also possible to look up a worksheet by number, eg db[0], or obtain a list with list(db.worksheets).)

print db["Sheet 1"].fields
# Expect output similar to: '[name, age]'

You should set up your Sheet using Google Drive so that the column names are in the first row. Once your sheet is set up, you can get a list of the fields in a worksheet using the fields attribute.

Fields will always be in lower case (even if your column headings use capital letters).

# Assuming columns "name" and "age"
for r in db["Sheet 1"].rows:
  print "%s is %s years old" % (r["name"], r["age"])

You can access rows of data like so:

# Convert age to dog years:
for r in db["Sheet 1"].rows:
  r["age"] = 7 * int(r["age"])

You can update them as well as reading them. You can assign numbers or strings, but the data you read from a row will always be a string:

ws = db["Sheet 1"]
row = ws.add_row(name="Bob", age=56)

You can add rows to your spreadsheet using the add_row(...) method:

(Note: add_row() normally returns the new created row. However, if you attempt to insert an empty row, it will return None.)

If you try to add a row containing a field that does not exist in the sheet, the field will be created automatically. You can change this behaviour by setting anvil.google.sheets.add_missing_fields to False, in which case an Exception will be raised if you try to set a non-existent field.

row.delete()

You can delete a row using the delete() method.

Beware: If you delete a row, other rows in the worksheet may shift unpredictably. If you delete a row, you should not re-use any other row objects from that worksheet. Call list_rows() again to find the rows you want again.

## Display the calculated value of
## the cell in row 1, col 2

print ws[1,2].value

## Display the formula in cell (1,2)

print ws[1,2].input_value

## Set the value of a cell

ws[3,4].value = "Some text"

It is also possible to access individual cells of your spreadsheet.

Using a logged-in user's files

As well as app files, you can use the Google service to access your users' own files when they log into your app with Google.

To do this, you will need a Google API client ID (same as for the Google REST API).

import anvil.google.drive

anvil.google.drive.login()

folder = anvil.google.drive.get_user_files()

for f in folder.list_files():
  print f["title"]

To read and write files in a user's Google Drive, you must first call anvil.google.drive.login(), which will ask the user for permission to access their files. This is different from calling anvil.google.auth.login(), which only asks for their email address.

Once a user has logged into your app using anvil.google.drive.login() you can read and write files in their Google Drive. (You can also pass a list of extra scopes to anvil.google.drive.login(), just like anvil.google.auth.login().)

You can get the top-level folder containing all the files in their drive by calling anvil.google.drive.get_user_files().

You will need to get a client ID and secret from the Google Developer Console if you want to use anvil.google.drive.login(). This client ID and secret must be pasted into the settings page for the Google service.

Google REST API

If you are an advanced user, you might want to use one of Google's REST APIs directly. To do this, you will need a Google API client id from the Google Developer Console . When registering the app with Google, set the "Authorized Redirect URI" to https://anvil.works/apps/_/client_auth_callback. The client ID and secret you generate must be pasted into the settings page for the Google service.

  # Log in and request access to a user's mailbox
  anvil.google.auth.login(["https://www.googleapis.com/auth/gmail.readonly"])

To request additional OAuth scopes, call anvil.google.auth.login() with a list of scope names. In this case we are requesting the ability to read the user's GMail inbox with the GMail API.

  access_token = anvil.google.auth.get_user_access_token()

  email_threads = anvil.http.request(
      "https://www.googleapis.com/gmail/v1/users/me/threads",
      json=True, headers={
        'Authorization':
          'Bearer ' + access_token
      })

Once logged in, you can get an access token for the current user's session by calling anvil.google.auth.get_access_token(). This token is a string, and you can use it to make requests to the Google REST API. In this example, we are getting a list of threads in the user's inbox using the GMail API.

  refresh_token = anvil.google.auth.get_user_refresh_token()

  # ... some time later ...

  access_token = anvil.google.auth.refresh_access_token(refresh_token)

Access tokens expire within a short period of time (typically less than an hour). If you want to retain access after this time, you need to store a refresh token. This refresh token can be converted into an access token later by calling anvil.google.auth.refresh_access_token().

Facebook Service

You can allow your users to sign in with Facebook using the Facebook Service.

Follow these steps to configure Facebook Login to allow Anvil to communicate with it:

  1. Create an App in Facebook for Developers, if you haven't already: go to https://developers.facebook.com/ and click on 'get started'.
  2. After some basic setup steps, you'll be presented with 'Add A Product'. Add Facebook Login.
  3. In the Facebook Login Product's Settings, there is a box marked 'Valid OAuth Redirect URIs'. Add https://anvil.works/apps/_/facebook_auth_callback to it.
  4. Now go to Settings->Basic. Copy the App ID and App Secret for your app.
  5. Add the Facebook Service to your Anvil app from the Anvil Editor, and paste your App ID and App Secret into the appropriate boxes.
  6. Add the Users Service and check the Sign In With Facebook box.
  7. Your login form will now show a 'Log In With Facebook' link, which opens the Facebook login dialog when clicked.

Stripe Service

Add the Stripe service to your project to accept credit card payments through Stripe in your app.

In order to use the Stripe service, you must connect your Stripe account. If you don't already have one, simply click "I need to create an account" on the Stripe service settings page. You can also sign up on the Stripe website, which asks for less information (just email, name and password).

Any payments you accept using the Anvil Stripe APIs will be subject to a 2% Anvil processing fee per transaction, in addition to the standard Stripe fees. (These fees do not apply when obtaining raw Stripe tokens)

Taking one-off payments

import stripe.checkout

# Take a payment of £9.99
charge = stripe.checkout.charge(currency="GBP", amount=999)
print "The result of the payment is: %s" % charge["result"]

To take a credit card payment from your app, call stripe.checkout.charge, supplying your required currency and the amount in pence (or equivalent). Make sure you import stripe.checkout first.

This code is just an example. There are many ways a charge could fail - if the user closes the dialog box, for example. You should make sure to wrap all calls to stripe.checkout.charge in a try...except block.

If the payment succeeds, stripe.checkout.charge will return a dict containing useful information about the charge, including an ID and URL that will allow you to look it up in your Stripe Dashboard. If the payment fails, or the user cancels, stripe.checkout.charge will raise an exception.

charge = stripe.checkout.charge(currency="GBP",
                                amount=999,
                                title="Acme Store",
                                description="3 Widgets",
                                zipcode=True,
                                icon_url=anvil.server.get_app_origin()+"_/theme/icon.png",
                                billing_address=True)

# Print a link to the transaction on the console:
print charge["url"]

You can specify extra, optional parameters to the charge function (see the example on the right). Set zipcode to True if you wish to verify the zipcode (postal code) of the customer when taking payment, and set billing_address to True in order to collect a billing address.

The stripe.checkout.charge function returns a dictionary containing useful information about the transaction. The example on the right displays a link to the new transaction on your Stripe dashboard. In practice, you would probably want to save this to a Google Spreadsheet or similar.

Setting the title, description and icon_url parameters will allow you to customise the payment form:

Payment form

Charging on the server

# In a form:
token, info = stripe.checkout.get_token(amount=999, currency="USD")
anvil.server.call('charge_user', token, info['email'])

# In a server module:
@anvil.server.callable
def charge_user(token, email):
  stripe_customer = anvil.stripe.new_customer(email, token)
  stripe_customer.charge(amount=999, currency="USD")

For greater security and control, you can collect card information in client code, and then make a charge or set up a subscription on the server. Because there are lots of regulatory issues with handling credit card details directly, Stripe turns card details into an opaque token. You can then pass this token to the server and make charges there.

Anvil has a simple built-in interface to the Stripe API, in the anvil.stripe module, which we will describe here. Like the client-side stripe.checkout... APIs, this API is also subject to the 2% Anvil fee per transaction, on top of the standard Stripe transaction charges. If you are using a Full Python server runtime, you may choose to use the full Stripe Python API (import stripe) directly, avoiding the extra fees.

Note that the get_token() function does not charge money, it only collects card information. The "amount" and "currency" supplied are just for display while collecting card details. get_token() takes the same arguments as charge().

customer = anvil.stripe.new_customer(email, token)
user['stripe_id'] = customer['id']

To create a new "Customer" object in Stripe, use anvil.stripe.new_customer(). You can use square brackets to look up attributes of the Customer object -- most useful is the id by which you can look them up again in future.

@anvil.server.callable
def add_card(token, email):
  user = anvil.users.get_user()

  if user['stripe_id'] is None:
    customer = anvil.stripe.new_customer(email, token)
    user['stripe_id'] = customer['id']
  else:
    customer = anvil.stripe.get_customer(user['stripe_id'])

  customer.add_token(token)

You can add payment methods to an existing customer with add_token(). Here is an example that creates a new customer, or retrieves an existing one, using a column in the Users table.

Recurring Subscriptions

subscription = customer.new_subscription("my-plan-id")

You can create recurring subscriptions for your customers using the new_subscription() function. Make sure you have created a plan ID in your Stripe Dashboard, then reference it as in the example on the right. You can optionally specify a quantity= parameter to charge multiples of a plan (eg if you want to charge per user). For more information, consult the Stripe API documentation.

if "my-plan-id" in customer.get_subscription_ids():
  # ...the user is subscribed...

You can check which subscription plans a user has by calling get_subscription_ids() to get a list of IDs. By default, it will only return "live" subscriptions (currently active; this corresponds to the Stripe states "live", "active" and "past_due"). You can change this by passing live_only=False.

for subscription in customer.get_subscriptions():
  subscription.cancel()

You can also call get_subscription() to get a list of subscriptions for that customer. By default, it will only return "live" subscriptions. You can change this by passing live_only=False.

Once you have a subscription object, you can use square brackets to access its full Stripe attributes, or call one of these methods:

  • subscription.cancel() - Cancel the subscription
  • subscription.is_live() - Returns True if this subscription is active (ie paid and up to date)
  • subscription.set_plan(plan_id) - Change which plan this subscription is for (eg if a user upgrades)

Testing Stripe

Stripe provides a simple way to test your app without making actual payments. When you first add the Stripe service, it is in TEST mode. In this mode, you can take 'dummy' payments using special, fake card details. These payments will show up in the test-mode dashboard on Stripe. See the Stripe documentation for more details.

When you are ready to accept real payments, simply click "Switch to live mode" on the Stripe service settings page.

You can find out whether you are in live or test mode by calling stripe.checkout.is_live_mode().

Raw Tokens in Stripe

If you're using the Full Python runtimes, you can choose not to use Anvil's simplified APIs, and instead use the official Stripe Python API from your Server Modules.

# Use Stripe Checkout to generate a raw token
token, info = stripe.checkout.charge(amount=999, currency="USD", raw=True)

To use this API, however, you will still need to collect users' card details and generate Stripe tokens. To do this, you will need to enter your Publishable Keys into Anvil's Stripe configuration page. In your Form code, you can then call stripe.checkout.get_token() with the raw=True parameter. This will generate a token that you can use with the Stripe Python API.

Charges made using the Stripe Python API will not incur Anvil processing fees.

Hint: If you do this, you'll need to put your Stripe Secret Key into your Anvil app. Use the App Secrets service to protect it, rather than leaving it in your source code!

Server Modules

The user has ultimate control over their own browser: If they open up their browser's developer tools or Javascript console, they can see and modify all the client-side code in your Anvil app (that's your forms and modules).

Sometimes, you want to write code that always executes exactly as it is written (for example, checking a password before changing a file), or code that contains some secret information (for example, your secret authentication keys for a third-party API).

You can write this code as a server module, and it will never be seen by your app's users (even if they're experts and poking around inside their own browser). You can call functions in your server modules from your client side code.

You can pass useful things like table rows and Media objects to and from server functions, as well as simple Python data structures (more information below).

Calling Server Functions

import anvil.server

@anvil.server.callable
def guess(number):
  CORRECT_NUMBER = 127

  if number < CORRECT_NUMBER:
    return "Too low!"
  elif number > CORRECT_NUMBER:
    return "Too high!"
  else:
    return "Correct!"

To make a server function available from your client code, decorate it as @anvil.server.callable.

# In client code (eg a form):
import anvil.server

def do_guess(n):
  result = anvil.server.call("guess", n)
  print "You guessed: " + n
  print "The server said: " + result
  return result

From ordinary client-side code (for example, the event handler in a form), you can call your server-side function using anvil.server.call(function_name), where function_name is the function name as a string.

Just call anvil.server.call(name, more_arguments...). Any extra arguments are passed through to the server function. This includes keyword arguments.

You can call server functions from other server functions, as well as from client-side code and uplink functions. (anvil.server.call() is available everywhere.)

import anvil.server

@anvil.server.callable("my_func")
def foo():
  return 42

You can customise the name of your callable function by passing a parameter to @anvil.server.callable. In this example, the function foo must be called with anvil.server.call('my_func').

def do_guess_silently(n):
  anvil.server.call_s("guess", n)

Using anvil.server.call in client code displays a spinner. To call server functions without displaying a spinner, use anvil.server.call_s.

Session state

import anvil.server

@anvil.server.callable
def ring_the_bell():

  n_rings = anvil.server.session.get("n_rings", 0)
  n_rings += 1

  print "DING!"
  print "You have rung the bell %d times" % n_rings

  anvil.server.session["n_rings"] = n_rings

In your server module, you might want to save information between calls in the same session. For example, you could store whether the user has logged in (and their username). In this example, we count how many times ring_the_bell() has been called.

Server modules have access to a dictionary called anvil.server.session. This data is private - it's never sent to the user's web browser - and it is saved between calls to server module functions. (All other variables will get wiped.)

The data in anvil.server.session is specific to the session - that is, each browser window running the app has its own anvil.server.session. So it will be different for each user.

import anvil.server
from anvil.google.drive import app_files

@anvil.server.callable
def check_password(password):
  if password == "MY SECRET":
    anvil.server.session["authenticated"] = True

@anvil.server.callable
def overwrite_file(new_data):
  if anvil.server.session.get("authenticated", False):
    app_files.my_file.set_bytes(new_data)

Here's another example, where we use anvil.server.session to record whether a user entered the correct password. We can later check that the user has authenticated before we let them upload data to an app file in Google Drive.

Session expiry

An Anvil session will stay open as long as an operation has been performed in the last 30 minutes.

If a user's session expires, the next server operation will cause the following dialog to appear:

Session Expired

Note that a "server operation" isn't always a call to anvil.server.call(). It could be a table search or a Google Drive query.

Recovering from an expired session

For most apps, the default behaviour is sufficient. However, some advanced apps might want to recover from this condition themselves. Here's how to do it.

# We think we might have timed out
try:
  anvil.server.call("foo")
except anvil.server.SessionExpiredError:
  anvil.server.reset_session()
  # This will work now, but with a blank session
  anvil.server.call("foo")

Under the hood, the server operation is raising an anvil.server.SessionExpiredError. If this exception is not caught, you will see the dialog.

Once the session has expired, every server operation will raise an SessionExpiredError. To stop this from happening, you must call anvil.server.reset_session().

You now have a fresh, blank session. This may cause some unexpected behaviour. For example:

  • You will be logged out from the Users service (anvil.users.get_user() will return None)
  • anvil.server.session will be reset to an empty dictionary ({})
  • Objects returned from the server (eg table rows and Google Drive files) will no longer be valid, and will raise exceptions if you try to use them.

We recommend that you manually catch SessionExpiredError only if you really need to.

Background Tasks

#################################################
# In a Server Module:
#################################################

@anvil.server.background_task
def my_task(n):
  # Do something slow, n times
  for i in range(n):
    crunch_numbers()
    # Report progress percent
    anvil.server.task_state['progress'] = int((i / float(n)) * 100)

@anvil.server.callable
def launch_my_task():
  # Crunch numbers 200 times, returning the task object
  # to the client. Background Tasks can only be launched
  # from server functions.
  return anvil.server.launch_background_task('my_task', 200)

#################################################
# On the client, with a button, timer and label:
#################################################

def button_1_click(**event_args):
  # Launch the task when the button is clicked
  self.task = anvil.server.call('launch_my_task')

def timer_1_tick(**event_args):
  # Update the label to display task progress
  if self.task:
    self.label_1.text = "Progress: %d%%" % self.task.get_state()['progress']

Anvil Server Functions are great for quick, synchronous tasks like retrieving something from a Data Table or verifying user authentication, but sometimes you need to run longer jobs that won't complete immediately, like sending a large number of emails, or performing some long-running data processing. Background Tasks are ideal for these situations. You can start a task by calling anvil.server.launch_background_task('my_task_name', args...), and this will immediately return a BackgroundTask object which you can use to query the state of the task or kill it.

To define a function that can be called as a background task, use the @anvil.server.background_task decorator. This behaves exactly like the @anvil.server.callable decorator, except the decorated function can be launched as a background task (with anvil.server.launch_background_task) rather than called as a function (with anvil.server.call).

Inside a Background Task function, you can update the anvil.server.task_state object to store any state information you wish. This can then be accessed from elsewhere by calling task.get_state() on a task object returned from anvil.server.launch_background_task(...). The task_state object is particularly useful for storing progress information. It is set to an empty dict by default, so you can immediately set key/value pairs: anvil.server.task_state['progress'] = 42.

To check whether a Background Task completed, call task.is_completed(). This will return True if the task completed successfully, False if the task is still running, or raise whatever exception killed the task if it has already failed.

To find out how a Background Task ended, call task.get_termination_status(). This will return:

  • None if the task is still running
  • "completed" if the task completed successfully
  • "failed" if the task terminated with an exception. Calling task.get_error() will re-throw whatever exception killed the task.
  • "killed" if the task was explicitly killed with task.kill(), or by clicking "Kill" in the development environment.
  • "missing" if an infrastructure failure caused us to lose track of the task

You can view the status of all your Background Tasks by choosing "Background Tasks..." from the gear menu in the app builder.

Cookies

Cookies allow you to store a small amount of information securely in the user's browser between sessions. Cookies can only be accessed/modified in server modules (or on the Uplink).

Anvil provides two cookies for you to choose from:

  • anvil.server.cookies.local is a private cookie available only to server modules in this app. Information you put in here is not accessible to any other app.

  • anvil.server.cookies.shared is a cookie shared by all your apps (or those in your team if you belong to one). Information in here is readable and writable by server modules in any of those apps.

# In a server module:

# Set a value with the default expiry of 30 days:
anvil.server.cookies.local['name'] = "Bob"

# Set multiple values with an expiry of 3 days:
anvil.server.cookies.local.set(3, name="Bob", age=42)

# Remove a value:
del anvil.server.cookies.local['age']

# Get a value, or return a default if not found:
anvil.server.cookies.local.get("name", "Not Found")

# Clear the cookie:
anvil.server.cookies.local.clear()

Cookies can be accessed like simple Python dictionaries to get and set values, including the usual get method to return a default value if the key is not found.

Values set using the standard [ ] indexing notation will expire after 30 days. To set a custom timeout, use the set(timeout, **vals) method, which accepts a custom timeout (in days), and the values as keyword arguments.

To clear all the keys/values in a cookie, call the clear() method.

Updates to cookies take effect immediately. This means that the user can navigate away from the page any time after a cookie has been updated and the data will persist.

Restrictions

Cookies can store any type of value that you can pass to/from a server function, apart from Media. This includes Data Table rows.

Bear in mind that cookies are limited by most web browsers to around 4Kb. This could be used up very quickly if you store more than the simplest objects, especially for a shared cookie on anvil.app. If a cookie is too large to accept the data you attempt to put in it, an anvil.server.CookieError will be raised.

Security considerations

Information in cookies is encrypted, so no-one can access the information, even by inspecting their own web browser. It is also authenticated, so if you read information from a cookie then you can be sure that you put that information there. It is not possible for a user to fake the value of one of their cookies, or to reuse the value of one cookie as the value of another.

Restrictions

  • The arguments and return values of server functions must be very simple. They may only be strings, numbers, lists, dicts, datetime.date, datetime.datetime or None, Media objects, or rows from a data table or Google spreadsheet. They may not be circular (for example, a dict may not contain itself).

  • anvil.server.session is even more restrictive - only strings, numbers, lists, dicts and None are allowed. (This will be addressed in a future Anvil update.)

  • By default, all variables are wiped clean after each server function call returns, except for anvil.server.session. This can be avoided by enabling the Persistent Server option, available on our Business plan and above.

  • Any naive datetime.datetime objects that are transferred between client and server (or vice versa) will have a timezone explicitly set before being sent. When leaving the server, they will be set to UTC, and when leaving the client they will be set to the timezone of the user's browser.

  • Server functions run synchronously and should return fairly quickly. By default, the timeout is set to 30 seconds, after which a running function will be stopped. For longer-running jobs, you should use Background Tasks.

HTTP APIs

import anvil.server

@anvil.server.http_endpoint("/users/:id")
def get_user(id, **params):
  return "You requested user %s with params %s" % id, params

You can build a programmatic HTTP API for your app by decorating server functions with the @anvil.server.http_endpoint decorator. All registered endpoints for your app are accessible at https://<your-app-id>.anvil.app/_/api..., or at https://your-custom-domain.com/_/api... if you have a custom domain. If your app is private, the endpoints will be at https://<your-app-id>.anvil.app/_/private_api/<your private access key>/....

You can think of URLs as having two parts: origin and path. The origin looks like https://<your-app-id>.anvil.app/_/api and tells Anvil how to route requests to your app. The path looks like /foo/:bar and is registered in your calls to the @anvil.server.http_endpoint decorator.

In the example on the right, if we navigate to https://<my-app-id>.anvil.app/_/api/users/42?x=foo, we will receive a response of You requested user 42 with params {'x': 'foo'}.

You can make a single endpoint respond to multiple request paths by using path parameters. In this example ("/users/:id", we match anything (except /) after the /users/ prefix, and assign the match to the id keyword argument of the function. You can also use path parameters in the middle of a path (/users/:id/history) or use multiple path parameters in the same path (/users/:user_id/history/:item_id).

Query-string parameters will be passed to your function as keyword arguments. In the example above, the params variable was used for that purpose.

The http_endpoint decorator

The @anvil.server.http_endpoint decorator makes your function callable over HTTP. It has one required argument - the path, e.g. /users/list. As described in the example above, the path may contain one or more path parameters, denoted by the : character, e.g. /users/:id.

There are also two optional keyword arguments, require_credentials and authenticate_users, both described in the "Authentication" section below.

The request object

HTTP requests have far more information associated with them than just path and query-string parameters. This information can be accessed through the anvil.server.request object, which is a thread-local variable containing information about the request currently being processed. The request object has the following attributes:

  • path - The path of this HTTP request.
  • method - The method of this HTTP request, e.g. GET, POST, etc.
  • query_params - The query-string parameters passed with this request, as a dictionary.
  • form_params - The form parameters passed with this request, as a dictionary.
  • origin - The URL origin of this HTTP request.
  • headers - Headers passed with this request, as a dictionary.
  • remote_address - The IP address of the source of this request.
  • body - The body of this HTTP request, as an anvil.Media object.
  • body_json - For requests with Content-Type: application/json, this is the decoded body as a dictionary. Otherwise None.
  • username - For authenticated requests (see below), returns the provided username. Otherwise None.
  • password - For authenticated requests (see below), returns the provided password. Otherwise None.
  • user - For authenticated requests, returns the row from the Users table representing the authenticated user.

Authentication

from anvil.server import http_endpoint, request

@http_endpoint("/protected", require_credentials=True)
def serve_protected_content():
  print("User %s connected with password %s" % (request.username, 
                                                request.password))

  # Check username and password before continuing...

The @anvil.server.http_endpoint decorator accepts the optional keyword argument require_credentials (default False). If this is set to True, remote users must provide a username and password through HTTP Basic Authentication. If credentials are not provided, a 401 Unauthorized response will be sent back automatically. It is your responsibility to check the provided username and password and return an appropriate response if the validation fails.

import anvil.server
from anvil.server import request

@anvil.server.http_endpoint("/protected", authenticate_users=True)
def serve_protected_content():
  print("Authenticated %s, who signed up on %s." % (request.user["email"], 
                                                    request.user["signed_up"]))

  # User is now authenticated.

Instead of setting require_credentials, you can set the authenticate_users keyword argument to True. This will automatically authenticate users against the Users Service in your app, where the provided username should be their email address. In this case, anvil.server.request.user will be set to the row from the Users table representing the authenticated user. Of course, you can also retrieve the logged-in user with the usual anvil.users.get_user() mechanism. If authentication fails, a 401 Unauthorized response will be sent back automatically.

Responding to HTTP requests

import anvil.server

@anvil.server.http_endpoint("/foo")
def serve_content():

  # This response will have Content-Type application/json
  return {"key": "value"}

Functions decorated with @anvil.server.http_endpoint can return strings (which will be returned with a Content-Type of text/plain), anvil.Media objects (which will be returned with their attached Content-Type), or any JSON-able object like a plain list or dict (which will be returned with Content-Type application/json).

import anvil.server

@anvil.server.http_endpoint("/foo")
def serve_content():

  response = anvil.server.HttpResponse(200, "Body goes here")
  response.headers["X-Custom-Header"] = "Custom value"

  return response

If you need more control over the response, you can return an anvil.server.HttpResponse object, providing a custom status code, body and header dictionary. Construct an HttpResponse object by optionally passing status code and body arguments, then set any required headers as in the example on the right.

Sometimes you will want to generate URLs that point to your HTTP endpoints without having to hard-code the origin of your app. For example, instead of writing:

endpoint_url = "https://my-app.anvil.app.net/_/api/users"

You can write:

endpoint_url = anvil.server.get_api_origin() + "/users"

This has the advantage of returning whichever origin the user is currently connected on, i.e. it will return your custom domain correctly.

You can also get the origin of the whole app to generate links for a browser:

app_url = anvil.server.get_app_origin()

Sometimes you want to write some server-side code that can't run on our servers. Perhaps it needs resources that are only available on your company network, or on your computer. You can connect code anywhere on the Internet to your Anvil app, by using the Anvil uplink.

The Anvil uplink is a library you add to your own code, running outside Anvil. It connects securely to the Anvil server, and allows your Anvil app to call functions in your project. You can also call server modules within your app from your uplinked code. The Anvil uplink works through most firewalls, as it initiates the connection to the Anvil server.

Start by selecting the Anvil Uplink in your app menu:

It will display an authentication key that you will use to connect your code to your app. Now follow the instructions for the programming language you are using.

You can connect both privileged (server) and unprivileged (client) code to your app using the uplink. The uplink dialog allows you to select a server or a client authentication key:

Server uplink code (connected with the server key) has the same privileges as server module code. As well as calling server functions, it can access data tables, log users in, and register new server functions with @anvil.server.callable. Use the server key for trusted code.

Client code (connected with the client key) has the same privileges as code in your app's forms. It cannot access data tables unless they are client-accessible, and it cannot register server functions. Use the client key for untrusted code which must authenticate before performing any privileged functions (for example, an Internet of Things device).

To use the Anvil uplink library in your Python project, first run pip install anvil-uplink on your machine.

# This is a simple program using Anvil Uplink to serve
# a file from my hard disk.

import anvil.server

@anvil.server.callable
def get_file():
  with open("./image.jpg") as f:
    return anvil.BlobMedia("image/jpeg", f.read())

# You will need to replace this with your app's connection key
anvil.server.connect("XXXXX-XXXXXXX")
anvil.server.wait_forever()

The API is exactly the same as for server modules. Mark functions with @anvil.server.callable if you want to call them from your app.

You will need to call anvil.server.connect() with your app's connection key in order to link this program with your Anvil app.

If you want your @anvil.server.callable functions to stay available, you will need to prevent your program from exiting immediately. The function anvil.server.wait_forever() sleeps in an infinite loop, but any solution will do.

# This is a simple program that makes a call to a server module.
import anvil.server

anvil.server.connect("XXXXX-XXXXXXX")

anvil.server.call("some_function")

You can also use anvil.server.call() from uplinked code.

This example does not register any @anvil.server.callable functions, and exits as soon as the call to some_function returns.


# Connect without printing to console
anvil.server.connect("XXXXX-XXXXXXX", quiet=True)

You can also pass an optional keyword argument "quiet" to anvil.server.connect(), in which case the default print output will be suppressed during the connection phase. Errors (such as connection failure and reconnection attempts) will still be printed.

import anvil.users

def setup():
  anvil.users.login_with_email("my_user@example.com", "MY_PASSWORD")

anvil.server.connect("XXXXX-XXXXXXX", init_session=setup)

anvil.server.call("some_func")

Sometimes, it is necessary to initialise an uplink's session before interacting with your app. For example, a client (unprivileged) uplink process might need to authenticate with the app before it will be allowed to perform certain operations.

If you pass the init_session= keyword parameter to anvil.server.connect, it will be called after the uplink connection is established, but before any other interaction (such as server calls or function registrations) have occurred. If connection is lost and the uplink library reconnects, init_session will be called again before any interaction occurs. This guarantees that init_session has completed whenever you interact with the Anvil app.

If init_session raises an exception, all subsequent Anvil interactions will fail and raise exceptions.

Media

All binary data (pictures, uploaded files, etc) is represented in Anvil as "Media objects". You can pass Media objects to and from server functions, store them in data tables, and use them with Anvil components.

Here are a few things you can do with Media objects:

  • Display a picture in an Image component by setting its source property to a Media object.

  • When a user uploads a file into a FileLoader component, the FileLoader's file property is a Media object.

  • You can open or download any Media by setting the url property of a Link to a Media object. This opens or downloads the media when the link is clicked.

  • You can store media in a data table, with the "Media" column type.

  • Canvas components can take a snapshot of what you've drawn as a Media object by calling get_image().

  • Google Drive files are Media objects. You can set the contents of a new Google Drive by passing a Media object to create_file(), or you can upload new contents to an existing file by calling set_media().

  • The image module allows you to resize images (the generate_thumbnail() function takes one Media object and returns another).

The rest of this section contains more advanced information on using Media objects directly.

Using Media objects directly

Media objects inherit from anvil.Media, and they have the following attributes:

  • content_type tells you the MIME type of this media. This is a string with values like "text/plain" or "image/png".

  • length is a number that tells you how many bytes long this media is.

  • name is a string containing the filename of this Media object (or None if it is anonymous)

  • url gives you a URL where this media can be downloaded, if this Media is "permanent" (eg if it is stored in data tables, or a Google Drive file). where this media can be downloaded, or None if it is not downloadable. If a Media object is not permanent (eg it is an anonymous BlobMedia object), its url property will be None. However, you can still download non-permanent Media objects using Link components, or display them with Image components.

    Some media's URLs are accessible from anywhere on the web (eg URLMedia('https://anvil.works/')). But some media objects provide a special URL, which is only valid for this browser session using this app. For example, the url of media from a data table is only valid in the session when it was generated.

  • get_bytes() is a method that returns the content as a string. For binary content such as images, this isn't very pretty to print out, but it gives you direct access to the raw content of this Media.

Constructing by hand

You can get Media objects from many places (for example, an uploaded file or Google Drive), but you can also construct your own.

my_media = URLMedia("https://anvil.works/ide/img/banner-100.png")

self.image_1.source = my_media

URLMedia gets media from a URL. This example gets media for the Anvil logo, and displays it in an Image component called image_1.

URLMedia only stores a URL; it does not fetch its data unless the length or content_type attributes are accessed, or get_bytes() is called. Because of browser security restrictions, it is often not possible to directly grab the bytes of an image from another website, even if you can display it in an Image component.

my_media = BlobMedia("text/plain", "Hello, world", name="hello.txt")

anvil.google.drive.app_files.my_file.set_media(my_media)

BlobMedia gets its media from a string you specify. This example makes a new BlobMedia containing a string of text, and then uploads it to an app file on Google Drive.

App Dependencies

Reusing Components and Code

Use form as component

You can reuse Custom Components and code from one Anvil app in another by making the app available as a library. Choose "Dependencies" from the App Browser gear icon.

Making an app available as a library

In the example on the right, we have made the current app available under the package name "my_app". This means that custom components and modules from this app can be used by any app that has this app as a dependency.

Using other apps as libraries

In this example, we have also imported the UsefulComponents app (which has the package name "useful_components"). Any custom components in that app will now show up in the Toolbox in this app:

Custom components in the toolbox

You can choose to depend on the Published or Development (most recent) version of the app. Note that if the library app does not have a published version, both options will refer to the most recent version.

from useful_components.Widget import Widget

self.add_component(Widget())

You can also refer to library apps in code by importing them as packages - see the example on the right. In this way, you can use custom components and modules from libraries directly.

Note that dependencies are transitive - that is, any dependencies of an app you depend on will also be available to your app.

Right now, it is only possible to depend on apps that you own (or your organisation owns). To use an app that someone else wrote as a library, you should clone their app and then depend on your copy.

Sharing Data Tables

The same Data Tables can be used by multiple apps. See Sharing Data Tables Between Apps for more information.

The Anvil Runtime

Python Versions

Anvil lets you write everything in Python - both in the web browser and on the server.

Your Form code, which runs in the browser, runs in approximately Python 2.7. We say "approximately", because in fact your Python code is being converted into Javascript so it can run right in the user's browser. The web browser is a constrained environment, so there are a couple of things missing (eg, you can't open a file in the browser!). That said, most things should work. If there's something that works for you in CPython, and doesn't work in an Anvil form, ask a question on the community forum.

Your Server Module code, which runs on the Anvil server, runs in a standard Python environment. For each app, you can choose between Python 2.7 or Python 3.7 for your server modules.

Free users must use our more restricted Python 2.7 sandbox, for performance and security reasons.

Technical note: Our client-side Python support uses the open source Skulpt Python-to-Javascript compiler. We believe strongly in giving back to open source - if you want to join us, visit us on GitHub.

Security Model

Anvil intends to provide an intuitive security model. The basic principle is, "If you can see it, you can use it". This is more formally known as a capability system.

For example, one cannot access any of an app's back-end services without knowing the (possibly secret) URL of that app. If a server module returns an object representing a database row or piece of media, the calling code can always read it.

Let's look at this in a little more detail:

The "Share" URL and client vs server code

The first layer of security protecting every app is a secret URL. An app's "Share" link contains an authentication token that is required in order to load the app. All apps on www.anvil.works are loaded over HTTPS.

You can reset and re-generate this authentication token from the "Share" dialog (accessible from the app browser). This makes all previous Share links useless, as the token they contain is now invalid.

Once the app is loaded, forms and modules ("client code") execute in the user's web browser. Because the user has full control over their own web browser, a malicious user can edit form and module code to do whatever they want. We call this untrusted code: we can't trust that Forms and modules will do what we tell them, and we must write our app knowing that any user (with the Share link) can edit the client code to do whatever they like. Server modules, by contrast, cannot be edited by the user, so we can trust them to do what we tell them.

For example, if you wanted to check a user's identity before letting them download a document, this should not be done in a method on the form. Forms are client code, so a malicious user could just edit the form to remove the authentication check! Instead, the form should call a server module, which performs the identity check and then returns the document. (We should then use permissions to make sure the client code can't access the document directly.)

Server communication

The app's communication with the server code (either explicitly, with server modules, or implicitly, by the use of services) occurs over WebSockets. This connection is authenticated with the token from the app URL, and secured over HTTPS.

This makes it safe to have entire apps secured only by their "Share" link. You can give the client code great privileges (eg write access to data tables, or Google Drive app files), as long as the URL is only shared with users who are trusted with full access to that data anyway.

Server communication occurs via an RPC (Remote Procedure Call) mechanism, as described in the server modules section.

Arguments and return values to RPC calls can be strings, numbers, dicts, lists, datetime.date/datetime, and None, and may not be circular. There are also two special sorts of objects that can be passed to and from the server:

  1. "Live Objects" such as rows from data tables and spreadsheets

  2. Media objects: Any media can be sent over RPC.

    Media objects that have an authoritative source (eg Google Drive files) will have valid url attributes. This makes them downloadable from the client's browser, but this is a restricted URL that will only be accessible from that browser session. Media objects that do not have an authoritative source (eg BlobMedia) will be copied into a new BlobMedia object.

Returning a special object to the client automatically grants read access to that object.

Example 1:

If a Google Drive app file's permissions are set to "No client access", but it is returned from a call to a server module, the client code can still use that file as a Media object. It cannot, however, edit that file's data or properties: the client just sees a read-only Media object.

Example 2:

If a server module returns one or more rows from a data table to client code, that client code can read what is in the row (and any linked rows), regardless of its permissions.

The client code cannot update that row unless the table permissions allow it to. However, the client code can pass that row back over RPC to a server module. The server module can then change the data in that row (presumably after checking that this operation should be permitted).

Anvil security policy

We take security very seriously. If you have a query or issue with the Anvil security model or implementation, please contact us at security@anvil.works.

Python Packages

Server modules that execute in full CPython version 2.7 or 3.7 have the following packages installed. This list is growing all the time, and if you wish to use a package not currently available, please contact us on support@anvil.works. We can usually get a new PyPI package installed for you within 24 hours.

Error Reporting

When an uncaught exception occurs in your Anvil app, it is displayed in the Output window if you are debugging in the Anvil editor. If you are running the app outside the Anvil editor, it will display a box at the bottom-right of the screen, as pictured here.

You can get a full stack trace of the error from the app logs.

Custom error handling

# This code displays an Anvil alert, rather than
# the default red box, when an error occurs.

def error_handler(err):
  alert(str(err), title="An error has occurred")

set_default_error_handling(error_handler)

You can install a custom handler for uncaught errors. The default error handler is a function that is called for any uncaught error in your code. It is passed the uncaught exception as its only argument.

If a custom error handler is set, it is called instead of the automatic pop-up box. This enables you to handle errors more smoothly, such as by connecting the user with customer support, or suggesting that they refresh the page.

The exception is always logged to the app log, whether or not you have set a custom error handler.

If a custom error handler throws an exception itself, Anvil will display the default red pop-up and log the new exception to the app log. Anvil will then disable the custom error handler (so as to avoid an infinite loop of errors), and fall back to the default pop-up from then on.

If the custom error handler re-raises the original exception (ie the one it got passed as an argument), the default red pop-up will appear, but the custom error handler will not be disabled.

Publishing an App

Anvil apps are automatically hosted - there's nothing you need to do to get it running.

A private URL is already set up for your app. Public URLs can be configured as well.

Publish privately

Your app is automatically published to a private URL of the format https://<something>.anvil.app/<private_key>. The <private_key> has sufficient entropy to be unguessable.

To see the private URL, click on 'Publish app' in the Gear menu in the App Browser:

Publishing privately

You can regenerate the <private_key> part by clicking the 'Reset Link' button in the Publish App dialog. This ensures anybody who has previously been given the private link can no longer see the app, unless you share the new link with them.

Publish publicly

To publish an app publicly, click on 'Share via public link':

Publishing publicly

By default, your app is assigned an auto-generated hostname composed of three words followed by anvil.app, e.g. happy-ancient-friend.anvil.app. If you are on a paid plan, you can modify the part before the anvil.app, or use a domain name of your choice, replacing the anvil.app with your own domain.

Using your own domain name

To use a custom domain, create a DNS A record pointing your domain to 52.56.203.177, and enter your domain in the box provided in the Publish App dialog.

See this HOWTO for tips on how to do this for some common domain registrars.

Adding a custom domain

Embedding your app in another web page

<script src="https://anvil.works/embed.js" async></script>

<iframe style="width:100%" data-anvil-embed src="https://UHW2FFPABHNLU2BD.anvil.app/3FVJOMF2YMVOADZJORAQI2GX"></iframe>

<iframe style="width:100%" data-anvil-embed src="https://KUOF2YMVOADZJO6X.anvil.app/2TJXJC4XVHNNDWRIIOFSQ77O"></iframe>

In the Publish dialog, you can choose whether to allow embedding of your app into other web pages. If embedding is disabled, your app will be served with the X-Frame-Options: deny header, and browsers will refuse to load your app if it is embedded inside another page.

If embedding is enabled, you see an HTML snippet you can copy and paste into any page. An example appears above on the right. If you are embedding multiple apps into one page, you only need to include the <script> tag once. If you omit the <script> tag entirely, your apps will still load, but the <iframe>s will no longer automatically resize to fit the vertical space used by your apps.

Published vs. Dev versions

If you want to work on your app without affecting the published version your users are interacting with, you can pin the published version to a particular version.

Click on 'View history' in the App Browser to get a tree of versions of your app. Each version has a 'Publish' link - click this and a 'PUBLISHED' icon appears next to that version. Now, users accessing your app will see this version. You can make changes without them affecting what the user sees.

Adding a custom domain

When you hit 'Run' in the Editor, you'll still see the development version. This allows you to test your development version. The published version will not be affected by this.

When using the Uplink, you have access to two keys: one for the published version, and one for the development version. This allows you to have published and development versions of the code you've connected to the Uplink as well. Note: the development version of your app will fall back to the published Uplink code if the development version is not available.

Anvil Service Plans

It is free to use Anvil, even for commercial purposes. However, certain features are only unlocked for users of one of our paid plans. These include:

  • Removing the Anvil branding banner from your apps

  • Using your own domain name for your apps

  • Using a wide range of Python libraries from your server modules.

All Anvil users are welcome to ask and answer questions on the Anvil community forum.

For more information on our price plans, see our pricing page, or contact sales@anvil.works.

Support Plans

We also offer live online support to keep you moving. Ask your questions right away, rather than waiting for a response to a forum post.

For more information on our support plans, contact support@anvil.works.

Anvil Private Instances

For users who need extra control or privacy for their data, we can provide a private instance of Anvil. This provides:

  • Protection for highly sensitive data that must remain within your network

  • On-site or private cloud (eg AWS) deployment

  • Direct SQL access to your data tables

  • Isolation from other users, with dedicated hardware

To learn more about on-site and private instances, click here, or email sales@anvil.works.