The DiracX client is a comprehensive toolset designed to interact with various services. It consists of three main components:
┌────────┐
│ client │
└────┬───┘
┌──▼──┐
│ api │
└──┬──┘
┌──▼──┐
│ cli │
└─────┘
- diracx-client: A client library generated from OpenAPI specifications.
- diracx-api: A Python API to interact with services using the diracx-client.
- diracx-cli: A command-line interface for direct interaction with the services.
The diracx-client
consists of three parts:
- an auto-generated client library that facilitates communication with services defined by OpenAPI specifications. (the
generated
folder) - customization, in the
patches
folder, which mirror the structure of the generated client. - the base modules (
aio
,extensions
,models
) just exporting what we want to be exporting
diracx-client
also defines a DiracClient
class which exposes all these low level calls, and handles the authentication/authorisation aspects, as well as the interactions with extensions.
The client is generated using AutoRest, a tool that reads OpenAPI configurations provided by FastAPI routers.
- Breaking Changes: Each time there is a breaking change in a router, the client needs to be regenerated.
The CI/CD pipeline handles client regeneration upon each push to the main
branch. This process helps detect breaking changes in the developer's code, causing the CI/CD to fail if such changes are present.
If a breaking change is acknowledged and approved, one of the repo admin will regenerate the client on behalf of the developer. Developers can still manually regenerate the client but it requires a few additional tools. The best up-to-date documentation lies in the client-generation
CI job.
The generated client consists of several key components:
- models: Represent the data structures.
- operations: Contain the methods to interact with the API endpoints.
- aio: Asynchronous client.
Further details can be found in the Python Autorest documentation.
Modifications to the generated client should be made in the patches
files to ensure maintainability, and possibly imported in the _patch.py
files if needed. Detailed guidance can be found in Python Autorest documentation.
Note: any modification in the synchronous client should also be performed in the asynchronous client (aio), and vice-versa.
Operations are accessible via the DiracClient
, which manages token refreshment:
from diracx.client.aio import DiracClient
async with DiracClient() as client:
jobs = await client.jobs.submit_bulk_jobs([x.read() for x in jdl])
Clients need to be configured to interact with services. This is performed through DiracxPreferences, which is a BaseSettings Pydantic model that load configuration from the environment.
Required environment variables to interact with the services:
DIRACX_URL
: the URL pointing to diracx servicesDIRACX_CA_PATH
: CA path used by the diracx services
Optional environment variables:
DIRACX_OUTPUT_FORMAT
: output format (e.g.JSON
). Default value depends whether the output stream is associated to a terminal.DIRACX_LOG_LEVEL
: logging level (e.g.ERROR
). Defaults toINFO
.DIRACX_CREDENTIALS_PATH
: path where access and refresh tokens are stored. Defaults to~/.cache/diracx/credentials.json
.
Developers can get access to the preferences through the following method:
from diracx.core.preferences import get_diracx_preferences
...
credentials_path = get_diracx_preferences().credentials_path
Note: preferences are cached.
The diracx-api
provides a Python API for interacting with services, leveraging the diracx-client
.
API methods are located in diracx-api/src/diracx/api/
. To create an API method:
- Import
DiracClient
. - Decorate the method with
@with_client
to handle client configuration. - Pass the
client
as a keyword argument.
from diracx.client.aio import DiracClient
from .utils import with_client
@with_client
async def create_sandbox(paths: list[Path], *, client: DiracClient) -> str:
...
In this example, paths
are the parameters of the API. The @with_client
decorator allows the method to be called without manually managing the client:
# Managed by @with_client
# Useful for basic work requiring a single call to the service
result = await create_sandbox(paths)
# For optimised performance with multiple service interactions
async with DiracClient() as client:
result = await create_sandbox(paths, client)
The diracx-cli
is a command-line interface built on diracx-client
and diracx-api
for direct interaction with services. It uses Typer for creating CLI commands and Rich for enhanced content display.
CLI commands are located in diracx-cli/src/diracx/cli/
. To create a CLI command:
- Import
DiracClient
and/or the diracx API. - Import
utils.AsyncTyper
. - Use the
@app.async_command
decorator to define commands.
For adding a new command, it needs to be added to one of the following entrypoint:
[project.entry-points."diracx.cli"]
jobs = "diracx.cli.jobs:app"
config = "diracx.cli.config:app"
[project.entry-points."diracx.cli.hidden"]
internal = "diracx.cli.internal:app"
from .utils import AsyncTyper
from diracx.client.aio import DiracClient
app = AsyncTyper()
@app.async_command()
async def submit(jdl: list[FileText]):
async with DiracClient() as client:
...
For more details on Typer and Rich options, refer to their Typer documentation and Rich documentation.
-
Commands without subcommands (e.g.,
dirac login
) should be implemented directly insrc/diracx/__init__.py
and decorated withapp.async_command()
. -
Commands with subcommands (e.g.,
dirac jobs submit
) should have their own modules insrc/diracx/<command>
and useAsyncTyper
.- To associate the command with
dirac
, import the module insrc/diracx/__init__.py
:
from . import <command> ... app.add_typer(<command name>.app, name="<command name>")
- To associate the command with
Users can then call the CLI:
$ dirac <command>
$ dirac <command> <subcommand> [--options]