Skip to content
Open
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
44 changes: 28 additions & 16 deletions apipod/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ def input_yes_no(question: str, default: bool = True) -> bool:
def select_base_image(manager: DeploymentManager, config_data: dict) -> str:
"""Interactive base image selection process."""
recommended_image = manager.recommend_image(config_data)
print(f"Detected configuration: Python {config_data.get('python_version')}, "
f"PyTorch: {config_data.get('pytorch')}, TensorFlow: {config_data.get('tensorflow')}, "
f"ONNX: {config_data.get('onnx')}")
print(
f"Detected configuration: profile={config_data.get('profile')}, "
f"Python {config_data.get('python_version')}, "
f"PyTorch: {config_data.get('pytorch')}, TensorFlow: {config_data.get('tensorflow')}, "
f"ONNX: {config_data.get('onnx')}"
)
print(f"Recommended Base Image: {recommended_image}")

if input_yes_no("Is this correct?"):
Expand Down Expand Up @@ -127,9 +130,14 @@ def run_build(args):
print("Error: Failed to obtain configuration.")
return

config_data["orchestrator"] = args.orchestrator
config_data["compute"] = args.compute
config_data["provider"] = args.provider
# Only override apipod.json with CLI flags when the user actually passed them.
# Argparse defaults are None so a plain `apipod --build` preserves the config file.
if args.orchestrator is not None:
config_data["orchestrator"] = args.orchestrator
if args.compute is not None:
config_data["compute"] = args.compute
if args.provider is not None:
config_data["provider"] = args.provider
if args.region:
config_data["region"] = args.region

Expand Down Expand Up @@ -159,16 +167,20 @@ def run_start(args):
"""Start an APIPod service with the given configuration."""
from apipod import APIPod

orchestrator = args.orchestrator or "local"
compute = args.compute or "dedicated"
provider = args.provider or "localhost"

app = APIPod(
orchestrator=args.orchestrator,
compute=args.compute,
provider=args.provider,
orchestrator=orchestrator,
compute=compute,
provider=provider,
)

port = args.port or 8000
host = args.host or "0.0.0.0"

print(f"Starting APIPod (orchestrator={args.orchestrator}, compute={args.compute}, provider={args.provider})")
print(f"Starting APIPod (orchestrator={orchestrator}, compute={compute}, provider={provider})")
app.start(port=port, host=host)


Expand Down Expand Up @@ -215,20 +227,20 @@ def main():
config_group.add_argument(
"--orchestrator",
choices=orchestrator_choices,
default="local",
help=f"Orchestration platform (default: local). Options: {', '.join(orchestrator_choices)}"
default=None,
help=f"Orchestration platform. Overrides apipod.json when passed; otherwise falls back to the config file or 'local'. Options: {', '.join(orchestrator_choices)}"
)
config_group.add_argument(
"--compute",
choices=compute_choices,
default="dedicated",
help=f"Compute type (default: dedicated). Options: {', '.join(compute_choices)}"
default=None,
help=f"Compute type. Overrides apipod.json when passed; otherwise falls back to the config file or 'dedicated'. Options: {', '.join(compute_choices)}"
)
config_group.add_argument(
"--provider",
choices=provider_choices,
default="localhost",
help=f"Infrastructure provider (default: localhost). Options: {', '.join(provider_choices)}"
default=None,
help=f"Infrastructure provider. Overrides apipod.json when passed; otherwise falls back to the config file or 'localhost'. Options: {', '.join(provider_choices)}"
)
config_group.add_argument(
"--region",
Expand Down
3 changes: 2 additions & 1 deletion apipod/deploy/detectors/IDetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def should_ignore(self, path: str) -> bool:
'__pycache__', '.git', '.svn', '.hg', '.DS_Store',
'node_modules', 'venv', 'env', '.env', '.venv',
'build', 'dist', '.pytest_cache', '.mypy_cache',
'.tox', '.coverage', 'htmlcov', '.eggs', '*.egg-info'
'.tox', '.coverage', 'htmlcov', '.eggs', '*.egg-info',
'apipod-deploy',
}

# Check if any part of the path matches ignore patterns
Expand Down
31 changes: 23 additions & 8 deletions apipod/deploy/detectors/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ class EntrypointDetector(Detector):
def detect(self, target_file: Optional[str] | None = None) -> Dict[str, Any]:
print("Scanning for entrypoint and service configuration...")

# Only fields the detector populates from the entrypoint go here.
# Deployment defaults (orchestrator/compute/provider) live in DeploymentConfig.
result = {
"file": None,
"title": "apipod-service", # Default
"found_config": False
"title": "apipod-service",
"found_config": False,
}

# 1. Prioritize user-provided target file
Expand Down Expand Up @@ -85,17 +87,30 @@ def _scan_file_for_title(self, file_path: str, result: Dict[str, Any]):
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id == "APIPod":
result["found_config"] = True
for keyword in node.keywords:
value = self._ast_constant(keyword.value)
if value is None:
continue
if keyword.arg == "title":
if isinstance(keyword.value, ast.Constant): # Python 3.8+
result["title"] = keyword.value.value
result["found_config"] = True
elif isinstance(keyword.value, ast.Str): # Python < 3.8
result["title"] = keyword.value.s
result["found_config"] = True
result["title"] = value
elif keyword.arg == "orchestrator":
result["orchestrator"] = value
elif keyword.arg == "compute":
result["compute"] = value
elif keyword.arg == "provider":
result["provider"] = value
except Exception:
pass

@staticmethod
def _ast_constant(node) -> Optional[str]:
if isinstance(node, ast.Constant) and isinstance(node.value, str):
return node.value
if isinstance(node, ast.Str):
return node.s
return None

def _scan_file_for_indicators(self, file_path: str, result: Dict[str, Any]) -> bool:
try:
with open(file_path, "r", encoding="utf-8") as f:
Expand Down
Loading