Skip to content

Commit 3f09c26

Browse files
committed
feat: R service added
1 parent eee8a21 commit 3f09c26

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

rock_spawner/main.py

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from logging import basicConfig, DEBUG
55
from pydantic import BaseModel
66
from .views.pod import router as pod_router
7+
from .views.r import router as r_router
78

89
basicConfig(level=DEBUG)
910

@@ -44,3 +45,9 @@ async def get_health(
4445
prefix="/pod",
4546
tags=["Pods"],
4647
)
48+
49+
app.include_router(
50+
r_router,
51+
prefix="/rserver",
52+
tags=["R Server"],
53+
)

rock_spawner/services/r.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from typing import Dict
2+
import asyncio
3+
import logging
4+
import requests
5+
from requests.auth import HTTPBasicAuth
6+
from ..models.pod import PodRef
7+
from .pod import PodService
8+
from ..config import _config
9+
10+
r_status = None
11+
r_packages = None
12+
datashield_packages = None
13+
14+
class RService:
15+
def __init__(self):
16+
self.pod = None
17+
18+
async def get_status(self) -> Dict:
19+
"""Fetches the status of the R server."""
20+
global r_status
21+
logging.info(f"Current R status: {r_status}")
22+
if r_status is not None:
23+
return r_status
24+
logging.info("Fetching R status")
25+
await self.connect()
26+
r_status = self._fetch("/rserver")
27+
return r_status
28+
29+
async def get_packages(self) -> Dict:
30+
"""Fetches the list of available packages."""
31+
global r_packages
32+
logging.info(f"Current R packages: {r_packages}")
33+
if r_packages is not None:
34+
return r_packages
35+
await self.connect()
36+
r_packages = self._fetch("/rserver/packages")
37+
return r_packages
38+
39+
async def get_datashield_packages(self) -> Dict:
40+
"""Fetches the list of available DataSHIELD packages."""
41+
global datashield_packages
42+
logging.info(f"Current DataSHIELD packages: {datashield_packages}")
43+
if datashield_packages is not None:
44+
return datashield_packages
45+
await self.connect()
46+
datashield_packages = self._fetch("/rserver/packages/_datashield")
47+
return datashield_packages
48+
49+
async def connect(self):
50+
"""Connects to the R server."""
51+
if self.pod is None:
52+
self.pod = await PodService().create_pod(wait=True)
53+
await self._ensure_ready(self.pod)
54+
55+
async def close(self):
56+
"""Disconnects from the R server."""
57+
if self.pod is not None:
58+
await PodService().delete_pod(self.pod.name)
59+
self.pod = None
60+
61+
async def _ensure_ready(self, pod: PodRef):
62+
"""Ensures the R server is ready to receive requests."""
63+
ready = self._check(pod)
64+
attempts = 0
65+
while not ready and attempts < 10:
66+
await asyncio.sleep(1)
67+
ready = self._check(pod)
68+
attempts += 1
69+
logging.info(f"R Server ready: {ready} after {attempts} attempts")
70+
if not ready:
71+
raise Exception("R Server not ready")
72+
73+
def _check(self, pod: PodRef) -> bool:
74+
"""Checks if the R server is ready to receive requests."""
75+
url = f"http://{pod.ip}:{pod.port}/_check"
76+
logging.info(f"Checking {url}")
77+
try:
78+
response = requests.get(url)
79+
if response.status_code >= 200 and response.status_code < 300:
80+
return True
81+
except Exception as e:
82+
logging.error(f"Error checking R Server: {e}")
83+
return False
84+
85+
def _fetch(self, path: str) -> Dict:
86+
"""Fetches a path from the R server."""
87+
url = f"http://{self.pod.ip}:{self.pod.port}{path}"
88+
basicAuth = HTTPBasicAuth(_config.ROCK_ADMINISTRATOR_NAME if _config.ROCK_ADMINISTRATOR_NAME != "" else "administrator",
89+
_config.ROCK_ADMINISTRATOR_PASSWORD if _config.ROCK_ADMINISTRATOR_PASSWORD != "" else "password")
90+
logging.info(f"Fetching {url}")
91+
response = requests.get(url, auth=basicAuth)
92+
response.raise_for_status()
93+
return response.json()

rock_spawner/views/r.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from typing import Dict
2+
from fastapi import APIRouter
3+
from ..services.r import RService
4+
5+
router = APIRouter()
6+
7+
8+
@router.get("/")
9+
async def get_status() -> Dict:
10+
"""Get R server info."""
11+
service = RService()
12+
status = await service.get_status()
13+
await service.close()
14+
return status
15+
16+
@router.get("/packages")
17+
async def get_packages() -> Dict:
18+
"""Lists all R packages."""
19+
service = RService()
20+
pkgs = await service.get_packages()
21+
await service.close()
22+
return pkgs
23+
24+
@router.get("/packages/_datashield")
25+
async def get_datashield_packages() -> Dict:
26+
"""Lists all DataSHIELD R packages."""
27+
service = RService()
28+
pkgs = await service.get_datashield_packages()
29+
await service.close()
30+
return pkgs

0 commit comments

Comments
 (0)