Python client driver for Fauna

Version: 1.1.2 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. The examples use Fauna’s demo data.

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

  • AWS Lambda

  • Vercel Functions

Installation

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

pip install fauna

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 Client.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="YOUR_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

To connect to Fauna, initialize a Client instance using a Fauna key, access token, or JWT:

client = Client(secret='YOUR_FAUNA_SECRET') # Your key, access token, or JWT

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.

Run FQL queries

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

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

By default, Client.query() uses query options from the Client configuration. You can pass options to Client.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 Client.paginate() to iterate a set that contains more than one page of results. Client.paginate() accepts the same Query options as Client.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="YOUR_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
    'endpoint': Endpoints.Default,
    'secret': 'YOUR_FAUNA_SECRET',
    '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': {'name': 'hello world query'},
    'query_timeout': timedelta(seconds=60),
    'typecheck': True,
}

client = Client(**config)

Max attempts

The maximum number of times a query will be attempted if a retryable exception is thrown (ThrottlingError). Defaults to 3, inclusive of the initial call. The retry strategy implemented is a simple exponential backoff.

To disable retries, pass max_attempts less than or equal to 1.

Max backoff

The maximum backoff in seconds to be observed between each retry. Defaults to 20 seconds.

Timeouts

There are a few different timeout settings that can be configured; each comes with a default setting. We recommend that most applications use the defaults.

Query timeout

The query timeout is the time, as datetime.timedelta, that Fauna will spend executing your query before aborting with a QueryTimeoutError.

The query timeout can be set using the query_timeout option. The default value if you do not provide one is DefaultClientBufferTimeout (5 seconds).

from datetime import timedelta
from fauna.client import Client

client = Client(query_timeout=timedelta(seconds=20))

The query timeout can also be set to a different value for each query using the QueryOptions.query_timeout option. Doing so overrides the client configuration when performing this query.

from datetime import timedelta
from fauna.client import Client, QueryOptions

response = client.query(myQuery, QueryOptions(query_timeout=timedelta(seconds=20)))

Client timeout

The client timeout is the time, as datetime.timedelta, that the client will wait for a network response before canceling the request. If a client timeout occurs, the driver will throw an instance of NetworkError.

The client timeout is always the query timeout plus an additional buffer. This ensures that the client always waits for at least as long Fauna could work on your query and account for network latency.

The client timeout buffer is configured by setting the client_buffer_timeout option. The default value for the buffer if you do not provide on is DefaultClientBufferTimeout (5 seconds), therefore the default client timeout is 10 seconds when considering the default query timeout.

from datetime import timedelta
from fauna.client import Client

client = Client(client_buffer_timeout=timedelta(seconds=20))

Idle timeout

The idle timeout is the time, as datetime.timedelta, that a session will remain open after there is no more pending communication. Once the session idle time has elapsed the session is considered idle and the session is closed. Subsequent requests will create a new session; the session idle timeout does not result in an error.

Configure the idle timeout using the http_idle_timeout option. The default value if you do not provide one is DefaultIdleConnectionTimeout (5 seconds).

from datetime import timedelta
from fauna.client import Client

client = Client(http_idle_timeout=timedelta(seconds=6))

Your application process may continue executing after all requests are completed for the duration of the session idle timeout. To prevent this, it is recommended to call Client.close() once all requests are complete. It is not recommended to set http_idle_timeout to small values.

Connect timeout

The connect timeout is the maximum amount of time, as datetime.timedelta, to wait until a connection to Fauna is established. If the client is unable to connect within this time frame, a ConnectTimeout exception is raised.

Configure the connect timeout using the http_connect_timeout option. The default value if you do not provide one is DefaultHttpConnectTimeout (5 seconds).

from datetime import timedelta
from fauna.client import Client

client = Client(http_connect_timeout=timedelta(seconds=6))

Pool timeout

The pool timeout specifies the maximum amount of time, as datetime.timedelta, to wait for acquiring a connection from the connection pool. If the client is unable to acquire a connection within this time frame, a PoolTimeout exception is raised. This timeout may fire if 20 connections are currently in use and one isn’t released before the timeout is up.

Configure the pool timeout using the http_pool_timeout option. The default value if you do not provide one is DefaultHttpPoolTimeout (5 seconds).

from datetime import timedelta
from fauna.client import Client

client = Client(http_pool_timeout=timedelta(seconds=6))

Read timeout

The read timeout specifies the maximum amount of time, as datetime.timedelta, to wait for a chunk of data to be received (for example, a chunk of the response body). If the client is unable to receive data within this time frame, a ReadTimeout exception is raised.

Configure the read timeout using the http_read_timeout option. The default value if you do not provide one is DefaultHttpReadTimeout (None).

from datetime import timedelta
from fauna.client import Client

client = Client(http_read_timeout=timedelta(seconds=6))

Write timeout

The write timeout specifies the maximum amount of time, as datetime.timedelta, to wait for a chunk of data to be sent (for example, a chunk of the request body). If the client is unable to send data within this time frame, a WriteTimeout exception is raised.

Configure the write timeout using the http_write_timeout option. The default value if you do not provide one is DefaultHttpWriteTimeout (5 seconds).

from datetime import timedelta
from fauna.client import Client

client = Client(http_write_timeout=timedelta(seconds=6))

Query options

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

  • Client.query()

  • Client.paginate()

You can pass an QueryOptions argument 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)

Event Streaming

Event Streaming is currently available in the beta Python driver.

To install the beta driver:

pip install fauna==1.2.0b4

Start a stream

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

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

import fauna

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

client = Client(secret='YOUR_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 Client.stream():

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

client.stream(query)

Iterate on a stream

Client.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, .quantity)')

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 <stream>.close() to close a stream:

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

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='YOUR_FAUNA_SECRET')

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

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!