|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import typing |
| 4 | +from collections import Counter |
4 | 5 | from datetime import UTC, date, datetime |
5 | 6 | from enum import StrEnum |
6 | 7 | from functools import cached_property |
7 | 8 | from operator import attrgetter |
8 | 9 | from typing import Literal, NewType |
9 | 10 |
|
10 | | -from pydantic import BaseModel, Field, field_serializer, field_validator |
| 11 | +from pydantic import BaseModel, Field, field_serializer, field_validator, model_validator |
11 | 12 |
|
12 | 13 | if typing.TYPE_CHECKING: # pragma: no cover |
13 | 14 | from pydantic import SerializationInfo |
@@ -162,6 +163,15 @@ def parse_dates(cls, v: str | date) -> date: |
162 | 163 | def serialize_dates(v: date, _info: SerializationInfo) -> str: |
163 | 164 | return v.strftime("%Y%m%d") |
164 | 165 |
|
| 166 | + @model_validator(mode="after") |
| 167 | + def check_no_overlapping_iterations(self) -> typing.Self: |
| 168 | + iterations_by_date = Counter([i.iteration_date for i in self.iterations]) |
| 169 | + if multiple_found := next(((d, c) for d, c in iterations_by_date.most_common() if c > 1), None): |
| 170 | + iteration_date, count = multiple_found |
| 171 | + message = f"{count} iterations with iteration date {iteration_date} in campaign {self.id}" |
| 172 | + raise ValueError(message) |
| 173 | + return self |
| 174 | + |
165 | 175 | @cached_property |
166 | 176 | def campaign_live(self) -> bool: |
167 | 177 | today = datetime.now(tz=UTC).date() |
|
0 commit comments