diff --git a/simvue/api/objects/base.py b/simvue/api/objects/base.py index 7f43904f..334af761 100644 --- a/simvue/api/objects/base.py +++ b/simvue/api/objects/base.py @@ -164,10 +164,14 @@ def __init__( ) ) - self._headers: dict[str, str] = { - "Authorization": f"Bearer {self._user_config.server.token.get_secret_value()}", - "User-Agent": _user_agent or f"Simvue Python client {__version__}", - } + self._headers: dict[str, str] = ( + { + "Authorization": f"Bearer {self._user_config.server.token.get_secret_value()}", + "User-Agent": _user_agent or f"Simvue Python client {__version__}", + } + if not self._offline + else {} + ) self._staging: dict[str, typing.Any] = {} @@ -423,7 +427,7 @@ def _post(self, is_json: bool = True, **kwargs) -> dict[str, typing.Any]: _json_response = get_json_from_response( response=_response, - expected_status=[http.HTTPStatus.OK], + expected_status=[http.HTTPStatus.OK, http.HTTPStatus.CONFLICT], scenario=f"Creation of {self._label}", ) diff --git a/simvue/config/parameters.py b/simvue/config/parameters.py index 472811f6..c6d65c93 100644 --- a/simvue/config/parameters.py +++ b/simvue/config/parameters.py @@ -21,19 +21,23 @@ class ServerSpecifications(pydantic.BaseModel): - url: pydantic.AnyHttpUrl - token: pydantic.SecretStr + url: pydantic.AnyHttpUrl | None + token: pydantic.SecretStr | None @pydantic.field_validator("url") @classmethod - def url_to_api_url(cls, v: typing.Any) -> str: + def url_to_api_url(cls, v: typing.Any) -> str | None: + if not v: + return if f"{v}".endswith("/api"): return f"{v}" _url = URL(f"{v}") / "api" return f"{_url}" @pydantic.field_validator("token") - def check_token(cls, v: typing.Any) -> str: + def check_token(cls, v: typing.Any) -> str | None: + if not v: + return value = v.get_secret_value() if not (expiry := get_expiry(value)): raise AssertionError("Failed to parse Simvue token - invalid token form") diff --git a/simvue/config/user.py b/simvue/config/user.py index 931d2fa5..22357ce5 100644 --- a/simvue/config/user.py +++ b/simvue/config/user.py @@ -212,10 +212,10 @@ def fetch( _run_mode = mode or _config_dict["run"].get("mode") or "online" - if not _server_url: + if not _server_url and _run_mode != "offline": raise RuntimeError("No server URL was specified") - if not _server_token: + if not _server_token and _run_mode != "offline": raise RuntimeError("No server token was specified") _config_dict["server"]["token"] = _server_token diff --git a/simvue/run.py b/simvue/run.py index af7a5aa0..a618f04c 100644 --- a/simvue/run.py +++ b/simvue/run.py @@ -31,8 +31,8 @@ from simvue.api.objects.alert.base import AlertBase from simvue.api.objects.alert.fetch import Alert -from simvue.api.objects.folder import Folder, get_folder_from_path -from simvue.exception import ObjectNotFoundError, SimvueRunError +from simvue.api.objects.folder import Folder +from simvue.exception import SimvueRunError from simvue.utilities import prettify_pydantic @@ -185,9 +185,13 @@ def __init__( if self._user_config.metrics.resources_metrics_interval < 1 else self._user_config.metrics.resources_metrics_interval ) - self._headers: dict[str, str] = { - "Authorization": f"Bearer {self._user_config.server.token.get_secret_value()}" - } + self._headers: dict[str, str] = ( + { + "Authorization": f"Bearer {self._user_config.server.token.get_secret_value()}" + } + if mode != "offline" + else {} + ) self._sv_obj: RunObject | None = None self._pid: int | None = 0 self._shutdown_event: threading.Event | None = None @@ -419,7 +423,9 @@ def _create_dispatch_callback( if self._user_config.run.mode == "online" and not self._id: raise RuntimeError("Expected identifier for run") - if not self._user_config.server.url or not self._sv_obj: + if ( + self._user_config.run.mode != "offline" and not self._user_config.server.url + ) or not self._sv_obj: raise RuntimeError("Cannot commence dispatch, run not initialised") def _dispatch_callback( @@ -621,13 +627,10 @@ def init( self._term_color = not no_color - try: - self._folder = get_folder_from_path(path=folder) - except ObjectNotFoundError: - self._folder = Folder.new( - path=folder, offline=self._user_config.run.mode == "offline" - ) - self._folder.commit() # type: ignore + self._folder = Folder.new( + path=folder, offline=self._user_config.run.mode == "offline" + ) + self._folder.commit() # type: ignore if isinstance(visibility, str) and visibility not in ("public", "tenant"): self._error( @@ -638,7 +641,9 @@ def init( self._error("invalid mode specified, must be online, offline or disabled") return False - if not self._user_config.server.token or not self._user_config.server.url: + if self._user_config.run.mode != "offline" and ( + not self._user_config.server.token or not self._user_config.server.url + ): self._error( "Unable to get URL and token from environment variables or config file" )