Skip to content
2 changes: 0 additions & 2 deletions examples/behavior_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ def main():
LauncherCliArgs,
cli_args=[
"--debug-mode",
"--temp-dir",
"./local/.temp",
"--allow-dirty",
"--skip-hardware-validation",
"--data-dir",
Expand Down
4 changes: 2 additions & 2 deletions src/clabe/apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
ExecutableApp,
Executor,
OutputParser,
StdCommand,
identity_parser,
)
from ._bonsai import AindBehaviorServicesBonsaiApp, BonsaiApp
from ._curriculum import CurriculumApp, CurriculumSettings, CurriculumSuggestion
from ._python_script import PythonScriptApp
from .rpc_executors import RpcExecutor

__all__ = [
"BonsaiApp",
Expand All @@ -29,5 +29,5 @@
"OutputParser",
"PythonScriptApp",
"ExecutableApp",
"RpcExecutor",
"StdCommand",
]
7 changes: 7 additions & 0 deletions src/clabe/apps/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ def _parse_output(self, result: CommandResult) -> TOutput:
return self._output_parser(result)


class StdCommand(Command[CommandResult]):
"""Standard command that returns the raw CommandResult."""

def __init__(self, cmd: str) -> None:
super().__init__(cmd, identity_parser)


def identity_parser(result: CommandResult) -> CommandResult:
"""Helper parser that returns the CommandResult as-is."""
return result
6 changes: 4 additions & 2 deletions src/clabe/apps/_bonsai.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pydantic
from aind_behavior_services import AindBehaviorRigModel, AindBehaviorSessionModel, AindBehaviorTaskLogicModel

from ..constants import TMP_DIR
from ._base import Command, CommandResult, ExecutableApp, identity_parser
from ._executors import _DefaultExecutorMixin

Expand Down Expand Up @@ -178,7 +179,7 @@ def __init__(
self,
workflow: os.PathLike,
*,
temp_directory: os.PathLike,
temp_directory: Optional[os.PathLike] = None,
rig: Optional[AindBehaviorRigModel] = None,
session: Optional[AindBehaviorSessionModel] = None,
task_logic: Optional[AindBehaviorTaskLogicModel] = None,
Expand Down Expand Up @@ -231,6 +232,8 @@ def __init__(
# -p:"TaskLogicPath"="/tmp/task_logic_temp.json"
```
"""
self._temp_directory = Path(temp_directory or TMP_DIR)

additional_externalized_properties = kwargs.pop("additional_externalized_properties", {}) or {}
if rig:
additional_externalized_properties["RigPath"] = os.path.abspath(self._save_temp_model(model=rig))
Expand All @@ -243,7 +246,6 @@ def __init__(
super().__init__(
workflow=workflow, additional_externalized_properties=additional_externalized_properties, **kwargs
)
self._temp_directory = Path(temp_directory)

def _save_temp_model(self, model: pydantic.BaseModel) -> Path:
"""
Expand Down
35 changes: 25 additions & 10 deletions src/clabe/apps/open_ephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@
import os
from enum import Enum
from pathlib import Path
from typing import Any, Literal, Optional
from typing import Any, Literal

import requests
from pydantic import BaseModel, Field

from ..services import ServiceSettings
from ._base import Command, CommandResult, ExecutableApp, identity_parser
from ._executors import _DefaultExecutorMixin

logger = logging.getLogger(__name__)


class OpenEphysAppSettings(ServiceSettings):
"""Settings for Open Ephys App."""

__yml_section__ = "open_ephys"

signal_chain: os.PathLike
executable: os.PathLike = Path("./.open_ephys/open_ephys.exe")
address: str = "localhost"
port: int = 37497


class OpenEphysApp(ExecutableApp, _DefaultExecutorMixin):
"""
A class to manage the execution of Open Ephys GUI.
Expand All @@ -29,10 +41,7 @@ class OpenEphysApp(ExecutableApp, _DefaultExecutorMixin):

def __init__(
self,
signal_chain: os.PathLike,
*,
executable: os.PathLike = Path("./.open_ephys/open_ephys.exe"),
client: Optional["_OpenEphysGuiClient"] = None,
settings: OpenEphysAppSettings,
skip_validation: bool = False,
) -> None:
"""
Expand All @@ -51,9 +60,10 @@ def __init__(
app.run()
```
"""
self.signal_chain = Path(signal_chain).resolve()
self.executable = Path(executable).resolve()
self._client = client or _OpenEphysGuiClient()
self.settings = settings
self.signal_chain = Path(self.settings.signal_chain).resolve()
self.executable = Path(self.settings.executable).resolve()
self._client = _OpenEphysGuiClient(host=self.settings.address, port=self.settings.port)

if not skip_validation:
self.validate()
Expand All @@ -79,6 +89,11 @@ def command(self) -> Command[CommandResult]:
"""Get the command to execute."""
return self._command

@property
def client(self) -> "_OpenEphysGuiClient":
"""Get the Open Ephys GUI client."""
return self._client


class Status(str, Enum):
"""GUI acquisition/recording mode."""
Expand Down Expand Up @@ -233,7 +248,7 @@ def get_status(self) -> Status:
Current status containing the GUI mode (IDLE, ACQUIRE, or RECORD).
"""
data = self._get("/status")
return Status(**data)
return StatusResponse(**data).mode

def set_status(self, mode: Status) -> Status:
"""Set the GUI's acquisition/recording status.
Expand All @@ -249,7 +264,7 @@ def set_status(self, mode: Status) -> Status:
"""
request = StatusRequest(mode=mode)
data = self._put("/status", request)
return Status(**data)
return StatusResponse(**data).mode

def start_acquisition(self) -> Status:
"""Start data acquisition without recording.
Expand Down
4 changes: 2 additions & 2 deletions src/clabe/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from pydantic_settings import BaseSettings, CliApp, CliSubCommand

from .rpc._server import _RpcServerStartCli
from .xml_rpc._server import _XmlRpcServerStartCli


class CliAppSettings(BaseSettings, cli_prog_name="clabe", cli_kebab_case=True):
"""CLI application settings."""

rpc_server: CliSubCommand[_RpcServerStartCli]
xml_rpc_server: CliSubCommand[_XmlRpcServerStartCli]

def cli_cmd(self):
"""Run the selected subcommand."""
Expand Down
43 changes: 43 additions & 0 deletions src/clabe/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import enum
import logging
import os
import typing as t
from pathlib import Path

logger = logging.getLogger(__name__)

TMP_DIR = ".cache"


PROGRAMDATA_DIR = os.environ.get("PROGRAMDATA", "C:/ProgramData")

# The config files will be used in order, with the first one having the highest priority

KNOWN_CONFIG_FILES: t.List[str] = [
"./local/clabe.yml",
"./clabe.yml",
str(Path(PROGRAMDATA_DIR) / "clabe.yml"),
]


for i, p in enumerate(KNOWN_CONFIG_FILES):
if Path(p).exists():
logger.debug(f"Found config file: {p} with rank priority {i}")


class ByAnimalFiles(enum.StrEnum):
"""
Enum for file types associated with animals in the experiment.

Defines the standard file types that can be associated with individual
animals/subjects in behavior experiments.

Example:
```python
# Use the task logic file type
filename = f"{ByAnimalFiles.TASK_LOGIC}.json"
```
"""

TASK_LOGIC = "task_logic"
TRAINER_STATE = "trainer_state"
16 changes: 8 additions & 8 deletions src/clabe/launcher/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)

from .. import __version__, logging_helper
from ..constants import TMP_DIR
from ..git_manager import GitRepository
from ..ui import DefaultUIHelper, UiHelper
from ..utils import abspath, format_datetime, utcnow
Expand Down Expand Up @@ -68,10 +69,14 @@ def __init__(
"""
self._settings = settings
self.ui_helper = ui_helper
self.temp_dir = abspath(settings.temp_dir) / format_datetime(utcnow())
self.temp_dir.mkdir(parents=True, exist_ok=True)
self.temp_dir = Path(TMP_DIR) / format_datetime(utcnow())
self.computer_name = os.environ["COMPUTERNAME"]

repository_dir = Path(self.settings.repository_dir) if self.settings.repository_dir is not None else None
self.repository = GitRepository() if repository_dir is None else GitRepository(path=repository_dir)

self._ensure_directory_structure()

# Solve logger
if attached_logger:
_logger = logging_helper.add_file_handler(attached_logger, self.temp_dir / "launcher.log")
Expand All @@ -84,11 +89,6 @@ def __init__(

self._logger = _logger

repository_dir = Path(self.settings.repository_dir) if self.settings.repository_dir is not None else None
self.repository = GitRepository() if repository_dir is None else GitRepository(path=repository_dir)

self._ensure_directory_structure()

self._session: Optional[AindBehaviorSessionModel] = None
self._has_copied_logs = False

Expand Down Expand Up @@ -178,7 +178,7 @@ async def my_async_experiment(launcher: Launcher):
try:
self.copy_logs()
except ValueError as ve: # In the case session_directory fails
logger.error("Failed to copy logs: %s", ve) # we swallow the error
logger.error("Failed to copy logs from %s. Error: %s", self.temp_dir, ve) # we swallow the error
self._exit(-1)
else:
self._exit(_code)
Expand Down
4 changes: 0 additions & 4 deletions src/clabe/launcher/_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
from pathlib import Path
from typing import Optional

from pydantic import Field
Expand All @@ -26,6 +25,3 @@ class LauncherCliArgs(ServiceSettings, cli_prog_name="clabe", cli_kebab_case=Tru
skip_hardware_validation: CliImplicitFlag[bool] = Field(
default=False, description="Whether to skip hardware validation"
)
temp_dir: os.PathLike = Field(
default=Path("local/.temp"), description="The directory used for the launcher temp files"
)
2 changes: 1 addition & 1 deletion src/clabe/pickers/default_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

from .. import ui
from .._typing import TRig, TSession, TTaskLogic
from ..constants import ByAnimalFiles
from ..launcher import Launcher
from ..services import ServiceSettings
from ..utils import ByAnimalFiles
from ..utils.aind_auth import validate_aind_username

logger = logging.getLogger(__name__)
Expand Down
12 changes: 0 additions & 12 deletions src/clabe/rpc/__init__.py

This file was deleted.

2 changes: 1 addition & 1 deletion src/clabe/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pydantic_settings as ps

from .utils import KNOWN_CONFIG_FILES
from .constants import KNOWN_CONFIG_FILES

logger = logging.getLogger(__name__)

Expand Down
37 changes: 0 additions & 37 deletions src/clabe/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
"""Constants used throughout the CLABE package."""

import enum
import logging
import os
import typing as t
from pathlib import Path

from aind_behavior_services.utils import format_datetime, model_from_json_file, utcnow
Expand All @@ -15,23 +11,8 @@
"format_datetime",
"model_from_json_file",
"utcnow",
"ByAnimalFiles",
"KNOWN_CONFIG_FILES",
]

PROGRAMDATA_DIR = os.environ.get("PROGRAMDATA", "C:/ProgramData")

# The config files will be used in order, with the first one having the highest priority
KNOWN_CONFIG_FILES: t.List[str] = [
"./local/clabe.yml",
"./clabe.yml",
str(Path(PROGRAMDATA_DIR) / "clabe.yml"),
]

for i, p in enumerate(KNOWN_CONFIG_FILES):
if Path(p).exists():
logger.debug(f"Found config file: {p} with rank priority {i}")


def abspath(path: os.PathLike) -> Path:
"""
Expand All @@ -44,21 +25,3 @@ def abspath(path: os.PathLike) -> Path:
Path: The absolute path
"""
return Path(path).resolve()


class ByAnimalFiles(enum.StrEnum):
"""
Enum for file types associated with animals in the experiment.

Defines the standard file types that can be associated with individual
animals/subjects in behavior experiments.

Example:
```python
# Use the task logic file type
filename = f"{ByAnimalFiles.TASK_LOGIC}.json"
```
"""

TASK_LOGIC = "task_logic"
TRAINER_STATE = "trainer_state"
14 changes: 14 additions & 0 deletions src/clabe/xml_rpc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from ._client import XmlRpcClient, XmlRpcClientSettings
from ._executor import XmlRpcExecutor
from ._server import XmlRpcServer, XmlRpcServerSettings
from .models import FileInfo, JobResult

__all__ = [
"XmlRpcServerSettings",
"XmlRpcServer",
"XmlRpcClientSettings",
"XmlRpcClient",
"JobResult",
"FileInfo",
"XmlRpcExecutor",
]
Loading