Skip to content

Concepts

Multiple sessions

A session holds a namespace and database, a set of variables and a sign-in. This page shows how to run several sessions over one WebSocket connection with the Python SDK, so that one connection can work in several databases, or for several users, at the same time.

Sessions need a WebSocket connection (ws:// or wss://) and SurrealDB 3.0 or later. On an HTTP or embedded connection, the session methods raise UnsupportedFeatureError. Against SurrealDB 2.x, they raise UnavailableFeatureError.

Method

Description

db.new_session()

Starts a new session with the sign-in of the connection

session.use(namespace?, database?)

Selects a namespace and database for the session

session.fork()

Starts a new session as a copy of the session

db.fork_session(session_id?)

Starts a new session as a copy of the connection's own session, or of another session

db.sessions()

Lists the ids of the sessions on the connection

session.reset()

Clears the namespace, database, variables and sign-in of the session

session.close_session()

Ends the session on the server

session.begin_transaction()

Begins a transaction in the session

db.attach()

Starts a session that is not signed in, and returns its id

db.detach(session_id)

Ends a session by its id

new_session() starts a session on the server and returns a session object: a BlockingSurrealSession on a blocking connection, or an AsyncSurrealSession on an asynchronous one. The session has the same query, data and authentication methods as the connection, and id holds its UUID.

from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()
db.signin({"username": "root", "password": "secret"})
db.use("surrealdb", "docs")

reports = db.new_session()
print(reports.query("RETURN session::db()").first())
Output
None

The examples on the rest of this page continue with db and reports.

A new session starts with the sign-in of the connection, but with nothing else from it. new_session() authenticates the session with the access token of the connection, so the session acts as the same user. The namespace, database and variables of the connection are not copied, so the session above has no database yet.

The exception is a user whose token names a namespace and database, such as a record user or a database user. The server selects those for every session that authenticates with the token.

Each session has its own namespace and database. A call to use() on a session does not change the connection or any other session:

reports.use("surrealdb", "reports")

print(reports.query("RETURN session::db()").first())
print(db.query("RETURN session::db()").first())
Output
reports
docs

use() on a session takes the same arguments as on the connection, so reports.use(database="archive") changes only the database of the session. Variables set with let() belong to the session in the same way.

A session can sign in as a different user. The sign-in replaces the user that the session started with, and does not change the connection or the other sessions. This lets one connection run requests for several users, each with their own permissions.

The example below signs a session in with the account record access method from Authentication:

customer = db.new_session()
customer.signin({
    "namespace": "surrealdb",
    "database": "docs",
    "access": "account",
    "variables": {
        "email": "tobie@surrealdb.com",
        "password": "hunter123",
    },
})

print(customer.query("RETURN session::ac()").first())
print(db.query("RETURN session::ac()").first())
Output
account
None

The customer session now runs as the record user, with the permissions of that user, while the connection stays signed in as the root user, which has no access method. Every authentication method works on a session: signin(), signup(), authenticate(), invalidate(), refresh() and revoke().

fork() starts a new session as a copy of an existing one. The copy gets the namespace, database, variables and sign-in of the session at that moment, and the two sessions are independent from then on:

copy = reports.fork()
print(copy.query("RETURN session::db()").first())
Output
reports

fork_session() on the connection does the same. Without an argument, it copies the connection's own session. With a session id, it copies that session, which must be open on the same connection:

from_connection = db.fork_session()
print(from_connection.query("RETURN session::db()").first())

from_reports = db.fork_session(reports.id)
print(from_reports.query("RETURN session::db()").first())
Output
docs
reports

A copy gets the access token of the original session but not its refresh token, because the server accepts a refresh token only once. To renew the copy, sign it in, or refresh the original session and call authenticate() on the copy with the new access token.

sessions() returns the ids of the sessions that are open on the connection, without the connection's own session. Each one is the id of a session object:

ids = db.sessions()
print(reports.id in ids)
Output
True

reset() returns a session to how it started on the server: it clears the namespace, the database, the variables and the sign-in. Sign the session in and select a namespace and database again before you use it.

copy.reset()
copy.signin({"username": "root", "password": "secret"})
copy.use("surrealdb", "docs")

close_session() ends the session on the server. The connection and its other sessions stay open. A request on a closed session raises NotFoundError, with a message such as Session not found.

copy.close_session()

Sessions are not context managers. To close a session when your code has finished with it, also when that code raises an exception, use try and finally:

from surrealdb import RecordID

archive = db.new_session()
try:
    archive.use("surrealdb", "archive")
    archive.create(RecordID("person", "tobie"), {"name": "Tobie"})
finally:
    archive.close_session()

Closing the connection ends all of its sessions. After close() and connect(), the session objects refer to sessions that no longer exist, so create new ones.

begin_transaction() on a session begins a transaction that runs in that session, with its namespace, database and sign-in:

from surrealdb import RecordID

with reports.begin_transaction() as txn:
    txn.create(RecordID("person", "tobie"), {"name": "Tobie"})
    txn.create(RecordID("person", "jaime"), {"name": "Jaime"})

The transaction commits when the block ends, and cancels if the block raises an exception. See Transactions for the details.

When the socket drops, the connection reconnects and restores every open session under the same id. For each session, it restores the namespace and database, the variables and the sign-in, and then starts the live queries of the session again. Your session objects keep working after the reconnect.

If the connection stops reconnecting after its last attempt, it forgets every session. A request on an old session object then raises NotFoundError.

The session objects call lower-level connection methods for you. Use these methods directly only when you manage session ids yourself:

  • attach() starts a session and returns its UUID. Unlike new_session(), it does not sign the session in.

  • session_id= is a keyword argument on most connection methods, such as query(), use(), signin() and create(), that runs the call in that session.

  • detach(session_id) ends the session.

session_id = db.attach()
db.authenticate(db.token, session_id=session_id)
db.use("surrealdb", "reports", session_id=session_id)

result = db.query("RETURN session::db()", session_id=session_id)
print(result.first())

db.detach(session_id)
Output
reports

Was this page helpful?