Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ If you you are creating a pull request yourself:

* Add a label skip:test:long_running, to skip running long running tests. This is important because some tests in this repository are marked as long_running and can take a significant amount of time to complete. By adding this label, you help ensure that the CI pipeline runs efficiently and avoids unnecessary delays.

## Maintenance mode

The Aignostics Python SDK is deprecated. Support ends on April 30, 2027.
Until that date, the SDK is in maintenance mode.

Use these rules for each change:

1. Accept anomaly fixes through PR-SOP-01.
2. Accept dependency updates that fix vulnerabilities.
3. Reject new features.
4. Keep the deprecation warning in each release.

For the notice text and the surfaces that show it, refer to [Deprecation notice](#deprecation-notice).

## Module Documentation Index

Every module has detailed CLAUDE.md documentation. For module-specific guidance, see:
Expand Down Expand Up @@ -486,6 +500,20 @@ def action_command(param: str):

## Important Notes

### Deprecation notice

The SDK is deprecated. `aignostics.constants.DEPRECATION_NOTICE` is the single
source of the notice text. In code, use the constant. Do not copy the text.
These surfaces show it:

* **Log** — in library mode, import of `aignostics` writes it as a `WARNING` log line
and emits a `FutureWarning`. Child processes do not repeat it. The CLI does not log it.
* **CLI** — the root help text and stderr before each command.
* **Launchpad** — a banner below the header of each page.
* **Docs site** — a Furo announcement banner.

`README.md` (and so the PyPI page) also shows the notice and a migration table.

### Module Loading

`nicegui`, `marimo`, and `ijson` are core dependencies, so modules using them
Expand Down
2 changes: 2 additions & 0 deletions CLI_REFERENCE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# CLI Reference

The Aignostics Python SDK is deprecated and gets only bug fixes and security fixes until April 30, 2027. After that date, the repository and the PyPI project are archived. Use the Aignostics Platform REST API (https://platform.aignostics.com/api/v1/docs) or the Console (https://platform.aignostics.com).

Command Line Interface (CLI) of Aignostics Python SDK providing access to Aignostics Platform.

**Usage**:
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Thank you for considering contributing to Aignostics Python SDK!

## Maintenance mode

The Aignostics Python SDK is deprecated. Support ends on April 30, 2027.
Until that date, the SDK is in maintenance mode.

Use these rules for each change:

1. Accept anomaly fixes through PR-SOP-01.
2. Accept dependency updates that fix vulnerabilities.
3. Reject new features.
4. Keep the deprecation warning in each release.

## Setup

[Create a fork](https://github.com/aignostics/python-sdk/fork) and clone your fork using ```git clone URL_OF_YOUR_CLONE```. Then change into the directory of your local Aignostics Python SDK repository with ```cd python-sdk```.
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@

# 🔬Aignostics Python SDK

## ⚠️ Deprecated

> The Aignostics Python SDK is deprecated and gets only bug fixes and security fixes until April 30, 2027. After that date, the repository and the PyPI project are archived. Use the Aignostics Platform REST API (https://platform.aignostics.com/api/v1/docs) or the Console (https://platform.aignostics.com).

### Migration

| You use | Use this instead |
| --- | --- |
| Python Library | Aignostics Platform REST API: [https://platform.aignostics.com/api/v1/docs](https://platform.aignostics.com/api/v1/docs) |
| CLI | Aignostics Platform REST API: [https://platform.aignostics.com/api/v1/docs](https://platform.aignostics.com/api/v1/docs) |
| Launchpad | Console: [https://platform.aignostics.com](https://platform.aignostics.com) |
| SDK upload command for the Console | Console: [https://platform.aignostics.com](https://platform.aignostics.com) |

[![License](https://img.shields.io/github/license/aignostics/python-sdk?logo=opensourceinitiative&logoColor=3DA639&labelColor=414042&color=A41831)](https://github.com/aignostics/python-sdk/blob/main/LICENSE)
[![Python Version](https://img.shields.io/pypi/pyversions/aignostics.svg?logo=python&color=204361&labelColor=1E2933)](https://pypi.org/project/aignostics/)
[![CI/CD](https://github.com/aignostics/python-sdk/actions/workflows/ci-cd.yml/badge.svg?branch=main)](https://github.com/aignostics/python-sdk/actions/workflows/ci-cd.yml)
Expand Down
13 changes: 13 additions & 0 deletions docs/partials/README_header.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# 🔬Aignostics Python SDK

## ⚠️ Deprecated

> The Aignostics Python SDK is deprecated and gets only bug fixes and security fixes until April 30, 2027. After that date, the repository and the PyPI project are archived. Use the Aignostics Platform REST API (https://platform.aignostics.com/api/v1/docs) or the Console (https://platform.aignostics.com).

### Migration

| You use | Use this instead |
| --- | --- |
| Python Library | Aignostics Platform REST API: [https://platform.aignostics.com/api/v1/docs](https://platform.aignostics.com/api/v1/docs) |
| CLI | Aignostics Platform REST API: [https://platform.aignostics.com/api/v1/docs](https://platform.aignostics.com/api/v1/docs) |
| Launchpad | Console: [https://platform.aignostics.com](https://platform.aignostics.com) |
| SDK upload command for the Console | Console: [https://platform.aignostics.com](https://platform.aignostics.com) |

[![License](https://img.shields.io/github/license/aignostics/python-sdk?logo=opensourceinitiative&logoColor=3DA639&labelColor=414042&color=A41831)](https://github.com/aignostics/python-sdk/blob/main/LICENSE)
[![Python Version](https://img.shields.io/pypi/pyversions/aignostics.svg?logo=python&color=204361&labelColor=1E2933)](https://pypi.org/project/aignostics/)
[![CI/CD](https://github.com/aignostics/python-sdk/actions/workflows/ci-cd.yml/badge.svg?branch=main)](https://github.com/aignostics/python-sdk/actions/workflows/ci-cd.yml)
Expand Down
2 changes: 2 additions & 0 deletions docs/partials/get_started_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ uvx aignostics --help

You should see the list of available command groups (`application`, `dataset`, `bucket`, `system`, and more). If the command isn't found, open a new terminal and try again.

Each command prints a deprecation notice to stderr. Stdout does not change, so scripts that read the output of a command continue to work.

## Run Atlas H&E-TME from the command line

This example downloads a public lung cancer dataset, submits an [Atlas H&E-TME](https://www.aignostics.com/products/he-tme-profiling-product) run, and downloads the results:
Expand Down
2 changes: 2 additions & 0 deletions docs/partials/get_started_launchpad.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ uvx aignostics launchpad

The first time you run this, it takes a minute or two to get ready. Then the **Launchpad window opens** — that means everything installed correctly.

Each page of the window shows a deprecation banner below the header. The banner has links to the alternatives: the Aignostics Platform REST API docs and the Console.

![The Launchpad window when it first opens](_static/launchpad/01-launchpad-window.png)

If you see an error instead of the window, see [Troubleshooting](#troubleshooting).
Expand Down
10 changes: 10 additions & 0 deletions docs/source/_static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@
width: 100% !important;
height: auto !important;
}

/* Deprecation announcement: wrap the long notice so that both links stay
visible. Furo's default is one line with a fixed height and a horizontal scroll. */
.announcement {
height: auto;
min-height: var(--header-height);
}
.announcement-content {
white-space: normal;
}
12 changes: 12 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Sphinx configuration."""

import html
import re
import sys
from datetime import UTC, datetime
from pathlib import Path

from aignostics.constants import DEPRECATION_NOTICE

# Add _ext directory to sys.path for custom extensions
sys.path.insert(0, str(Path(__file__).parent / "_ext"))

Expand Down Expand Up @@ -98,6 +101,14 @@
"api_reference_v1.rst",
]

# Deprecation banner on each page: the notice with each URL as a clickable link.
# The closing parenthesis after a URL in the notice is not part of the link.
DEPRECATION_ANNOUNCEMENT = re.sub(
r"https://[^\s)]+",
lambda match: f'<a href="{match.group(0)}">{match.group(0)}</a>',
html.escape(DEPRECATION_NOTICE),
)

html_theme = "furo"
html_static_path = ["_static"]
html_css_files = ["custom.css"]
Expand All @@ -120,6 +131,7 @@
"color-brand-primary": "#9b95ec",
"color-brand-content": "#9b95ec",
},
"announcement": DEPRECATION_ANNOUNCEMENT,
}


Expand Down
3 changes: 2 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def _generate_openapi_schemas(session: nox.Session) -> None:
f"--api-version={version}",
*format_info["args"],
]
session.run(*cmd_args, stdout=f, external=True)
session.run(*cmd_args, stdout=f, stderr=None, external=True)
session.log(f"Generated API {version} OpenAPI schema in {format_name} format")


Expand All @@ -459,6 +459,7 @@ def _generate_sdk_metadata_schema(session: nox.Session, schema_type: str) -> Non
"--env",
LOG_STDERR_DISABLED,
stdout=f,
stderr=None,
external=True,
)

Expand Down
12 changes: 12 additions & 0 deletions src/aignostics/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
"""Python SDK providing access to Aignostics AI services."""

import multiprocessing
import os
import warnings
from typing import Any

# TODO(Helmut): remove when google_crc32c supports Python 3.14
warnings.filterwarnings("ignore", message="As the c extension couldn't be imported", category=RuntimeWarning)

from loguru import logger # noqa: E402

from .constants import ( # noqa: E402
DEPRECATION_NOTICE,
HETA_APPLICATION_ID,
SENTRY_INTEGRATIONS,
TEST_APP_APPLICATION_ID,
WSI_SUPPORTED_FILE_EXTENSIONS,
WSI_SUPPORTED_FILE_EXTENSIONS_TEST_APP,
)
from .utils import __is_library_mode__ # noqa: E402
from .utils.boot import boot # noqa: E402

# Add scheme to HTTP proxy environment variables if missing
Expand Down Expand Up @@ -42,7 +47,14 @@ def _log_filter(record: Any) -> bool: # noqa: ANN401

boot(sentry_integrations=SENTRY_INTEGRATIONS, log_filter=_log_filter)

# Warn library users once per process tree. The CLI shows its own notice, and child processes
# (for example of the Launchpad) import the package again.
if __is_library_mode__ and multiprocessing.parent_process() is None:
logger.warning("{}", DEPRECATION_NOTICE)
warnings.warn(DEPRECATION_NOTICE, FutureWarning, stacklevel=2)

__all__ = [
"DEPRECATION_NOTICE",
"HETA_APPLICATION_ID",
"TEST_APP_APPLICATION_ID",
"WSI_SUPPORTED_FILE_EXTENSIONS",
Expand Down
64 changes: 61 additions & 3 deletions src/aignostics/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import sys
from importlib.util import find_spec
from itertools import pairwise
from pathlib import Path
from typing import Any

import typer
from loguru import logger
from typer.core import TyperGroup

from aignostics.constants import NOTEBOOK_DEFAULT, WINDOW_TITLE
from aignostics.constants import DEPRECATION_NOTICE, NOTEBOOK_DEFAULT, WINDOW_TITLE
from aignostics.utils import (
__is_running_in_container__,
__python_version__,
Expand All @@ -16,10 +19,63 @@
prepare_cli,
)

_JSON_OUTPUT_REQUESTED = "aignostics.json_output_requested"


def _requests_json_output(args: list[str]) -> bool:
"""Check if the command arguments request JSON output.

Args:
args (list[str]): Command line arguments.

Returns:
bool: True if the arguments contain ``--format json`` or ``--format=json``.
"""
return "--format=json" in args or any(arg == "--format" and value == "json" for arg, value in pairwise(args))


class _RootGroup(TyperGroup):
"""Root command group that records if the command arguments request JSON output.

Click empties ``ctx.args`` before it calls the root callback. So the group records the
decision in ``ctx.meta`` while it parses the arguments.
"""

def parse_args(self, ctx: Any, args: list[str]) -> list[str]: # noqa: ANN401
"""Record if the arguments request JSON output, then parse them.

Args:
ctx (Any): Click context of the root command group.
args (list[str]): Command line arguments.

Returns:
list[str]: The remaining arguments.
"""
ctx.meta[_JSON_OUTPUT_REQUESTED] = _requests_json_output(args)
return super().parse_args(ctx, args)


cli = typer.Typer(
help="Command Line Interface (CLI) of Aignostics Python SDK providing access to Aignostics Platform.",
cls=_RootGroup,
help=f"{DEPRECATION_NOTICE}\n\n"
"Command Line Interface (CLI) of Aignostics Python SDK providing access to Aignostics Platform.",
)


def _print_deprecation_notice(ctx: typer.Context) -> None:
"""Print the deprecation notice to stderr, so that stdout of each command does not change.

Commands that write JSON (``--format json``) get no notice, because they can write a JSON error object
to stderr that consumers parse.

Args:
ctx (typer.Context): Typer context of the root command.
"""
if ctx.meta.get(_JSON_OUTPUT_REQUESTED, False):
return
typer.secho(DEPRECATION_NOTICE, err=True, fg=typer.colors.YELLOW)


if find_spec("nicegui") and find_spec("webview") and not __is_running_in_container__:

@cli.command()
Expand Down Expand Up @@ -120,7 +176,9 @@ def mcp_list_tools() -> None:
cli.add_typer(mcp_cli)

prepare_cli(
cli, f"🔬 Aignostics Python SDK v{__version__} - built with love in Berlin 🐻 // Python v{__python_version__}"
cli,
f"🔬 Aignostics Python SDK v{__version__} - built with love in Berlin 🐻 // Python v{__python_version__}",
root_callback=_print_deprecation_notice,
)


Expand Down
9 changes: 9 additions & 0 deletions src/aignostics/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,14 @@
WSI_SUPPORTED_FILE_EXTENSIONS_TEST_APP = {".tiff"}

WINDOW_TITLE = "Aignostics Launchpad"

# Deprecation of the Aignostics Python SDK
PLATFORM_API_DOCS_URL = "https://platform.aignostics.com/api/v1/docs"
CONSOLE_URL = "https://platform.aignostics.com"
DEPRECATION_NOTICE = (
"The Aignostics Python SDK is deprecated and gets only bug fixes and security fixes until April 30, 2027. "
"After that date, the repository and the PyPI project are archived. "
f"Use the Aignostics Platform REST API ({PLATFORM_API_DOCS_URL}) or the Console ({CONSOLE_URL})."
)
# Organizations with internal/advanced access (e.g., platform-wide queue visibility, GPU config)
INTERNAL_ORGS = {"aignostics", "pre-alpha-org", "lmu", "charite"}
8 changes: 8 additions & 0 deletions src/aignostics/gui/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ The gui module provides common GUI framework components and theming for the Aign
- `_frame.py` - Common layout components and health updates
- `_error.py` - Error page handling (in-file class `PageBuilder`, imported as `ErrorPageBuilder`)

**Deprecation Banner (`_frame.py`):**

- `frame()` renders a full-width banner directly below the header, before the page content (and the left drawer), so every page that uses `frame()` shows it
- Content: a `warning` icon, `aignostics.constants.DEPRECATION_NOTICE` as a label, and two links that open in a new tab: "Open REST API docs" (`PLATFORM_API_DOCS_URL`) and "Open Console" (`CONSOLE_URL`)
- Marker: `DEPRECATION_BANNER` (used by `tests/aignostics/gui/frame_test.py`)
- The banner is not closable: it has no close button and no state to hide it
- Do not render the notice with `ui.html`; use `ui.label` and `ui.link`

**Usage Pattern:**

- Provides shared `PageBuilder` class for module auto-discovery
Expand Down
15 changes: 13 additions & 2 deletions src/aignostics/gui/_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from humanize import naturaldelta
from loguru import logger

from aignostics.constants import WINDOW_TITLE
from aignostics.constants import CONSOLE_URL, DEPRECATION_NOTICE, PLATFORM_API_DOCS_URL, WINDOW_TITLE
from aignostics.utils import __version__, open_user_data_directory

from ._theme import theme
Expand All @@ -28,6 +28,7 @@
CLASSES_FULL_WIDTH = "w-full"
CLASSES_FULL_HEIGHT = "h-full"
CLASSES_FULL_SIZE = f"{CLASSES_FULL_WIDTH} {CLASSES_FULL_HEIGHT}"
CLASSES_BANNER_LINK = "text-black underline whitespace-nowrap"


@contextmanager
Expand Down Expand Up @@ -272,6 +273,16 @@ def toggle_dark_mode() -> None:
with ui.button(on_click=lambda _: right_drawer.toggle(), icon="menu").props(FLAT_COLOR_WHITE):
ui.tooltip("Open menu")

with (
ui.row()
.classes(f"{CLASSES_FULL_WIDTH} items-center no-wrap gap-3 px-4 py-2 bg-amber-100 text-black")
.mark("DEPRECATION_BANNER")
):
ui.icon("warning", color="warning").classes("text-2xl")
ui.label(DEPRECATION_NOTICE).classes("grow")
ui.link("Open REST API docs", PLATFORM_API_DOCS_URL, new_tab=True).classes(CLASSES_BANNER_LINK)
ui.link("Open Console", CONSOLE_URL, new_tab=True).classes(CLASSES_BANNER_LINK)

if left_sidebar:
with ui.left_drawer(top_corner=True, bottom_corner=True, elevated=True).props("breakpoint=0"):
yield
Expand Down Expand Up @@ -337,7 +348,7 @@ def toggle_dark_mode() -> None:
with ui.item_section().props("avatar"):
ui.icon("domain", color="primary")
with ui.item_section():
ui.link("Go to Console", "https://platform.aignostics.com", new_tab=True).mark("LINK_PLATFORM")
ui.link("Go to Console", CONSOLE_URL, new_tab=True).mark("LINK_PLATFORM")
with ui.item().props("clickable"):
with ui.item_section().props("avatar"):
ui.icon("local_library", color="primary")
Expand Down
Loading
Loading