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
4 changes: 2 additions & 2 deletions src/openlifu/cloud/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

class Api:

def __init__(self):
self._request = Request()
def __init__(self, api_url: str):
self._request = Request(api_url)
self._request.debug_log = True
self._databases = DatabasesApi(self._request)
self._protocols = ProtocolsApi(self._request)
Expand Down
16 changes: 8 additions & 8 deletions src/openlifu/cloud/api/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import urllib3
from requests.adapters import HTTPAdapter

from openlifu.cloud.const import API_URL
from openlifu.cloud.utils import logger_cloud, to_json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
Expand All @@ -34,7 +33,8 @@ def init_poolmanager(self, *args, **kwargs):
class Request:
TIMEOUT = (5, 300)

def __init__(self):
def __init__(self, api_url: str):
self._api_url = api_url
self.headers = {}
self.session = requests.Session()
adapter = SlicerAdapter(
Expand All @@ -48,7 +48,7 @@ def _log_request(self, method: str, url: str, start_time: float, status_code: in

def get(self, url: str) -> str:
start = time.perf_counter()
response = self.session.get(API_URL + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.get(self._api_url + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("GET", url, start, response.status_code)

logger_cloud.debug(f"GET: {url}, status_code: {response.status_code}\nresponse: {response.text}")
Expand All @@ -57,7 +57,7 @@ def get(self, url: str) -> str:

def get_bytes(self, url: str) -> bytes:
start = time.perf_counter()
response = self.session.get(API_URL + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.get(self._api_url + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("GET_BYTES", url, start, response.status_code)

logger_cloud.debug(f"GET bytes: {url}, status_code: {response.status_code}")
Expand All @@ -66,7 +66,7 @@ def get_bytes(self, url: str) -> bytes:

def post(self, url: str, dto) -> str:
start = time.perf_counter()
response = self.session.post(API_URL + url, data=to_json(dto), headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.post(self._api_url + url, data=to_json(dto), headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("POST", url, start, response.status_code)

logger_cloud.debug(f"POST: {url}, body: {to_json(dto)}, status_code: {response.status_code}\nresponse: {response.text}")
Expand All @@ -75,7 +75,7 @@ def post(self, url: str, dto) -> str:

def post_bytes(self, url: str, data) -> str:
start = time.perf_counter()
response = self.session.post(API_URL + url, data=data, headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.post(self._api_url + url, data=data, headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("POST_BYTES", url, start, response.status_code)

logger_cloud.debug(f"POST bytes: {url}, status_code: {response.status_code}\nresponse: {response.text}")
Expand All @@ -84,7 +84,7 @@ def post_bytes(self, url: str, data) -> str:

def put(self, url: str, dto) -> str:
start = time.perf_counter()
response = self.session.put(API_URL + url, data=to_json(dto), headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.put(self._api_url + url, data=to_json(dto), headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("PUT", url, start, response.status_code)

logger_cloud.debug(f"PUT: {url}, body: {to_json(dto)}, status_code: {response.status_code}\nresponse: {response.text}")
Expand All @@ -93,7 +93,7 @@ def put(self, url: str, dto) -> str:

def delete(self, url: str) -> str:
start = time.perf_counter()
response = self.session.delete(API_URL + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
response = self.session.delete(self._api_url + url, headers=self.headers, timeout=self.TIMEOUT, verify=False)
self._log_request("DELETE", url, start, response.status_code)

logger_cloud.debug(f"DELETE: {url}, status_code: {response.status_code}\nresponse: {response.text}")
Expand Down
15 changes: 11 additions & 4 deletions src/openlifu/cloud/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from openlifu.cloud.components.transducers import Transducers
from openlifu.cloud.components.users import Users
from openlifu.cloud.components.volumes import Volumes
from openlifu.cloud.const import API_URL_DEV, API_URL_PROD, ENV_DEV, ENV_PROD
from openlifu.cloud.filesystem_observer import FilesystemObserver
from openlifu.cloud.status import Status
from openlifu.cloud.sync_thread import SyncThread
Expand All @@ -30,10 +31,16 @@

class Cloud:

def __init__(self):
def __init__(self, environment: str = ENV_PROD):
if environment == ENV_DEV:
api_url = API_URL_DEV
elif environment == ENV_PROD:
api_url = API_URL_PROD
else:
raise ValueError(f"Unsupported cloud environment {environment!r}. Expected {ENV_DEV!r} or {ENV_PROD!r}.")
self._filesystem_observer = FilesystemObserver(self._on_file_system_update)
self._api = Api()
self._websocket = Websocket(self._on_websocket_update)
self._api = Api(api_url)
self._websocket = Websocket(api_url, self._on_websocket_update)
self._components: List[AbstractComponent] = []
self._sync_thread = SyncThread(self._on_status_changed)
self._db_path: Path | None = None
Expand Down Expand Up @@ -172,7 +179,7 @@ def _create_components(self):
logger_cloud.setLevel(logging.DEBUG)
logger_cloud.addHandler(logging.StreamHandler(sys.stdout))

cloud = Cloud()
cloud = Cloud(ENV_DEV)
token = os.getenv("TOKEN")
db_path = os.getenv("DB_PATH")

Expand Down
6 changes: 5 additions & 1 deletion src/openlifu/cloud/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

API_URL = "https://api.openwater.health"
API_URL_PROD = "https://api.openwater.health"
API_URL_DEV = "https://dev.api.openwater.health"

ENV_PROD = "prod"
ENV_DEV = "dev"

CONFIG_FILE = "config"
DATA_FILE = "data"
Expand Down
8 changes: 4 additions & 4 deletions src/openlifu/cloud/ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import socketio
from socketio import exceptions

from openlifu.cloud.const import API_URL
from openlifu.cloud.utils import logger_cloud

DATABASE_UPDATES_NS = "/database_updates"

class Websocket:

def __init__(self, update_callback: Callable[[dict], None]):
def __init__(self, api_url: str, update_callback: Callable[[dict], None]):
self._api_url = api_url
self._sio: socketio.Client | None = None
self._database_id = None
self._auth = {}
Expand All @@ -28,7 +28,7 @@ def authenticate(self, access_token: str):
self.connect(self._database_id)

def connect(self, database_id: int):
self.log(f"Attempting connection to {API_URL} for DB {database_id}")
self.log(f"Attempting connection to {self._api_url} for DB {database_id}")

if self._sio is not None:
self.disconnect()
Expand Down Expand Up @@ -71,7 +71,7 @@ def on_update(data):

try:
self._sio.connect(
f"{API_URL}/socket.io",
f"{self._api_url}/socket.io",
auth=self._auth,
namespaces=[DATABASE_UPDATES_NS],
transports=["websocket"],
Expand Down
41 changes: 41 additions & 0 deletions tests/test_cloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

import pytest

from openlifu.cloud.cloud import Cloud
from openlifu.cloud.const import API_URL_DEV, API_URL_PROD


@pytest.mark.parametrize(("kwargs", "api_url"), [
({}, API_URL_PROD),
({"environment": "prod"}, API_URL_PROD),
({"environment": "dev"}, API_URL_DEV),
])
def test_environment_routes_http_and_websocket(mocker, tmp_path, kwargs, api_url):
send = mocker.patch("requests.Session.send")
send.return_value.status_code = 200
send.return_value.text = '{"id": 42}'
websocket = mocker.patch("openlifu.cloud.ws.socketio.Client")
mocker.patch("openlifu.cloud.cloud.get_mac_address", return_value="00:11:22:33:44:55")
mocker.patch("openlifu.cloud.cloud.SyncThread.start")

cloud = Cloud(**kwargs)
try:
cloud.set_access_token("test-token")
cloud.start(tmp_path)

request = send.call_args.args[0]
assert request.method == "PUT"
assert request.url == api_url + "/databases/claim"
assert request.headers["Authorization"] == "Bearer test-token"
connection = websocket.return_value.connect.call_args
assert connection.args == (api_url + "/socket.io",)
assert connection.kwargs["auth"] == {"token": "Bearer test-token"}
finally:
cloud.stop()


@pytest.mark.parametrize("environment", ["DEV", "deev", "dev ", "staging", "", None])
def test_invalid_environment(environment):
with pytest.raises(ValueError, match="Unsupported cloud environment"):
Cloud(environment=environment)
Loading