"""Models for parsing and validating the contents of `settings.yaml`."""
from enum import Enum
from enum import unique
from typing import Dict, Optional
from pydantic import BaseModel
from pydantic import Extra
from pydantic import Json
from pydantic import validator
from pydantic_yaml import VersionedYamlModel
[docs]@unique
class LogLevel(str, Enum):
"""Possible log levels."""
_DEBUG = "DEBUG"
_INFO = "INFO"
_ERROR = "ERROR"
_WARNING = "WARNING"
_CRITICAL = "CRITICAL"
[docs]@unique
class EncoderEndpointType(str, Enum):
"""Possible types for the encoder input or output."""
FILE = "file"
LSL = "LSL"
[docs]@unique
class EphysGeneratorEndpointType(str, Enum):
"""Possible types of input for the ephys generator."""
TESTING = "testing"
LSL = "LSL"
[docs]@unique
class EncoderModelType(str, Enum):
"""Possible types of input for the encoder model."""
PLUGIN = "plugin"
VELOCITY_TUNING_CURVES = "velocity_tuning_curves"
[docs]class TimerModel(BaseModel, extra=Extra.forbid):
"""Settings for the timer implementation."""
max_cpu_buffer: float
loop_time: float
[docs]class LSLOutputModel(BaseModel, extra=Extra.forbid):
"""Settings for all LSL outlets."""
class _Instrument(BaseModel, extra=Extra.forbid):
manufacturer: str
model: str
id: int
channel_format: LSLChannelFormatType
stream_name: str
stream_type: str
source_id: str
instrument: _Instrument
channel_labels: Optional[list[str]]
[docs]class EncoderSettings(BaseModel, extra=Extra.forbid):
"""Settings for the encoder."""
[docs] class Output(BaseModel, extra=Extra.forbid):
"""Settings for the encoder output."""
n_channels: int
type: EncoderEndpointType
file: Optional[str]
lsl: Optional[LSLOutputModel]
model: str
preprocessor: Optional[str]
postprocessor: Optional[str]
model_weights_file: Optional[str]
input: Input
output: Output
@validator("model")
def _model_entry_point_must_be_a_python_file(cls, v):
if v is None or v.endswith(".py"):
return v
raise ValueError("The model entry point must be a Python file")
@validator("preprocessor", "postprocessor")
def _plugin_entry_point_must_be_a_python_file(cls, v):
if v is None or v.endswith(".py"):
return v
raise ValueError("The plugin entry point must be a Python file")
@validator("input", "output")
def _file_type_must_have_a_file_object(cls, value):
if value.type == EncoderEndpointType.FILE and not value.file:
raise ValueError("File type must have a file object")
return value
@validator("input", "output")
def _lsl_type_must_have_a_lsl_object(cls, value):
if value.type == EncoderEndpointType.LSL and not value.lsl:
raise ValueError("LSL type must have a lsl object")
return value
[docs]class EphysGeneratorSettings(BaseModel, extra=Extra.forbid):
"""Settings for the spike generator."""
[docs] class Output(BaseModel, extra=Extra.forbid):
"""Settings for the ephys generator output."""
[docs] class Raw(BaseModel, extra=Extra.forbid):
"""Settings for the ephys generator output type raw."""
lsl: LSLOutputModel
[docs] class LFP(BaseModel, extra=Extra.forbid):
"""Settings for the ephys generator output type LFP."""
data_frequency: float
filter_cutoff: float
filter_order: int
lsl: LSLOutputModel
[docs] class SpikeEvents(BaseModel, extra=Extra.forbid):
"""Settings for the ephys generator output type spike events."""
lsl: LSLOutputModel
raw: Raw
lfp: LFP
spike_events: SpikeEvents
[docs] class Noise(BaseModel, extra=Extra.forbid):
"""Settings for the ephys generator noise."""
beta: float
standard_deviation: float
fmin: float
samples: int
waveforms: Waveforms
input: Input
output: Output
noise: Noise
resolution: float
random_seed: Optional[int]
raw_data_frequency: float
n_units_per_channel: int
refractory_time: float
lsl_chunk_frequency: float
@validator("input")
def _input_lsl_type_must_have_a_lsl_object(cls, input_value):
if input_value.type == EphysGeneratorEndpointType.LSL and not input_value.lsl:
raise ValueError("LSL type must have a lsl object")
return input_value
@validator("input")
def _input_testing_type_must_have_a_testing_object(cls, input_value):
if (
input_value.type == EphysGeneratorEndpointType.TESTING
and not input_value.testing
):
raise ValueError("Testing type must have a testing object")
return input_value
[docs]class Settings(VersionedYamlModel):
"""All settings for the NDS main package."""
log_level: LogLevel
timer: TimerModel
encoder: EncoderSettings
ephys_generator: EphysGeneratorSettings
[docs] class Config:
"""Pydantic configuration."""
extra = Extra.forbid