Skip to content

afnio.tellurio

afnio.tellurio.configure_logging(verbosity='info')

Configure logging for the afnio library.

Sets up logging format and levels for CLI, scripts, and Jupyter notebooks. In a notebook, adds a custom handler for INFO-level logs to display user-facing messages with color and formatting.

Parameters:

Name Type Description Default
verbosity str

Logging level as a string ("info", "debug", etc.).

'info'
Source code in afnio/logging_config.py
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def configure_logging(verbosity: str = "info"):
    """
    Configure logging for the `afnio` library.

    Sets up logging format and levels for CLI, scripts, and Jupyter notebooks.
    In a notebook, adds a custom handler for INFO-level logs to display
    user-facing messages with color and formatting.

    Args:
        verbosity: Logging level as a string (`"info"`, `"debug"`, etc.).
    """
    if not isinstance(verbosity, str):
        raise TypeError("verbosity must be a string like 'info', 'debug', etc.")
    level = getattr(logging, verbosity.upper(), logging.INFO)
    if level <= logging.DEBUG:
        fmt = "%(asctime)s - %(name)-35s - %(levelname)-9s - %(message)s"
    else:
        fmt = "%(levelname)-9s: %(message)s"

    # Set root logger to WARNING (default for all libraries)
    logging.basicConfig(
        level=logging.WARNING,
        format=fmt,
        force=True,
    )

    # Set only afnio logs to the desired level
    logging.getLogger("afnio").setLevel(level)

    # Add notebook handler for INFO logs if in notebook
    if _in_notebook() and level == logging.INFO:
        handler = _NotebookInfoHandler()
        handler.setLevel(logging.INFO)
        handler.addFilter(lambda record: record.levelno == logging.INFO)
        logging.getLogger().addHandler(handler)

        class NoInfoFilter(logging.Filter):
            """
            Logging filter that suppresses INFO-level log records.
            Used to prevent duplicate INFO messages in notebook environments.
            """

            def filter(self, record):
                """
                Filter method to suppress INFO-level log records.

                Args:
                    record (logging.LogRecord): The log record to filter.

                Returns:
                    bool: True if the record should be logged, False otherwise.
                """
                return record.levelno != logging.INFO

        for h in logging.getLogger().handlers:
            if not isinstance(h, _NotebookInfoHandler):
                h.addFilter(NoInfoFilter())

afnio.tellurio.suppress_variable_notifications()

Context manager to temporarily suppress variable change notifications.

When this context manager is active, any attribute changes to afnio.Variable instances will not trigger _on_variable_change notifications. This is useful for internal/client-initiated updates where you do not want to broadcast changes back to the server.

Source code in afnio/tellurio/_variable_registry.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
@contextmanager
def suppress_variable_notifications():
    """
    Context manager to temporarily suppress variable change notifications.

    When this context manager is active, any attribute changes to afnio.Variable
    instances will not trigger `_on_variable_change` notifications. This is useful
    for internal/client-initiated updates where you do not want to broadcast changes
    back to the server.
    """
    global _SUPPRESS_NOTIFICATIONS
    token = _SUPPRESS_NOTIFICATIONS
    _SUPPRESS_NOTIFICATIONS = True
    try:
        yield
    finally:
        _SUPPRESS_NOTIFICATIONS = token

afnio.tellurio.init(namespace_slug, project_display_name, name=None, description=None, status=RunStatus.RUNNING, client=None)

Initializes a new Tellurio Run.

Parameters:

Name Type Description Default
namespace_slug str

The namespace slug where the project resides. It can be either an organization slug or a user slug.

required
project_display_name str

The display name of the project. This will be used to retrive or create the project through its slugified version.

required
name str | None

The name of the run. If not provided, a random name is generated (e.g., "brave_pasta_123"). If the name is provided but already exists, an incremental number is appended to the name (e.g., "test_run_1", "test_run_2").

None
description str | None

A description of the run.

None
status RunStatus | None

The status of the run (default: "RUNNING").

RUNNING
client TellurioClient | None

An instance of TellurioClient. If not provided, the default client will be used.

None

Returns:

Type Description
Run

A Run object representing the created run.

Raises:

Type Description
Exception

If there is an error during the API requests to create the run or retrieve/create the Project.

Source code in afnio/tellurio/run.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
def init(
    namespace_slug: str,
    project_display_name: str,
    name: Optional[str] = None,
    description: Optional[str] = None,
    status: Optional[RunStatus] = RunStatus.RUNNING,
    client: Optional[TellurioClient] = None,
) -> Run:
    """Initializes a new Tellurio [`Run`][afnio.tellurio.run.Run].

    Args:
        namespace_slug: The namespace slug where the project resides. It can be
          either an organization slug or a user slug.
        project_display_name: The display name of the project. This will be used
            to retrive or create the project through its slugified version.
        name: The name of the run. If not provided, a random name is
            generated (e.g., "brave_pasta_123"). If the name is provided but already
            exists, an incremental number is appended to the name (e.g., "test_run_1",
            "test_run_2").
        description: A description of the run.
        status: The status of the run (default: "RUNNING").
        client: An instance of TellurioClient. If not provided,
            the default client will be used.

    Returns:
        A Run object representing the created run.

    Raises:
        Exception: If there is an error during the API requests to create the run or
            retrieve/create the [`Project`][afnio.tellurio.project.Project].
    """
    client = client or get_default_clients()[0]

    # Generate the project's slug from its name
    project_slug = slugify(project_display_name)

    # Ensure the project exists
    try:
        project_obj = get_project(
            namespace_slug=namespace_slug,
            project_slug=project_slug,
            client=client,
        )
        if project_obj is not None:
            logger.info(
                f"Project with slug {project_slug!r} already exists "
                f"in namespace {namespace_slug!r}."
            )
        else:
            logger.info(
                f"Project with slug {project_slug!r} does not exist "
                f"in namespace {namespace_slug!r}. "
                f"Creating it now with RESTRICTED visibility."
            )
            project_obj = create_project(
                namespace_slug=namespace_slug,
                display_name=project_display_name,
                visibility="RESTRICTED",
                client=client,
            )
    except Exception as e:
        logger.error(f"An error occurred while retrieving or creating the project: {e}")
        raise

    # Dynamically construct the payload to exclude None values
    payload = {}
    if name is not None:
        payload["name"] = name
    if description is not None:
        payload["description"] = description
    if status is not None:
        payload["status"] = status.value

    # Create the run
    endpoint = f"/api/v0/{namespace_slug}/projects/{project_slug}/runs/"

    try:
        response = client.post(endpoint, json=payload)

        if response.status_code == 201:
            data = response.json()
            base_url = os.getenv(
                "TELLURIO_BACKEND_HTTP_BASE_URL", "https://platform.tellurio.ai"
            )
            run_slug = slugify(data["name"])
            logger.info(
                f"Run {data['name']!r} created successfully at: "
                f"{base_url}/{namespace_slug}/projects/{project_slug}/runs/{run_slug}/"
            )

            # Parse date fields
            date_created = datetime.fromisoformat(
                data["date_created"].replace("Z", "+00:00")
            )
            date_updated = datetime.fromisoformat(
                data["date_updated"].replace("Z", "+00:00")
            )

            # Parse project and user fields
            org_obj = RunOrg(
                slug=namespace_slug,
            )
            project_obj = RunProject(
                uuid=data["project"]["uuid"],
                display_name=data["project"]["display_name"],
                slug=data["project"]["slug"],
            )
            user_obj = RunUser(
                uuid=data["user"]["uuid"],
                username=data["user"]["username"],
                slug=data["user"]["slug"],
            )

            # Create and return the Run object
            run = Run(
                uuid=data["uuid"],
                name=data["name"],
                description=data["description"],
                status=RunStatus(data["status"]),
                date_created=date_created,
                date_updated=date_updated,
                organization=org_obj,
                project=project_obj,
                user=user_obj,
            )
            set_active_run(run)
            _register_safeguard(run)
            return run
        else:
            logger.error(
                f"Failed to create run: {response.status_code} - {response.text}"
            )
            response.raise_for_status()
    except Exception as e:
        logger.error(f"An error occurred while creating the run: {e}")
        raise

afnio.tellurio.login(api_key=None, relogin=False)

Logs in the user and establishes both HTTP and WebSocket connections to the Tellurio Studio backend.

This function authenticates the user using an API key, which can be provided directly, via the TELLURIO_API_KEY environment variable, or retrieved from the local keyring. On successful authentication, it establishes a WebSocket connection for real-time communication and stores the API key securely for future use.

Parameters:

Name Type Description Default
api_key str

The user's API key. If not provided, the function will attempt to use the TELLURIO_API_KEY environment variable. If that is not set, it will look for a stored API key in the local keyring.

None
relogin

If True, forces a re-login and requires the user to provide a new API key (either directly or via the environment variable).

False

Returns:

Type Description
dict

A dictionary containing the user's email, username, and session ID for the WebSocket connection. Example:

{ "email": "user@example.com", "username": "user123", "session_id": "abc123xyz" }

Raises:

Type Description
ValueError

If the API key is not provided during first login or re-login.

InvalidAPIKeyError

If the backend rejects the API key.

RuntimeError

If the WebSocket connection fails.

Exception

For any other unexpected errors during login.

Notes
  • On first login, the API key is stored securely in the local keyring for future use.
  • If relogin is True, a new API key must be provided (directly or via environment variable).
  • This function is synchronous and can be called from both scripts and interactive environments.
Source code in afnio/tellurio/__init__.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def login(api_key: str = None, relogin=False) -> dict:
    """Logs in the user and establishes both HTTP and WebSocket connections to the
    [Tellurio Studio](https://platform.tellurio.ai/) backend.

    This function authenticates the user using an API key, which can be provided
    directly, via the `TELLURIO_API_KEY` environment variable, or retrieved from the
    local keyring. On successful authentication, it establishes a WebSocket connection
    for real-time communication and stores the API key securely for future use.

    Args:
        api_key: The user's API key. If not provided, the function will
            attempt to use the `TELLURIO_API_KEY` environment variable. If that is not
            set, it will look for a stored API key in the local keyring.
        relogin: If `True`, forces a re-login and requires the user to provide a
            new API key (either directly or via the environment variable).

    Returns:
        A dictionary containing the user's email, username, and session ID for the \
        WebSocket connection. Example:

            `{
                "email": "user@example.com",
                "username": "user123",
                "session_id": "abc123xyz"
            }`

    Raises:
        ValueError: If the API key is not provided during first login or re-login.
        afnio.tellurio.client.InvalidAPIKeyError: If the backend rejects the API key.
        RuntimeError: If the WebSocket connection fails.
        Exception: For any other unexpected errors during login.

    Notes:
        - On first login, the API key is stored securely in the local keyring for
            future use.
        - If relogin is True, a new API key must be provided (directly or via
            environment variable).
        - This function is synchronous and can be called from both scripts and
            interactive environments.
    """
    # Get the default HTTP and WebSocket clients
    # Login in a separate step below to pass parameters
    client, ws_client = get_default_clients(login=False)

    return run_in_background_loop(
        _login(client=client, ws_client=ws_client, api_key=api_key, relogin=relogin)
    )  # Handle both sync and async contexts

afnio.tellurio.log(name, value, step=None, client=None)

Log a metric to the active Run.

Parameters:

Name Type Description Default
name str

Name of the metric.

required
value Any

Value of the metric. Can be any type that is JSON serializable.

required
step int | None

Step number. If not provided, the backend will auto-compute it.

None
client TellurioClient | None

The client to use for the request.

None
Source code in afnio/tellurio/__init__.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def log(
    name: str,
    value: Any,
    step: Optional[int] = None,
    client: Optional[TellurioClient] = None,
):
    """Log a metric to the active [`Run`][afnio.tellurio.run.Run].

    Args:
        name: Name of the metric.
        value: Value of the metric. Can be any type that is JSON serializable.
        step: Step number. If not provided, the backend will auto-compute it.
        client: The client to use for the request.
    """
    run = get_active_run()
    run.log(name=name, value=value, step=step, client=client)