Hello World! BUILD Module

This notebook shows how to interact with the BUILD API. First it shows the accessing the API through the TeselaGen's Python API Client, and then it shows the same but with a general purpose requests library.

Requirements:

  • Python 3.9+
  • TeselaGen's Python Client 0.4.4+

We start by making some imports

In [1]:
from pathlib import Path
import platform
import json
print(f"Python version: {platform.python_version()}")

from IPython.core.display import display
from IPython.core.display import HTML

import teselagen
print(f"TeselaGen's Python Client version: {teselagen.__version__}")
from teselagen.api import TeselaGenClient
from teselagen.utils.plot_tools import plot_plasmid_features
from teselagen.utils.plot_tools import RenderJSON
Python version: 3.9.9
TeselaGen's Python Client version: 0.4.4
In [2]:
# Define the host url address to be used across this notebook
HOST_URL = "https://demo-single.teselagen.com"

1. Python API Client (Alternative 1)

Here it is described how to connect to API by using Python API Client.

Make login into the platform. You should get "Connection Accepted" printed below.

In [3]:
# Connect to your teselagen instance by passing it as the 'host_url' argument of TeselaGenClient(host_url=host_url)
client = TeselaGenClient(host_url=HOST_URL)
# client = TeselaGenClient()
client.login()
client.select_laboratory(lab_name="TeselaGen")
Connection Accepted at https://demo-single.teselagen.com
Selected Lab: TeselaGen

Exploring Samples

The get_samples method can be used for exploring Samples. The gqlFilterparameter can be used to filter the query by different criteria, as shown in the following examples:

In [4]:
gqlFilter: str = json.dumps({"name": "Pool8-Isolate63"})
samples = client.build.get_samples(gqlFilter=gqlFilter)
print(json.dumps(samples, indent=4))
[
    {
        "id": "6ab82ad3-6d0a-41e7-9d3f-ae110f663164",
        "name": "Pool8-Isolate63",
        "status": null,
        "sampleTypeCode": "ISOLATED_SAMPLE",
        "sampleType": {
            "code": "ISOLATED_SAMPLE",
            "name": "Isolated Sample",
            "__typename": "sampleType"
        },
        "sampleFormulations": [],
        "updatedAt": "2022-04-14T20:22:43.051Z",
        "createdAt": "2022-04-14T19:54:45.233Z",
        "taggedItems": [],
        "material": {
            "id": "5f9bfe4f-b955-4606-a418-6f7ba459666c",
            "name": "BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)",
            "__typename": "material"
        },
        "batch": null,
        "lab": {
            "id": "09e5e181-9e3b-442b-9c89-da7f59d78bc5",
            "name": "TeselaGen",
            "__typename": "lab"
        },
        "user": {
            "id": "f8c0a79b-73ec-477d-afa7-7ccc4ae49978",
            "username": "Jota Hurtado",
            "__typename": "user"
        },
        "__typename": "sample"
    },
    {
        "id": "f4592049-4e98-4444-b8d9-78505f8bdcad",
        "name": "Pool8-Isolate63",
        "status": null,
        "sampleTypeCode": "ISOLATED_SAMPLE",
        "sampleType": {
            "code": "ISOLATED_SAMPLE",
            "name": "Isolated Sample",
            "__typename": "sampleType"
        },
        "sampleFormulations": [],
        "updatedAt": "2022-04-13T22:08:16.289Z",
        "createdAt": "2022-04-08T19:35:00.211Z",
        "taggedItems": [],
        "material": {
            "id": "5f9bfe4f-b955-4606-a418-6f7ba459666c",
            "name": "BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)",
            "__typename": "material"
        },
        "batch": null,
        "lab": {
            "id": "09e5e181-9e3b-442b-9c89-da7f59d78bc5",
            "name": "TeselaGen",
            "__typename": "lab"
        },
        "user": {
            "id": "5a540ee9-32a5-4d66-8066-4dd2d1f0476f",
            "username": "Eduardo Abeliuk",
            "__typename": "user"
        },
        "__typename": "sample"
    },
    {
        "id": "77f115a0-b3e0-4dd4-8bc9-88caae1b2bee",
        "name": "Pool8-Isolate63",
        "status": null,
        "sampleTypeCode": "ISOLATED_SAMPLE",
        "sampleType": {
            "code": "ISOLATED_SAMPLE",
            "name": "Isolated Sample",
            "__typename": "sampleType"
        },
        "sampleFormulations": [],
        "updatedAt": "2022-04-08T17:05:17.576Z",
        "createdAt": "2022-04-08T17:02:08.581Z",
        "taggedItems": [],
        "material": {
            "id": "5f9bfe4f-b955-4606-a418-6f7ba459666c",
            "name": "BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)",
            "__typename": "material"
        },
        "batch": null,
        "lab": {
            "id": "09e5e181-9e3b-442b-9c89-da7f59d78bc5",
            "name": "TeselaGen",
            "__typename": "lab"
        },
        "user": {
            "id": "5a540ee9-32a5-4d66-8066-4dd2d1f0476f",
            "username": "Eduardo Abeliuk",
            "__typename": "user"
        },
        "__typename": "sample"
    }
]

Even "deep" references can be used as filters

In [5]:
gqlFilter: str = json.dumps({"taggedItems.tag.name":  ["TagTest"]})# Also more than one simultaneous value can be used with a list, ex: ["TagTest1", "TagTest2"]
samples = client.build.get_samples(gqlFilter=gqlFilter)
print(json.dumps(samples, indent=4))
[
    {
        "id": "775361d6-b07d-4b96-9563-cf0cd259adb0",
        "name": "Construct_0001",
        "status": null,
        "sampleTypeCode": "REGISTERED_SAMPLE",
        "sampleType": {
            "code": "REGISTERED_SAMPLE",
            "name": "Registered Sample",
            "__typename": "sampleType"
        },
        "sampleFormulations": [],
        "updatedAt": "2021-12-16T16:15:49.165Z",
        "createdAt": "2021-12-09T15:59:20.673Z",
        "taggedItems": [
            {
                "id": "4e269b4a-6e29-47ed-858e-51b6beda1b88",
                "tag": {
                    "id": "b1bf7892-6fb5-4dd2-b833-e7b1e761d76c",
                    "name": "TagTest",
                    "color": "#d81515",
                    "__typename": "tag"
                },
                "tagOption": null,
                "__typename": "taggedItem"
            },
            {
                "id": "0e80124b-d52c-4a8b-ad3d-2862a4858798",
                "tag": {
                    "id": "ac76d044-82db-49d9-9eb5-148d15717a13",
                    "name": "TagBac\u00e1n",
                    "color": "#48ad31",
                    "__typename": "tag"
                },
                "tagOption": null,
                "__typename": "taggedItem"
            }
        ],
        "material": {
            "id": "30ada90c-300e-4463-8634-f1fff07cbb15",
            "name": "Construct_0001",
            "__typename": "material"
        },
        "batch": null,
        "lab": {
            "id": "09e5e181-9e3b-442b-9c89-da7f59d78bc5",
            "name": "TeselaGen",
            "__typename": "lab"
        },
        "user": {
            "id": "8de79bf9-da34-4204-9fdb-6e1d426f3445",
            "username": "Adam Thomas",
            "__typename": "user"
        },
        "__typename": "sample"
    }
]

Retrieveing a specific plate

The endpoint for specific plates returns more information than the previous one. It also can be accessed via Python's Client and the id of the plate should be provided.

In this example we are use the id of one of the samples listed above

In [6]:
sample_id = samples[0]['id']
print(sample_id)
775361d6-b07d-4b96-9563-cf0cd259adb0

And now we get info about this particular register

In [7]:
sample_data = client.build.get_sample(sample_id=sample_id)
#print(json.dumps(sample_data, indent=4))
RenderJSON(sample_data)

This register contains a lot of detail. In this example it also stores information about DNA material. We can, for example, plot the related features:

In [8]:
sequence = sample_data['material']['polynucleotideMaterialSequence']
In [9]:
# Add "forward" field (this won't be requeried from teselagen >= 0.4.5)
features = map( lambda x: {**x, **{'forward': x['strand']==1}}, sequence['sequenceFeatures'])
# Plot features
_ = plot_plasmid_features(plasmid_length=sequence['size'],
    features=[feat for feat in features if feat['end'] - feat['start'] > 100])

2. Generic Python requests (alternative 2)

If you prefer an alternative way to accessing the API, on this section we provide an example of BUILD API access through a general purpose http communication library (requests)

In [10]:
import requests

Login

Define connection variables

In [11]:
USERNAME = "****@teselagen.com" # Replace this with your username
PASSWORD = "*******" # Replace this with your password/api-key

Define a persistent session object that will be used for storing headers

In [13]:
session: requests.Session = requests.Session()
session.headers.update({'Content-Type': 'application/json', 'Accept': 'application/json'})

Login request. The next cell will just generate a token to be included in the headers

In [14]:
response: requests.Response = session.put(
    url=f'{HOST_URL}/build/cli-api/public/auth',
    json={
        'username': USERNAME,
        'password': PASSWORD,
        'expiresIn': '1d',
    },
)
response.raise_for_status() # Raise an error if a problem is found

# update session headers - TOKEN
session.headers.update(
    {
        'x-tg-cli-token': response.json()['token']  # TOKEN
     },
)
del response

Also, a lab should be selected

In [15]:
# First we get the labs
response = session.get(
    url=f'{HOST_URL}/test/cli-api/laboratories',
)
response.raise_for_status()
labs = {lab['name']:lab['id'] for lab in response.json()} 
print(f"Available labs: {labs}")

# Now we select one
session.headers.update({'tg-active-lab-id': labs['TeselaGen']})
Available labs: {'Regeneron': '939d4b20-d956-4a51-b8e5-818b632ae8c9', 'TeselaGen': '09e5e181-9e3b-442b-9c89-da7f59d78bc5'}

Let's take a look into the headers to be used:

In [16]:
print(session.headers)
{'User-Agent': 'python-requests/2.26.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'Connection': 'keep-alive', 'Content-Type': 'application/json', 'x-tg-cli-token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoidjEyIiwiZ3VpZCI6ImRlNWZhMDM5LTZkOWQtNGE5ZS05OTAxLWZlODU1ODcxOGM4YyIsInNlc3Npb25JZCI6IjdhMTFmNjc2LTZiMDYtNGI5Mi1hNjA5LWZkMTQ1ZTU3MjQxMiIsImlkIjoiOTBmNWMwNzAtMDE1Ny00MTllLTk0OWMtOWZhMWEwYmM2ZDNjIiwidXNlcm5hbWUiOiJhZG1pbkB0ZXNlbGFnZW4uY29tIiwicm9sZXMiOnsiQURNSU4iOnRydWUsIkxBQl9DUkVBVE9SIjp0cnVlfSwicm9sZUNvZGVzIjpbIkFETUlOIiwiTEFCX0NSRUFUT1IiLCJNRU1CRVIiXSwiaWF0IjoxNjUwNTgyNjU3LCJleHAiOjE2NTA2NjkwNTd9.uw8joh4CAfHx7QrYtP9Ap4sZySdA3v2MOAjBP9XI8e0', 'tg-active-lab-id': '09e5e181-9e3b-442b-9c89-da7f59d78bc5'}

We can now use those headers for calling any API endpoint, as in the following examples

Get Samples

In [17]:
# Set a filter parameter:
gqlFilter: str = json.dumps({"name": "Pool8-Isolate63"})

response = session.get(
    url=f'{HOST_URL}/build/cli-api/samples?gqlFilter={gqlFilter}',
)
response.raise_for_status()
print(response.json())  # [{'id': str, 'name': str}, ... ]
[{'id': '6ab82ad3-6d0a-41e7-9d3f-ae110f663164', 'name': 'Pool8-Isolate63', 'status': None, 'sampleTypeCode': 'ISOLATED_SAMPLE', 'sampleType': {'code': 'ISOLATED_SAMPLE', 'name': 'Isolated Sample', '__typename': 'sampleType'}, 'sampleFormulations': [], 'updatedAt': '2022-04-14T20:22:43.051Z', 'createdAt': '2022-04-14T19:54:45.233Z', 'taggedItems': [], 'material': {'id': '5f9bfe4f-b955-4606-a418-6f7ba459666c', 'name': 'BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)', '__typename': 'material'}, 'batch': None, 'lab': {'id': '09e5e181-9e3b-442b-9c89-da7f59d78bc5', 'name': 'TeselaGen', '__typename': 'lab'}, 'user': {'id': 'f8c0a79b-73ec-477d-afa7-7ccc4ae49978', 'username': 'Jota Hurtado', '__typename': 'user'}, '__typename': 'sample'}, {'id': 'f4592049-4e98-4444-b8d9-78505f8bdcad', 'name': 'Pool8-Isolate63', 'status': None, 'sampleTypeCode': 'ISOLATED_SAMPLE', 'sampleType': {'code': 'ISOLATED_SAMPLE', 'name': 'Isolated Sample', '__typename': 'sampleType'}, 'sampleFormulations': [], 'updatedAt': '2022-04-13T22:08:16.289Z', 'createdAt': '2022-04-08T19:35:00.211Z', 'taggedItems': [], 'material': {'id': '5f9bfe4f-b955-4606-a418-6f7ba459666c', 'name': 'BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)', '__typename': 'material'}, 'batch': None, 'lab': {'id': '09e5e181-9e3b-442b-9c89-da7f59d78bc5', 'name': 'TeselaGen', '__typename': 'lab'}, 'user': {'id': '5a540ee9-32a5-4d66-8066-4dd2d1f0476f', 'username': 'Eduardo Abeliuk', '__typename': 'user'}, '__typename': 'sample'}, {'id': '77f115a0-b3e0-4dd4-8bc9-88caae1b2bee', 'name': 'Pool8-Isolate63', 'status': None, 'sampleTypeCode': 'ISOLATED_SAMPLE', 'sampleType': {'code': 'ISOLATED_SAMPLE', 'name': 'Isolated Sample', '__typename': 'sampleType'}, 'sampleFormulations': [], 'updatedAt': '2022-04-08T17:05:17.576Z', 'createdAt': '2022-04-08T17:02:08.581Z', 'taggedItems': [], 'material': {'id': '5f9bfe4f-b955-4606-a418-6f7ba459666c', 'name': 'BL21 (DP_EXP1-3715811d-3c4b-414b-a41c-40072193dc27)', '__typename': 'material'}, 'batch': None, 'lab': {'id': '09e5e181-9e3b-442b-9c89-da7f59d78bc5', 'name': 'TeselaGen', '__typename': 'lab'}, 'user': {'id': '5a540ee9-32a5-4d66-8066-4dd2d1f0476f', 'username': 'Eduardo Abeliuk', '__typename': 'user'}, '__typename': 'sample'}]
In [ ]: