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
6 changes: 0 additions & 6 deletions src/runpod_flash/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
update,
)
from .update_checker import start_background_check
from ..core.cli_context import mark_cli_invocation


def get_version() -> str:
Expand Down Expand Up @@ -79,11 +78,6 @@ def main(
version: bool = typer.Option(False, "--version", "-v", help="Show version"),
):
"""Runpod Flash CLI - Distributed inference and serving framework."""
# Mark this process as a CLI invocation so resource lifecycle operations
# (deploy/undeploy/app/env management) are permitted. Direct SDK calls
# outside the CLI raise FlashUsageError. See core.cli_context.
mark_cli_invocation()

if version:
console.print(f"Runpod Flash CLI v{get_version()}")
raise typer.Exit()
Expand Down
97 changes: 0 additions & 97 deletions src/runpod_flash/core/cli_context.py

This file was deleted.

19 changes: 1 addition & 18 deletions src/runpod_flash/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,7 @@
"""


class FlashError(Exception):
"""Base class for all runpod_flash domain errors.

Catch this to handle any flash-raised error; catch a subclass for a
specific failure mode.
"""


class FlashUsageError(FlashError):
"""Raised when the SDK is used in a way that is not supported.

Currently raised when a CLI-managed lifecycle operation (deploy, undeploy,
app/environment management) is invoked directly from the SDK instead of
through the flash CLI. The message names the equivalent CLI command.
"""


class RunpodAPIKeyError(FlashError):
class RunpodAPIKeyError(Exception):
"""Raised when RUNPOD_API_KEY environment variable is missing or invalid.

This exception provides helpful guidance on how to obtain and configure
Expand Down
8 changes: 0 additions & 8 deletions src/runpod_flash/core/resources/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging

from ..api.runpod import RunpodGraphQLClient
from ..cli_context import cli_only

from .constants import (
TARBALL_CONTENT_TYPE,
Expand Down Expand Up @@ -326,7 +325,6 @@ async def _hydrate(self) -> None:
self._hydrated = True
return

@cli_only("flash env create")
async def create_environment(self, environment_name: str) -> Dict[str, Any]:
"""Create an environment within an app.

Expand Down Expand Up @@ -364,7 +362,6 @@ async def _get_tarball_upload_url(self, tarball_size: int) -> Dict[str, str]:
{"flashAppId": self.id, "tarballSize": tarball_size}
)

@cli_only("flash deploy")
async def deploy_build_to_environment(
self,
build_id: str,
Expand Down Expand Up @@ -497,14 +494,12 @@ async def from_name(cls, app_name: str) -> "FlashApp":
return cls(app_name, id=result["id"], eager_hydrate=False)

@classmethod
@cli_only("flash app create")
async def create(cls, app_name: str) -> "FlashApp":
async with RunpodGraphQLClient() as client:
result = await client.create_flash_app({"name": app_name})
return cls(app_name, id=result["id"], eager_hydrate=False)

@classmethod
@cli_only("flash app create")
async def get_or_create(cls, app_name: str) -> "FlashApp":
try:
return await cls.from_name(app_name)
Expand All @@ -514,7 +509,6 @@ async def get_or_create(cls, app_name: str) -> "FlashApp":
return cls(app_name, id=result["id"], eager_hydrate=False)

@classmethod
@cli_only("flash app create")
async def create_environment_and_app(
cls, app_name: str, environment_name: str
) -> Tuple["FlashApp", Dict]:
Expand All @@ -528,7 +522,6 @@ async def list(cls):
return await client.list_flash_apps()

@classmethod
@cli_only("flash app delete")
async def delete(
cls, app_name: Optional[str] = None, app_id: Optional[str] = None
) -> bool:
Expand All @@ -547,7 +540,6 @@ async def delete(
result = await client.delete_flash_app(app_id)
return result.get("success", False)

@cli_only("flash env delete")
async def delete_environment(self, environment_name: str) -> bool:
"""Delete an environment from this flash app.

Expand Down
6 changes: 2 additions & 4 deletions src/runpod_flash/core/resources/load_balancer_sls_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ class LoadBalancerSlsResource(ServerlessResource):
workersMin=1,
workersMax=3,
)

Deploy via the CLI (`flash deploy`); SDK lifecycle calls are CLI-only.
await lb.deploy()
"""

# Override default type to LB
Expand Down Expand Up @@ -210,8 +209,7 @@ class CpuLoadBalancerSlsResource(CpuEndpointMixin, LoadBalancerSlsResource):
workersMin=1,
workersMax=3,
)

Deploy via the CLI (`flash deploy`); SDK lifecycle calls are CLI-only.
await cpu_lb.deploy()
"""

instanceIds: Optional[List[CpuInstanceType]] = [CpuInstanceType.CPU3G_2_8]
Expand Down
2 changes: 0 additions & 2 deletions src/runpod_flash/core/resources/network_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from ..urls import RUNPOD_CONSOLE_URL
from .base import DeployableResource
from .resource_manager import ResourceManager
from ..cli_context import cli_only

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -215,7 +214,6 @@ async def _do_deploy(self) -> "DeployableResource":
log.error(f"{self} failed to deploy: {e}")
raise

@cli_only("flash deploy")
async def deploy(self) -> "DeployableResource":
resource_manager = ResourceManager()
resource = await resource_manager.get_or_deploy_resource(self)
Expand Down
4 changes: 0 additions & 4 deletions src/runpod_flash/core/resources/serverless.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from runpod.endpoint.runner import Job

from ..api.runpod import RunpodGraphQLClient
from ..cli_context import cli_only
from ..exceptions import RunpodAPIKeyError
from ..utils.backoff import get_backoff_delay
from .base import DeployableResource
Expand Down Expand Up @@ -1115,7 +1114,6 @@ async def _do_deploy(self) -> "DeployableResource":
log.error(f"{self} failed to deploy: {e}")
raise

@cli_only("flash deploy")
async def update(self, new_config: "ServerlessResource") -> "ServerlessResource":
"""Update existing endpoint with new configuration.

Expand Down Expand Up @@ -1319,7 +1317,6 @@ def _has_structural_changes(self, new_config: "ServerlessResource") -> bool:

return False

@cli_only("flash deploy")
async def deploy(self) -> "DeployableResource":
resource_manager = ResourceManager()
resource = await resource_manager.get_or_deploy_resource(self)
Expand Down Expand Up @@ -1372,7 +1369,6 @@ async def _do_undeploy(self) -> bool:

return False

@cli_only("flash undeploy")
async def undeploy(self) -> Dict[str, Any]:
resource_manager = ResourceManager()
result = await resource_manager.undeploy_resource(self.resource_id)
Expand Down
31 changes: 0 additions & 31 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import pytest

from runpod_flash.core.cli_context import _invoked_by_cli, allow_lifecycle_operations
from runpod_flash.core.resources.resource_manager import ResourceManager
from runpod_flash.core.utils.singleton import SingletonMixin

Expand All @@ -30,36 +29,6 @@ def pytest_configure(config):
"""
# This hook is called early in pytest initialization
# xdist will check for this during test distribution
config.addinivalue_line(
"markers",
"no_cli_context: run with the flash CLI context disabled so guarded "
"lifecycle methods raise FlashUsageError (used by guard tests).",
)


@pytest.fixture(autouse=True)
def _flash_cli_context(request):
"""Permit CLI-managed lifecycle operations during tests.

Lifecycle methods (deploy/undeploy/app/env management) are restricted to
flash CLI invocation. Most tests drive them directly, so enable the context
by default. Guard tests opt out with @pytest.mark.no_cli_context.

The opt-out branch actively forces the context to False (rather than merely
skipping the enable) and restores it afterwards. ``mark_cli_invocation()`` is
set-and-leave, so a CLI test that ran earlier in the same worker could leave
the ContextVar True; forcing False here makes guard tests independent of test
order instead of relying on each guard module to reset the var itself.
"""
if request.node.get_closest_marker("no_cli_context"):
token = _invoked_by_cli.set(False)
try:
yield
finally:
_invoked_by_cli.reset(token)
else:
with allow_lifecycle_operations():
yield


def pytest_collection_modifyitems(config, items):
Expand Down
Loading
Loading