Skip to content

cms-DQM/dials-py

Repository files navigation

dials-py

The Python api client interface to DIALS service.

Installation

To install dials-py, simply

$ pip install cmsdials

It is also possible to specify the following extras, to enable optional features:

pandas, tqdm

Usage

Before interfacing with any route you need to generate valid credentials, it is possible to authenticate trough the device authorization flow or using the client secret key of any application registered in DIALS. Note that, the device flow is an interactively authentication procedure that is possible to distinguish users in DIALS backend and the client secret flow is not interactive and is not possible to distinguish users so it should only be used for automation scripts.

Generating credentials with client secret

from cmsdials.auth.secret_key import Credentials

creds = Credentials(token=".....")

Generating credentials with device

Loading from AuthClient

from cmsdials.auth.client import AuthClient
from cmsdials.auth.bearer import Credentials

auth = AuthClient()
token = auth.device_auth_flow()
creds = Credentials.from_authclient_token(token)

Loading from cached credentials file

Credentials are always cached once you authenticate at least one time, calling this method without having a cached credential file will automatically trigger the AuthClient device flow.

from cmsdials.auth.bearer import Credentials

creds = Credentials.from_creds_file()

Basic Example

from cmsdials.auth.bearer import Credentials
from cmsdials import Dials
from cmsdials.filters import LumisectionHistogram1DFilters

creds = Credentials.from_creds_file()
dials = Dials(creds)

# Getting h1d data
data = dials.h1d.list_all(LumisectionHistogram1DFilters(me="PixelPhase1/Tracks/PXBarrel/charge_PXLayer_2"), max_pages=5)

Workspace

Users are automatically routed to a workspace based on e-groups, but it is possible to overwrite this configuration and inspect data from others workspaces:

dials = Dials(creds, workspace="jetmet")

Available endpoints

This package interacts with DIALS api endpoints using underlying classes in Dials object.

Retrieving a specific object using get

dials.file_index.get(dataset_id=14677060, file_id=3393809397)
dials.h1d.get(dataset_id=14677060, run_number=367112, ls_number=10, me_id=1)
dials.h1d.get(dataset_id=14677060, run_number=367112, ls_number=10, me_id=96)
dials.lumi.get(dataset_id=14677060, run_number=367112, ls_number=10)
dials.run.get(dataset_id=14677060, run_number=367112)

Retrieving a list of objects per page using list

It is possible to get a list of entries from those endpoint using the list and list_all methods, the list method will fetch only one page and the list_all will fetch all available pages:

dials.file_index.list()
dials.h1d.list()
dials.h2d.list()
dials.lumi.list()
dials.run.list()

Retrieving all available pages of a list of objects using list_all

Note: Keep in mind that running list_all without any filter can take too much time, since you will be retrieving all rows in the database.

dials.file_index.list_all()
dials.h1d.list_all()
dials.h2d.list_all()
dials.lumi.list_all()
dials.run.list_all()

If you don't need all available pages but just a subset of then, it is possible to specify a max_pages integer parameter:

dials.run.list_all(..., max_pages=5)

Using filters

Keep in mind that calling those methods without any filter can take a lot of time, because the underlying query will try to load the entire database table through multiple requests, then it is recommended to apply filters according to DIALS live documentation using filter classes for each table:

from cmsdials.filters import (
    FileIndexFilters,
    LumisectionHistogram1DFilters,
    LumisectionHistogram2DFilters,
    LumisectionFilters,
    RunFilters
)

dials.file_index.list(FileIndexFilters(dataset__regex="2024B"))

dials.h1d.list(LumisectionHistogram1DFilters(me="PixelPhase1/Tracks/PXBarrel/charge_PXLayer_2"))

dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=5)

dials.lumi.list_all(LumisectionFilters(run_number=360392), max_pages=5)

dials.run.list_all(RunFilters(run_number__gte=360392, run_number__lte=365000), max_pages=5)

Dials MEs

It is possible to inspect the list of ingested MEs in DIALS listing the endpoint mes trough the method:

dials.mes.list()

Automatically convert paginated results to pandas DataFrame

You can enable this optional feature by installing the package with the pandas extra.

All Paginated metaclasses contain the method to_pandas that will automatically transform the results attribute of the metaclass into a pandas dataframe, for example:

data = dials.h1d.list_all(LumisectionHistogram1DFilters(me="PixelPhase1/Tracks/PXBarrel/charge_PXLayer_2"), max_pages=5)
data.to_pandas()

Indefinite progress bar when fetch multi-page result

You can enable this optional feature by installing the package with the tqdm extra.

Whenever you call a list_all method that fetches multiple pages a dynamic progress will be rendered to indicate duration and number of pages, for example:

>>> dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=5)
Progress: 100%|█████████████████████████████████████| 5/5 [00:02<00:00,  1.70it/s]

The total attribute of the bar is dynamically updated while fetching the pages.

Retrying

In case of an unstable connection, DNS failures or service overload it is possible to configure any get, list and list_all to retry the underlying requests using native urllib3 retry class, for example:

from urllib3.util import Retry

data = dials.h1d.get(dataset_id=14677060, run_number=367112, ls_number=10, me_id=1, retries=Retry(total=3, backoff_factor=0.1))
data = dials.h1d.list(retries=Retry(total=3, backoff_factor=0.1))
data = dials.h1d.list_all(LumisectionHistogram1DFilters(), max_pages=5, retries=Retry(total=5, backoff_factor=0.1))

Resuming

When listing and endpoint for a long time, you may loose connection or the server can potentially return an error. By specifying keep_failed and using resume_from you can resume from an older response object, for example:

from cmsdials.auth.client import AuthClient
from cmsdials.auth.bearer import Credentials
from cmsdials import Dials
from cmsdials.filters import LumisectionHistogram2DFilters

auth = AuthClient()
creds = Credentials.from_creds_file()
dials = Dials(creds, workspace="tracker")

data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True)  # Code may broke inside this routine
print(len(data.results))  # 100
print(data.exc_type)
print(data.exc_formatted)

# After failing, run this again
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), keep_failed=True, resume_from=data)  # Resume from failed object
print(len(data.results))  # 200

You may find it useful to resume from a partial response, i.e,

data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True)  # Code may not break, however will fetch only 100 elements
print(len(data.results))  # 100

# After fetching first 100, fetch next 100
data = dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="PXBarrel", ls_number=78, entries__gte=100), max_pages=10, keep_failed=True, resume_from=data)  # Resume from partial object
print(len(data.results))  # 200

Usage with local DIALS

All classes that interface the DIALS service inherits the class BaseAPIClient which propagate the base_url, route and version attributes with production values. In order to use dials-py with a local version of DIALS it is possible to overwrite those attributes when instantiating the AuthClient and the Dials client, for example:

from cmsdials.auth.client import AuthClient
from cmsdials.auth.bearer import Credentials
from cmsdials import Dials
from cmsdials.filters import LumisectionHistogram2DFilters

DEV_URL = "http://localhost:8000/"
DEV_CACHE_DIR = ".cache-dev"

auth = AuthClient(base_url=DEV_URL)
creds = Credentials.from_creds_file(cache_dir=DEV_CACHE_DIR, client=auth)  # Make sure to specify the auth client with overwritten values, using another cache_dir is recommended
dials = Dials(creds, base_url=DEV_URL)

dials.h2d.list_all(LumisectionHistogram2DFilters(me__regex="EEOT digi occupancy EE +", entries__gte=100, run_number__gte=360392, run_number__lte=365000), max_pages=5)

Running tests

The repository has some tests written to make sure DIALS responses are compatible with pydantic metaclasses, you can use pytest to run all tests but you need to specify a secret key to authenticate non-interactively against DIALS api:

SECRET_KEY=... pytest tests

The secret key is an api client enabled secret key and can be obtained from the applications portal, any api client secret key whitelisted in DIALS can be used. The interactive authentication flow should be tested manually, an example for this can be found in this line.

If testing against a local version of DIALS you need to specify the BASE_URL:

SECRET_KEY=... BASE_URl=http://localhost:8000 pytest tests