Python driver tutorial

In this tutorial, we’ll use a sample application to showcase how to define a schema, insert initial data, read data, apply pagination and inference, and add additional data.

Prerequisites

Deploy TypeDB Cloud, or install and run TypeDB Core.

Install the Python driver.

Running the sample app will result in a deletion of a database named iam if it exists already on the server.

Source code

See the full source code of the sample app below:

from typedb.driver import TypeDB, SessionType, TransactionType, TypeDBOptions
from datetime import datetime
DB_NAME = "iam"
SERVER_ADDR = "127.0.0.1:1729"

print("IAM Sample App")

print("Attempting to connect to a TypeDB Core server: ", SERVER_ADDR)
with TypeDB.core_driver(SERVER_ADDR) as driver:  # Connect to TypeDB Core server
    if driver.databases.contains(DB_NAME):
        print("Found a pre-existing database! Re-creating with the default schema and data...")
        driver.databases.get(DB_NAME).delete()
    driver.databases.create(DB_NAME)
    if driver.databases.contains(DB_NAME):
        print("Empty database created.")
    print("Opening a Schema session to define a schema.")
    with driver.session(DB_NAME, SessionType.SCHEMA) as session:
        with session.transaction(TransactionType.WRITE) as transaction:
            with open('iam-schema.tql', 'r') as data:
                define_query = data.read()
            transaction.query.define(define_query)
            transaction.commit()
    print("Opening a Data session to insert data.")
    with driver.session(DB_NAME, SessionType.DATA) as session:
        with session.transaction(TransactionType.WRITE) as transaction:
            with open('iam-data-single-query.tql', 'r') as data:
                insert_query = data.read()
            transaction.query.insert(insert_query)
            transaction.commit()
        print("Testing the new database.")
        with session.transaction(TransactionType.READ) as transaction:  # Re-using a session to open a new transaction
            test_query = "match $u isa user; get $u; count;"
            response = transaction.query.get_aggregate(test_query)
            result = response.resolve().as_value().as_long()
            if result == 3:
                print("Database setup complete. Test passed.")
            else:
                print("Test failed with the following result:", result, " expected result: 3.")
                exit()

    print("Commencing sample requests.")
    with driver.session(DB_NAME, SessionType.DATA) as session:
        print("\nRequest #1: User listing")
        with session.transaction(TransactionType.READ) as transaction:
            typeql_read_query = "match $u isa user, has full-name $n, has email $e; get;"
            iterator = transaction.query.get(typeql_read_query)  # Executing the query
            k = 0  # Reset counter
            for item in iterator:  # Iterating through results
                k += 1
                print("User #" + str(k) + ": " + item.get("n").as_attribute().get_value() + ", has E-Mail: " + item.get("e").as_attribute().get_value())
            print("Users found:", k)

        print("\nRequest #2: Files that Kevin Morrison has access to")
        with session.transaction(TransactionType.READ) as transaction:
            typeql_read_query = "match $u isa user, has full-name 'Kevin Morrison'; $p($u, $pa) isa permission; " \
                                "$o isa object, has path $fp; $pa($o, $va) isa access; get $fp;"
            iterator = transaction.query.get(typeql_read_query)
            k = 0
            for item in iterator:
                k += 1
                print("File #" + str(k) + ": " + item.get("fp").as_attribute().get_value())
            print("Files found:", k)

        print("\nRequest #3: Files that Kevin Morrison has view access to (with inference)")
        with session.transaction(TransactionType.READ, TypeDBOptions(infer=True)) as transaction:  # Inference enabled
            typeql_read_query = "match $u isa user, has full-name 'Kevin Morrison'; $p($u, $pa) isa permission; " \
                                "$o isa object, has path $fp; $pa($o, $va) isa access; " \
                                "$va isa action, has name 'view_file'; get $fp; sort $fp asc; offset 0; limit 5;"  # Only the first five results
            iterator = transaction.query.get(typeql_read_query)
            k = 0
            for item in iterator:
                k += 1
                print("File #" + str(k) + ": " + item.get("fp").as_attribute().get_value())

            typeql_read_query = "match $u isa user, has full-name 'Kevin Morrison'; $p($u, $pa) isa permission; " \
                                "$o isa object, has path $fp; $pa($o, $va) isa access; " \
                                "$va isa action, has name 'view_file'; get $fp; sort $fp asc; offset 5; limit 5;"  # The next five results
            iterator = transaction.query.get(typeql_read_query)
            for item in iterator:
                k += 1
                print("File #" + str(k) + ": " + item.get("fp").as_attribute().get_value())
            print("Files found:", k)

        print("\nRequest #4: Add a new file and a view access to it")
        with session.transaction(TransactionType.WRITE) as transaction:  # Open a transaction to write
            filename = "logs/" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".log"
            typeql_insert_query = "insert $f isa file, has path '" + filename + "';"
            transaction.query.insert(typeql_insert_query)  # Executing the query to insert the file
            print("Inserted file:", filename)
            typeql_insert_query = "match $f isa file, has path '" + filename + "'; " \
                                  "$vav isa action, has name 'view_file'; " \
                                  "insert ($vav, $f) isa access;"
            transaction.query.insert(typeql_insert_query)  # Executing the second query in the same transaction
            print("Added view access to the file.")
            transaction.commit()  # commit transaction to persist changes

Additional files

Schema and initial data

For running the sample application, use the following schema and insert query for the sample dataset insertion.

Building and running

Place all the files in the same folder and run:

python .\sample.py

Explanation

This section is for comments and explanation of the sample application implementation details.

Database setup

First of all, we connect to a TypeDB Core server by initializing a driver instance.

We proceed to check whether a database named iam exists on the server already. We delete such a database if it exists to prevent any possible schema or data conflicts. Then we create a new database with the name iam.

We open a schema session to this database, open a write transaction, read the iam-schema.tql file, and send its contents as a Define query. Next, we commit the transaction to persist the resulted schema.

After that we open a data session, a write transaction, read the iam-data-single-query.tql file, and send its contents as an Insert query. We commit the transaction again and re-using the same data session to open a read transaction.

With the new read transaction, we test the resulted database by retrieving the number of users in it.

For more information on server connections, sessions, and transactions, see the Connecting to TypeDB page.

Request #1: User listing

We open a data session that will be re-used for multiple requests, open a read transaction, and send a simple TypeQL query to get all users with full-name and email attributes.

match
$u isa user,
    has full-name $n,
    has email $e;
get;

We iterate through results to print every user and the total count of users.

Query explanation

This is a Get query that includes match and get clauses.

We go through all entities of the user type that have a full-name attribute and email attribute.

A get clause with no variables and just a semicolon means, that we are retrieving concepts for all variables from the preceding match clause pattern in every result.

Request #2: Files

We open a new read transaction in the same data session and send a TypeQL query to get paths of all filenames that user with the full-name of Kevin Morrison has access to.

match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
get $fp;

We iterate through results to print every file and the total count of files.

Query explanation

This is a Get query that includes match and get clauses.

A match clause pattern looks for a user (variable $u) with attribute full-name of value Kevin Morrison assigned, a permission relation ($p) in between this user $u and potential access $pa. Additionally, we state that an object ($o) with a path $fp should be a part of $pa access relation, without having to specify what kind of action $va it should be.

A get clause filters response to only contain the matched path attributes ($fp).

Request #3: Inference

We start with opening a new transaction with the inference option enabled.

Then we send a query to get Kevin’s files, but this time we specify the access action to be view_file, since the permission for this access wasn’t inserted in the sample dataset and must be inferred by the add-view-permission rule. We also add sort, offset and limit modifiers to the query to manage pagination. This request consists of two queries splitting the results between queries.

Query 1
match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
$va isa action, has name 'view_file';
get $fp;
sort $fp asc;
offset 0;
limit 5;

And then, we repeat the query with different offset to get the rest of results.

Query 2
match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
$va isa action, has name 'view_file';
get $fp;
sort $fp asc;
offset 5;
limit 5;

Query explanation

We update the previous query to set the type of action ($va) to the view_file value. We still get only path ($fp), but now we add sorting and pagination: we set offset and limit for the results.

Note that Kevin has been assigned only modify_file access, and the view_file access is being inferred by a rule. To use inference in this query, we modify TypeDB options and send the modified set of options to the transaction call.

Request #4: Add a new file

To be able to insert new data, we open a write transaction this time. Then we generate a filename from current time and date and insert an instance of the file type that owns the attribute of type path and value of the generated filename.

insert $f isa file, has path 'logs/2023-12-19-19-11-36.log';

After that we do another query to match the new file we just inserted and add a relation of the access type with it:

match
$f isa file, has path 'logs/2023-12-19-19-11-36.log';
$vav isa action, has name 'view_file';
insert
($vav, $f) isa access;

Query explanation

The first query inserts a file entity that owns a path attribute with the value of a generated filename.

With the second query, we look for a file entity that has the attribute path with the value we generated before. And we find an action, that has a name attribute with the value of view_file. Finally, we insert an access relation between the file and the action.

After both requests are done, we commit the write transaction to persist new information.

Note that we create the file first. If we try to insert an access to a nonexistent file our query will run but will not insert any new data (relation) because its match clause will find no matches in a database.

Provide Feedback