Skip to main content

TangoGQL

Introduction

This is a GraphQL schema and implementation for accessing a Tango control system. It's motivating use case is as backend for the Taranta project. But it can be used as a generic GraphQL API for Tango.

GraphQL is a standard, there are many resources to learn about it.

This project is based on Ariadne (https://ariadnegraphql.org).

Note: this repo replaces the older implementation at https://gitlab.com/tango-controls/web/tangogql. Versions above 2.0 are based on this repo.

TODO

  • Some parts of the schema are not implemented (mainly, the "domain/family/member")
  • Think about better ways of structuring the code.

Usage

Install (preferrably in a virtualenv) from PyPI with:

pip install tangogql

To run (assuming you have TANGO_HOST setup to point to an accessible control system):

uvicorn tangogql.dev:app

Then visit http://localhost:8000 for an interactive GraphIQL "playground". This is probably the best way to get acquainted with the schema.

For a "Taranta compatible" setup:

uvicorn tangogql.main:app --port <port>

To use with taranta, start taranta dev server locally, access /testdb/ and look for the error message [HPM] Error occurred while trying to proxy request /testdb/db from localhost:3001 to http://localhost:22484 in the terminal. Here, port number 22484 is the one to use for tangogql above.

It is also possible to use TangoGQL as a route in another web application. See Ariadne's documentation.

Configuration

The server can be configured either using environment variables, or a config.json file. See settings.py for the available settings. For a production setting, you should at least set the TANGOGQL_SECRET to something random, it's used for encrypting client auth data.

For local testing, you can set TANGOGQL_NO_AUTH=true to disable all the auth stuff.

OIDC authentication

Set TANGOGQL_OIDC_ISSUER to indigo or entra to enable OIDC authentication for protected resolvers. In this mode TangoGQL expects an Authorization: Bearer <access-token> header and verifies the token signature, expiry, issuer and configured audience. TANGOGQL_NO_AUTH=true continues to bypass both OIDC and the legacy cookie-JWT authentication.

/db browser explorer

When TANGOGQL_OIDC_ISSUER is enabled, opening /db in a browser requires either a valid OIDC bearer header or a signed browser-session cookie (jwt-auth or taranta_jwt). An unauthenticated browser receives User is not logged in with HTTP 401. No server-side session is created: the cookie is verified with TANGOGQL_SECRET, which must match the secret used to issue the existing IC@MS/Taranta JWT.

Without TANGOGQL_OIDC_ISSUER, /db keeps its original behavior.

The signed IC@MS/Taranta session preserves the legacy cookie-JWT access behaviour. Indigo group and Entra scope/role checks apply when TangoGQL receives an actual OIDC bearer token.

After the GraphiQL explorer has opened, use its HTTP Headers panel to attach a bearer token to each query or mutation. Add the following JSON and then run the query:

{
  "Authorization": "Bearer <access-token>"
}

For example, this authorizes query { devices { name } }. The browser address bar cannot send a custom Authorization header, so it cannot use a bearer token to load the initial /db page. A browser user must first have the IC@MS/Taranta login cookie; bearer-only callers should use an HTTP client such as curl or Postman. Do not put access tokens in the /db URL.

Examples:

# Microsoft Entra (both v1 and v2 access tokens are supported)
TANGOGQL_OIDC_ISSUER=entra
TANGOGQL_MSENTRA_TENANT_ID=<tenant-id>
TANGOGQL_MSENTRA_API_ID=<API application client ID>
# Optional explicit audience; otherwise API_ID (v2) or api://API_ID (v1) is used.
TANGOGQL_MSENTRA_API_AUDIENCE=<Application-ID-URI-or-audience>
# Optional override when OIDC discovery cannot be used.
TANGOGQL_MSENTRA_JWKS_URL=<JWKS URL>

# Indigo IAM
TANGOGQL_OIDC_ISSUER=indigo
TANGOGQL_IAMS_AUTHORITY=https://<iam-host>
TANGOGQL_IAMS_CLIENT_ID=<resource-server-client-id>
# Optional explicit audience. Enable validation only when Indigo issues aud.
TANGOGQL_IAMS_API_AUDIENCE=<audience>
TANGOGQL_IAMS_REQUIRE_AUDIENCE=true
# Required when protected read/write operations are enabled for Indigo IAM.
TANGOGQL_IAMS_READ_GROUP=app:tangogql_ariadne:readers
TANGOGQL_IAMS_READWRITE_GROUP=app:tangogql_ariadne:writers

For Indigo, audience validation is disabled by default because some deployed user tokens have no aud claim.

For Indigo IAM, TangoGQL authorizes protected operations from the token's groups claim, not its scope claim. Configure TANGOGQL_IAMS_READ_GROUP and TANGOGQL_IAMS_READWRITE_GROUP with the IAM group names that grant the corresponding permissions. For example, a token containing app:tangogql_ariadne:readers can use resolvers decorated with @check_auth("read"). These settings intentionally have no defaults.

Microsoft Entra continues to use its scp/roles values for the same read/write checks. TANGOGQL_REQUIRED_GROUPS remains an optional additional gate for all providers.

You can override logging config by pointing logging_config to a different file than the default logging.ini.

Testing

pip install -e .[tests]
pytest

Development tips

  • The repo contains a pre-commit configuration that is also enforced by the CI jobs, and it's recommended to also use it locally. The point is to keep committed code clean and consistently formatted. Commits may fail either because linting caught some issue, or because code reformatting resulted in changes.

      pip install pre-commit
      pre-commit install
    
  • Running uvicorn with --log-level=debug provides more feedback on what's happening.

  • It may also be useful to set the environment variable PYTHONASYNCIODEBUG=1 which enables some asyncio debugging tools. This can help track down problems with tasks/coroutines.

  • The repo contains a requirements.txt file to pin versions when building the docker image. That file was generated by running uv pip compile pyproject.toml -o requirements.txt. You could just use pip freeze, but uv pip compile gives a nicer output giving you an idea from where the dependencies come from. You can update a single dependency with --upgrade-package. I'd recommend to just delete and re-create the file from time to time to update all dependencies. See the uv pip compile documentation.

Navigating the source code

While the codebase isn't large, the structure may be a little hard to follow. This is because Ariadne works by starting with the tangogql.graphql schema file, and then pulling the code together based on that structure.

Firstly you may want to get familiar with the Ariadne docs and the GraphQL standard.

Now, if you want to know how e.g. query { device(name:"...") { alias } } is retrieved, check the definition in the schema file:

...
type Query {
    ...
    device(name: String!): Device
    ...
}
...

What we are looking for is the toplevel Query object type, and the field "device" (which has Device object type).

The code base is organized along the top level fields; "query", "subscription", and "mutation". So, look in the query/query.py file. Then find the "resolver" for the "device" field, i.e.

@query.field("device")
def resolve_device(_, info, name: str) -> DbDeviceInfo:
    return info.context["device_loader"].load(name)

This function gets run in order to get data for each device query. It uses a "dataloader" which is basically a way to get data in a more efficient way by batching calls for the whole query. You can look in loaders.py for more info, but the important thing here is that it returns a DbDeviceInfo object for our device. This is a simple database information class.

In this case, "alias" is directly available on this object, which means it will automatically get picked up by Ariadne and returned. Done!

Let's look for some slightly more complicated Device field such as connected; search the code for ObjectType("Device") to find where it's defined, and then look for the resolver function for the "connected" field, like above. You should find a function that gets our DbDeviceInfo object and does some work to check if the device responds, and returns a boolean depending on that. Done.

With this knowledge, you should be able to navigate the code by looking for the correct object types and resolvers, i.e. a resolver can return a new object, which gets resolved, and so on. The chain can go many steps deep but the pattern is the same.

Running Unit Tests in Docker for TangoGQL

To run unit tests in Docker for the TangoGQL project, follow these steps. These instructions assume you are using PowerShell:

Steps to Run Unit Tests in Docker

  1. Build the Docker Image
    Open PowerShell and build the Docker image from the project directory:

    docker build -t tangogql .
    
  2. Run the container

    docker run --rm -it tangogql bash
    
  3. Install test Dependencies Install the test dependencies for TangoGQL (TangoGQL itself is already installed)

    uv pip install ".[tests]"
    
  4. Run Unit Tests Finally, run the test suite:

    pytest
    

These steps will set up and run the unit tests within the Docker container.

Metadata

Release files for tangogql 2.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tangogql 2.3.0
File Size Uploaded
tangogql-2.3.0.tar.gz 97.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tangogql 2.3.0
File Interpreter ABI Platform
tangogql-2.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 162.6 kB

Release files / tangogql-2.3.0.tar.gz

Download URL tangogql-2.3.0.tar.gz
Size 97.2 kB
Tags Source
SHA-256 checksum
How to use checksums
058e2de83b97640c9eb5976e4deb3b3d4dd7ad26c66c89c0d9f56b5f13027ecc
BLAKE2b-256 checksum
How to use checksums
dac728d6a7f8b948216ebdc3d7006888e6259ddab19b63a95703c54fb1717216
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.12

Release files / tangogql-2.3.0-py3-none-any.whl

Download URL tangogql-2.3.0-py3-none-any.whl
Size 65.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a95a5a8cd892a04c94abd8d55dadba4ed1a4b56b1e15ee93a0f6aec1fc6df6a
BLAKE2b-256 checksum
How to use checksums
ee881e9c6a8a5fc443ab6d536917ccffb0749e135bc93cd538031ee5cf00240f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.12

Release history Release notifications | RSS feed

This release

2.3.0 This release

2 release files

2.2.7

2 release files

2.2.6

2 release files

2.2.5

2 release files

2.2.4

2 release files

2.2.3

2 release files

2.2.2

2 release files

2.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page