Python client driver

Version: 2.1.0 Repository: fauna/fauna-python

Fauna’s Python client driver lets you run FQL queries from Python applications.

This guide shows how to set up the driver and use it to run FQL queries.

This driver can only be used with FQL v10. It’s not compatible with earlier versions of FQL. To use earlier FQL versions, use the faunadb package.

Supported Python versions

  • Python 3.9

  • Python 3.10

  • Python 3.11

  • Python 3.12

Supported cloud runtimes

Installation

The driver is available on PyPI. To install it, run:

pip install fauna

API reference

API reference documentation for the driver is available at https://fauna.github.io/fauna-python/. The docs are generated using https://pdoc.dev/docs/pdoc.[pdoc^].

Basic usage

The following application:

  • Initializes a client instance to connect to Fauna

  • Composes a basic FQL query using an fql template

  • Runs the query using query()

from fauna import fql
from fauna.client import Client
from fauna.encoding import QuerySuccess
from fauna.errors import FaunaException

# Initialize the client to connect to Fauna
client = Client(secret='FAUNA_SECRET')

try:
    # Compose a query
    query = fql(
        """
        Product.sortedByPriceLowToHigh() {
            name,
            description,
            price
            }"""
    )

    # Run the query
    res: QuerySuccess = client.query(query)
    print(res.data)
except FaunaException as e:
    print(e)
finally:
    # Clean up any remaining resources
    client.close()

Connect to Fauna

Each Fauna query is an independently authenticated request to the Query HTTP API endpoint. You authenticate with Fauna using an authentication secret.

Get an authentication secret

Fauna supports several secret types. For testing, you can create a key, which is a type of secret:

  1. Log in to the Fauna Dashboard.

  2. In the Dashboard, create a database and navigate to it.

  3. In the upper left pane of Dashboard’s Explorer page, click the demo database, and click the Keys tab.

  4. Click Create Key.

  5. Choose a Role of Server.

  6. Click Save.

  7. Copy the Key Secret. The secret is scoped to the database.

Initialize a client

To send query requests to Fauna, initialize a Client instance using a Fauna authentication secret:

client = Client(secret='FAUNA_SECRET')

If not specified, secret defaults to the FAUNA_SECRET environment variable. For other configuration options, see Client configuration.

Multiple connections

You can use a single client instance to run multiple asynchronous queries at once. The driver manages HTTP connections as needed. Your app doesn’t need to implement connection pools or other connection management strategies.

You can create multiple client instances to connect to Fauna using different credentials or client configurations.

AWS Lambda connections

AWS Lambda freezes, thaws, and reuses execution environments for Lambda functions. See Lambda execution environment.

When an execution environment is thawed, Lambda only runs the function’s handler code. Objects declared outside of the handler method remain initialized from before the freeze. Lambda doesn’t re-run initialization code outside the handler.

Fauna drivers keep socket connections that can time out during long freezes, causing ECONNRESET errors when thawed.

To prevent timeouts, create Fauna client connections inside function handlers. Fauna drivers use lightweight HTTP connections. You can create new connections for each request while maintaining good performance.

Run FQL queries

Use fql templates to compose FQL queries. Run the queries using query():

query = fql("Product.sortedByPriceLowToHigh()")
client.query(query)

By default, query() uses query options from the Client configuration. You can pass options to query() to override these defaults. See Query options.

Variable interpolation

The driver supports queries with Python primitives, lists, and dicts.

Use ${} to pass native Python variables to fql queries as kwargs. You can escape a variable by prepending an additional $.

# Create a native Python var
collection_name = 'Product'

# Pass the var to an FQL query
query = fql('''
  let collection = Collection(${collection_name})
  collection.sortedByPriceLowToHigh()''',
  collection_name=collection_name)

client.query(query);

Passed variables are encoded to an appropriate type and passed to Fauna’s HTTP API. This helps prevent injection attacks.

Subqueries

You can use native variables to pass an FQL query to another FQL query. This lets you create reusable subqueries:

# Create a reusable FQL subquery
def get_product(name):
  return fql(
    'Product.byName(${name}).first()',
    name=name)

# Use the subquery in another FQL query
query = fql('''
  let product = ${get_product}
  product?.update({
    name: "pizza pie"
  })''',
  get_product=get_product('pizza'))

client.query(query);

Pagination

Use paginate() to iterate a set that contains more than one page of results. paginate() accepts the same Query options as query().

# Adjust `pageSize()` size as needed.
query = fql('''
  Product.sortedByPriceLowToHigh()
    .pageSize(2)''')

pages = client.paginate(query);

for products in pages:
    for product in products:
        print(products)

Query statistics

Successful query responses and ServiceError errors return query statistics:

from fauna import fql
from fauna.client import Client
from fauna.errors import ServiceError

client = Client(secret='FAUNA_SECRET')

try:
    query = fql('"Hello world"')
    res = client.query(query)
    print(res.stats)
except ServiceError as e:
    if e.stats is not None:
        print(e.stats)
    # more error handling...

User-defined classes

Serialization and deserialization with user-defined classes is not supported.

When composing FQL queries, adapt your classes into dicts or lists. When instantiating classes from a query result, build them from the expected result.

class MyClass:
    def __init__ (self, my_prop):
        self.my_prop = my_prop

    def to_dict(self):
        return { 'my_prop': self.my_prop }

    @static_method
    def from_result(obj):
        return MyClass(obj['my_prop'])

Client configuration

The Client instance comes with reasonable configuration defaults. We recommend using the defaults in most cases.

If needed, you can configure the client to override the defaults. This also lets you set default Query options.

from datetime import timedelta
from fauna.client import Client
from fauna.client.headers import Header
from fauna.client.endpoints import Endpoints

config = {
    # Configure the client
    'secret': 'FAUNA_SECRET',
    'endpoint': Endpoints.Default,
    'client_buffer_timeout': timedelta(seconds=5),
    'http_read_timeout': None,
    'http_write_timeout': timedelta(seconds=5),
    'http_connect_timeout': timedelta(seconds=5),
    'http_pool_timeout': timedelta(seconds=5),
    'http_idle_timeout': timedelta(seconds=5),
    'max_attempts': 3,
    'max_backoff': 20,

    # Set default query options
    'additional_headers': {'foo': 'bar'},
    'linearized': False,
    'max_contention_retries': 5,
    'query_tags': {'tag': 'value'},
    'query_timeout': timedelta(seconds=60),
    'typecheck': True,
}

client = Client(**config)

For supported parameters, see Client in the API reference.

Environment variables

By default, secret and endpoint default to the respective FAUNA_SECRET and FAUNA_ENDPOINT environment variables.

For example, if you set the following environment variables:

export FAUNA_SECRET=FAUNA_SECRET
export FAUNA_ENDPOINT=https://db.fauna.com/

You can initialize the client with a default configuration:

client = Client()

Retries

By default, the client automatically retries a query if the request returns a 429 HTTP status code. Retries use an exponential backoff.

Use the Client configuration's max_backoff parameter to set the maximum time between retries. Similarly, use max_attempts to set the maximum number of retry attempts.

Query options

The Client configuration sets default query options for the following methods:

  • query()

  • paginate()

You can pass a QueryOptions object to override these defaults:

options = QueryOptions(
    additional_headers={'foo': 'bar'},
    linearized=False,
    max_contention_retries=5,
    query_tags={'name': 'hello world query'},
    query_timeout=timedelta(seconds=60),
    traceparent='00-750efa5fb6a131eb2cf4db39f28366cb-000000000000000b-00',
    typecheck=True
)

client.query(fql('"Hello world"'), options)

For supported properties, see QueryOptions in the API reference.

Event streaming

The driver supports Event streams.

Start a stream

To get a stream token, append set.toStream() or set.changeOn() to a set from a supported source.

To start and subscribe to the stream, pass the stream token to stream():

import fauna

from fauna import fql
from fauna.client import Client, StreamOptions

client = Client(secret='FAUNA_SECRET')

response = client.query(fql('''
  let set = Product.all()
  {
    initialPage: set.pageSize(10),
    streamToken: set.toStream()
  }
  '''))

initialPage = response.data['initialPage']
streamToken = response.data['streamToken']

client.stream(streamToken)

You can also pass a query that produces a stream token directly to stream():

query = fql('Product.all().changesOn(.price, .stock)')

client.stream(query)

Iterate on a stream

stream() returns an iterator that emits events as they occur. You can use a generator expression to iterate through the events:

query = fql('Product.all().changesOn(.price, .stock)')

with client.stream(query) as stream:
    for event in stream:
        eventType = event['type']
        if (eventType == 'add'):
            print('Add event: ', event)
            ## ...
        elif (eventType == 'update'):
            print('Update event: ', event)
            ## ...
        elif (eventType == 'remove'):
            print('Remove event: ', event)
            ## ...

Close a stream

Use close() to close a stream:

query = fql('Product.all().changesOn(.price, .stock)')

count = 0
with client.stream(query) as stream:
    for event in stream:
        print('Stream event', event)
        # ...
        count+=1

        if (count == 2):
            stream.close()

Error handling

If a non-retryable error occurs when opening or processing a stream, Fauna raises a FaunaException:

import fauna

from fauna import fql
from fauna.client import Client
from fauna.errors import FaunaException

client = Client(secret='FAUNA_SECRET')

try:
    with client.stream(fql(
        'Product.all().changesOn(.price, .stock)'
    )) as stream:
        for event in stream:
            print(event)
        # ...
except FaunaException as e:
    print('error ocurred with stream: ', e)

Stream options

The Client configuration sets default options for the stream() method.

You can pass a StreamOptions object to override these defaults:

options = StreamOptions(
    max_attempts=5,
    max_backoff=1,
    start_ts=1710968002310000,
    status_events=True
)

client.stream(fql('Product.all().toStream()'), options)

For supported properties, see StreamOptions in the API reference.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!