Skip to content

Commit

Permalink
Split AppList into client and internal web RPCs (#2073)
Browse files Browse the repository at this point in the history
* Split AppList into client and internal web RPCs

* Remove app_utils.py and modal.app.list_apps()
  • Loading branch information
ekzhang committed Aug 6, 2024
1 parent 1875629 commit 3c059b1
Show file tree
Hide file tree
Showing 6 changed files with 18 additions and 70 deletions.
4 changes: 0 additions & 4 deletions modal/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
from ._utils.function_utils import FunctionInfo, is_global_object, is_top_level_function
from ._utils.grpc_utils import unary_stream
from ._utils.mount_utils import validate_volumes
from .app_utils import ( # noqa: F401
_list_apps,
list_apps,
)
from .client import _Client
from .cloud_bucket_mount import _CloudBucketMount
from .cls import _Cls
Expand Down
22 changes: 0 additions & 22 deletions modal/app_utils.py

This file was deleted.

26 changes: 7 additions & 19 deletions modal/cli/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Copyright Modal Labs 2022
import time
from typing import List, Optional, Union

import typer
Expand All @@ -9,9 +8,9 @@
from typer import Argument, Option

from modal._utils.async_utils import synchronizer
from modal.app_utils import _list_apps
from modal.client import _Client
from modal.environments import ensure_env
from modal.object import _get_environment_name
from modal_proto import api_pb2

from .utils import ENV_OPTION, display_table, get_app_id_from_name, stream_app_logs, timestamp_to_local
Expand All @@ -34,6 +33,11 @@
async def list(env: Optional[str] = ENV_OPTION, json: bool = False):
"""List Modal apps that are currently deployed/running or recently stopped."""
env = ensure_env(env)
client = await _Client.from_env()

resp: api_pb2.AppListResponse = await client.stub.AppList(
api_pb2.AppListRequest(environment_name=_get_environment_name(env))
)

columns: List[Union[Column, str]] = [
Column("App ID", min_width=25), # Ensure that App ID is not truncated in slim terminals
Expand All @@ -44,23 +48,7 @@ async def list(env: Optional[str] = ENV_OPTION, json: bool = False):
"Stopped at",
]
rows: List[List[Union[Text, str]]] = []
apps: List[api_pb2.AppStats] = await _list_apps(env)
now = time.time()
for app_stats in apps:
if (
# Previously, all deployed objects (Dicts, Volumes, etc.) created an entry in the App table.
# We are waiting to roll off support for old clients before we can clean up the database.
# Until then, we filter deployed "single-object apps" from this output based on the object entity.
(app_stats.object_entity and app_stats.object_entity != "ap")
# AppList always returns up to the 250 most-recently stopped apps, which is a lot for the CLI
# (it is also used in the web interface, where apps are organized by tabs and paginated).
# So we semi-arbitrarily limit the stopped apps to those stopped within the past 2 hours.
or (
app_stats.state in {api_pb2.AppState.APP_STATE_STOPPED} and (now - app_stats.stopped_at) > (2 * 60 * 60)
)
):
continue

for app_stats in resp.apps:
state = APP_STATE_TO_MESSAGE.get(app_stats.state, Text("unknown", style="gray"))
rows.append(
[
Expand Down
23 changes: 10 additions & 13 deletions modal_proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,16 @@ message AppListRequest {
}

message AppListResponse {
repeated AppStats apps = 1;
message AppListItem {
string app_id = 1;
string description = 3;
AppState state = 4;
double created_at = 5;
double stopped_at = 6;
int32 n_running_tasks = 8;
string name = 10;
}
repeated AppListItem apps = 1;
}

message AppLookupObjectRequest {
Expand Down Expand Up @@ -342,18 +351,6 @@ message AppSetObjectsRequest {
string single_object_id = 6;
}

message AppStats {
string app_id = 1;
string description = 3;
AppState state = 4;
double created_at = 5;
double stopped_at = 6;
int32 n_running_tasks = 8;
string object_entity = 9;
string name = 10;
double deployed_at = 11;
}

message AppStopRequest {
string app_id = 1 [ (modal.options.audit_target_attr) = true ];
AppStopSource source = 2;
Expand Down
11 changes: 0 additions & 11 deletions test/app_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from modal import App, Dict, Image, Mount, Secret, Stub, Volume, enable_output, web_endpoint
from modal._output import OutputManager
from modal.app import list_apps # type: ignore
from modal.exception import DeprecationError, ExecutionError, InvalidError, NotFoundError
from modal.partial_function import _parse_custom_domains
from modal.runner import deploy_app, deploy_stub
Expand Down Expand Up @@ -319,16 +318,6 @@ def test_app(client):
square_modal.remote(42)


def test_list_apps(client):
apps_0 = [app.name for app in list_apps(client=client)]
app = App()
deploy_app(app, "foobar", client=client)
apps_1 = [app.name for app in list_apps(client=client)]

assert len(apps_1) == len(apps_0) + 1
assert set(apps_1) - set(apps_0) == set(["foobar"])


def test_non_string_app_name():
with pytest.raises(InvalidError, match="Must be string"):
App(Image.debian_slim()) # type: ignore
Expand Down
2 changes: 1 addition & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ async def AppList(self, stream):
apps = []
for app_name, app_id in self.deployed_apps.items():
apps.append(
api_pb2.AppStats(
api_pb2.AppListResponse.AppListItem(
name=app_name,
description=app_name,
app_id=app_id,
Expand Down

0 comments on commit 3c059b1

Please sign in to comment.