diff --git a/bluecellulab/circuit/circuit_access.py b/bluecellulab/circuit/circuit_access.py index 2684a3b3..59ecb268 100644 --- a/bluecellulab/circuit/circuit_access.py +++ b/bluecellulab/circuit/circuit_access.py @@ -36,7 +36,6 @@ from bluepysnap.exceptions import BluepySnapError from bluepysnap import Circuit as SnapCircuit import pandas as pd -from pydantic import Extra from pydantic.dataclasses import dataclass from bluecellulab import circuit, neuron @@ -54,7 +53,7 @@ logger = logging.getLogger(__name__) -@dataclass(config=dict(extra=Extra.forbid)) +@dataclass(config=dict(extra="forbid")) class EmodelProperties: threshold_current: float holding_current: float diff --git a/bluecellulab/circuit/config/sections.py b/bluecellulab/circuit/config/sections.py index e236cc6f..4ceb8dd9 100644 --- a/bluecellulab/circuit/config/sections.py +++ b/bluecellulab/circuit/config/sections.py @@ -14,10 +14,9 @@ """Classes to represent config sections.""" from __future__ import annotations -from typing import Optional +from typing import Literal, Optional -from pydantic import Extra, Field, validator -from pydantic.typing import Literal +from pydantic import field_validator, Field from pydantic.dataclasses import dataclass from libsonata._libsonata import Conditions as LibSonataConditions @@ -34,14 +33,14 @@ def string_to_bool(value: str) -> bool: raise ValueError(f"Invalid boolean value {value}") -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class ConditionEntry: """For mechanism specific conditions.""" minis_single_vesicle: Optional[int] = Field(None, ge=0, le=1) init_depleted: Optional[int] = Field(None, ge=0, le=1) -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class MechanismConditions: """For mechanism specific conditions.""" ampanmda: Optional[ConditionEntry] = None @@ -49,7 +48,7 @@ class MechanismConditions: glusynapse: Optional[ConditionEntry] = None -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class Conditions: mech_conditions: Optional[MechanismConditions] = None celsius: Optional[float] = None @@ -125,7 +124,7 @@ def init_empty(cls) -> Conditions: ) -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class ConnectionOverrides: source: str target: str @@ -135,7 +134,8 @@ class ConnectionOverrides: synapse_configure: Optional[str] = None mod_override: Optional[Literal["GluSynapse"]] = None - @validator("mod_override") + @field_validator("mod_override") + @classmethod def validate_mod_override(cls, value): """Make sure the mod file to override is present.""" if isinstance(value, str) and not hasattr(bluecellulab.neuron.h, value): diff --git a/bluecellulab/stimuli.py b/bluecellulab/stimuli.py index e603ca8b..9b8c7a01 100644 --- a/bluecellulab/stimuli.py +++ b/bluecellulab/stimuli.py @@ -19,7 +19,7 @@ from typing import Optional import warnings -from pydantic import Extra, NonNegativeFloat, PositiveFloat, validator +from pydantic import field_validator, NonNegativeFloat, PositiveFloat from pydantic.dataclasses import dataclass @@ -92,7 +92,7 @@ def from_sonata(cls, pattern: str) -> Pattern: raise ValueError(f"Unknown pattern {pattern}") -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class Stimulus: target: str delay: NonNegativeFloat @@ -301,42 +301,43 @@ def from_sonata(cls, stimulus_entry: dict) -> Optional[Stimulus]: raise ValueError(f"Unknown pattern {pattern}") -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class Noise(Stimulus): mean_percent: float variance: float -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class Hyperpolarizing(Stimulus): ... -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class Pulse(Stimulus): amp_start: float width: float frequency: float -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class RelativeLinear(Stimulus): percent_start: float -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class SynapseReplay(Stimulus): spike_file: str source: str - @validator("spike_file") + @field_validator("spike_file") + @classmethod def spike_file_exists(cls, v): if not Path(v).exists(): raise ValueError(f"spike_file {v} does not exist") return v -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class ShotNoise(Stimulus): rise_time: float decay_time: float @@ -348,14 +349,15 @@ class ShotNoise(Stimulus): mode: ClampMode = ClampMode.CURRENT reversal: float = 0.0 - @validator("decay_time") + @field_validator("decay_time") + @classmethod def decay_time_gt_rise_time(cls, v, values): - if v <= values["rise_time"]: + if v <= values.data["rise_time"]: raise ValueError("decay_time must be greater than rise_time") return v -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class RelativeShotNoise(Stimulus): rise_time: float decay_time: float @@ -367,14 +369,15 @@ class RelativeShotNoise(Stimulus): mode: ClampMode = ClampMode.CURRENT reversal: float = 0.0 - @validator("decay_time") + @field_validator("decay_time") + @classmethod def decay_time_gt_rise_time(cls, v, values): - if v <= values["rise_time"]: + if v <= values.data["rise_time"]: raise ValueError("decay_time must be greater than rise_time") return v -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class OrnsteinUhlenbeck(Stimulus): tau: float sigma: PositiveFloat @@ -384,9 +387,10 @@ class OrnsteinUhlenbeck(Stimulus): mode: ClampMode = ClampMode.CURRENT reversal: float = 0.0 - @validator("mean") + @field_validator("mean") + @classmethod def mean_in_range(cls, v, values): - if v < 0 and abs(v) > 2 * values["sigma"]: + if v < 0 and abs(v) > 2 * values.data["sigma"]: warnings.warn( "mean is outside of range [0, 2*sigma],", " ornstein uhlenbeck signal is mostly zero.", @@ -394,7 +398,7 @@ def mean_in_range(cls, v, values): return v -@dataclass(frozen=True, config=dict(extra=Extra.forbid)) +@dataclass(frozen=True, config=dict(extra="forbid")) class RelativeOrnsteinUhlenbeck(Stimulus): tau: float mean_percent: float diff --git a/setup.py b/setup.py index ec1b23b7..3586d6b8 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ "matplotlib>=3.0.0,<4.0.0", "pandas>=1.0.0,<3.0.0", "bluepysnap>=1.0.5,<3.0.0", - "pydantic>=1.10.2,<2.0.0", + "pydantic>=2.5.2,<3.0.0", "typing-extensions>=4.8.0" ], keywords=[ diff --git a/tests/test_cell/test_injector.py b/tests/test_cell/test_injector.py index 596a52dd..63da23aa 100644 --- a/tests/test_cell/test_injector.py +++ b/tests/test_cell/test_injector.py @@ -56,7 +56,7 @@ def test_inject_pulse(self): assert tstim.stim.to_python() == [0.0, 4.0, 4.0, 0.0, 0.0] assert tstim.tvec.to_python() == [2.0, 2.0, 4.0, 4.0, 22.0] - with raises(TypeError): + with raises(ValidationError): unsupported_stimulus = Pulse( target="single-cell", delay=2,