Skip to content

Date

sereto.models.date

Date

Bases: SeretoBaseModel

Model representing a date with its associated event.

Attributes:

Name Type Description
type DateType

Type of the event.

date DateRange | SeretoDate

Date or date range.

Source code in sereto/models/date.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class Date(SeretoBaseModel):
    """Model representing a date with its associated event.

    Attributes:
        type (DateType): Type of the event.
        date (DateRange | SeretoDate): Date or date range.
    """

    type: DateType
    date: DateRange | SeretoDate

    @model_validator(mode="after")
    def range_allowed(self) -> Date:
        if isinstance(self.date, DateRange) and self.type not in TYPES_WITH_ALLOWED_RANGE:
            raise ValueError(f"type {self.type} does not have allowed date range, only single date")
        return self

    def __str__(self) -> str:
        match self.date:
            case SeretoDate():
                return str(self.date)
            case DateRange():
                return f"{self.date.start} to {self.date.end}"

    def __hash__(self) -> int:
        return hash((self.type, self.date))

    @field_serializer("date")
    def _serialize_date(self, value: SeretoDate | DateRange) -> str | dict[str, str]:
        if isinstance(value, DateRange):
            return {"start": str(value.start), "end": str(value.end)}
        return str(value)

DateRange

Bases: SeretoBaseModel

Model representing a period of time with start and end date.

start cannot be equal to end. In that case you should use SeretoDate.

Attributes:

Name Type Description
start SeretoDate

Start date of the period.

end SeretoDate

End date of the period.

Source code in sereto/models/date.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
class DateRange(SeretoBaseModel):
    """Model representing a period of time with start and end date.

    `start` cannot be equal to `end`. In that case you should use `SeretoDate`.

    Attributes:
        start (SeretoDate): Start date of the period.
        end (SeretoDate): End date of the period.
    """

    start: SeretoDate
    end: SeretoDate

    @model_validator(mode="after")
    def chronological_order(self) -> DateRange:
        if self.start >= self.end:
            raise ValueError("DateRange type forbids start after or equal to end")
        return self

    def __hash__(self) -> int:
        return hash((self.start, self.end))

DateType

Bases: StrEnum

Enum representing the event type for date.

Source code in sereto/models/date.py
104
105
106
107
108
109
110
class DateType(StrEnum):
    """Enum representing the event type for date."""

    sow_sent = "sow_sent"
    pentest_ongoing = "pentest_ongoing"
    review = "review"
    report_sent = "report_sent"

SeretoDate

Bases: date

Date subclass with format %d-%b-%Y (e.g., "01-Jan-2024").

This is a datetime.date subclass with: - Custom __new__ that accepts strings in %d-%b-%Y format - Custom __str__ that formats as %d-%b-%Y - All standard date operations (comparison, arithmetic, hashing) work natively

The format string specifies
  • %d: Day of the month as a zero-padded decimal number (e.g. 01, 02, ..., 31).
  • %b: Month abbreviation in the current locale's abbreviated name (e.g. Jan, Feb, ..., Dec).
  • %Y: Year with century as a decimal number (e.g. 2021, 2022, ...).
Source code in sereto/models/date.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class SeretoDate(date):
    """Date subclass with format `%d-%b-%Y` (e.g., "01-Jan-2024").

    This is a `datetime.date` subclass with:
     - Custom `__new__` that accepts strings in `%d-%b-%Y` format
     - Custom `__str__` that formats as `%d-%b-%Y`
     - All standard date operations (comparison, arithmetic, hashing) work natively

    The format string specifies:
     - `%d`: Day of the month as a zero-padded decimal number (e.g. 01, 02, ..., 31).
     - `%b`: Month abbreviation in the current locale's abbreviated name (e.g. Jan, Feb, ..., Dec).
     - `%Y`: Year with century as a decimal number (e.g. 2021, 2022, ...).
    """

    def __new__(cls, value: Any = None, month: int | None = None, day: int | None = None) -> SeretoDate:
        """Create a SeretoDate from a string, date, or year/month/day components.

        Args:
            value: Either a string in `%d-%b-%Y` format, a date object, a SeretoDate, or year (int).
            month: Month (1-12), required when value is year.
            day: Day (1-31), required when value is year.

        Returns:
            A new SeretoDate instance.

        Raises:
            ValueError: If the string format is invalid or type is unsupported.
        """
        # Handle (year, month, day) calling convention used by date.today(), date.fromtimestamp(), etc.
        if isinstance(value, int) and month is not None and day is not None:
            return super().__new__(cls, value, month, day)

        match value:
            case SeretoDate():
                return value
            case date():
                return super().__new__(cls, value.year, value.month, value.day)
            case str():
                d = datetime.strptime(value, SERETO_DATE_FORMAT).date()
                return super().__new__(cls, d.year, d.month, d.day)
            case _:
                raise ValueError(f"invalid type for SeretoDate: {type(value).__name__}")

    def __str__(self) -> str:
        return self.strftime(SERETO_DATE_FORMAT)

    def __repr__(self) -> str:
        return f"SeretoDate('{self!s}')"

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: Callable[[Any], core_schema.CoreSchema],
    ) -> core_schema.CoreSchema:
        def validate(value: Any) -> SeretoDate:
            if not isinstance(value, str | date):
                raise ValueError(f"invalid type for SeretoDate: {type(value)}")
            return cls(value)

        def serialize(value: SeretoDate) -> str:
            return str(value)

        return core_schema.json_or_python_schema(
            json_schema=core_schema.chain_schema(
                [
                    core_schema.str_schema(),
                    core_schema.no_info_plain_validator_function(validate),
                ]
            ),
            python_schema=core_schema.union_schema(
                [
                    core_schema.is_instance_schema(cls),
                    core_schema.no_info_plain_validator_function(validate),
                ]
            ),
            serialization=core_schema.plain_serializer_function_ser_schema(serialize),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        return handler(core_schema.str_schema())

__new__(value=None, month=None, day=None)

Create a SeretoDate from a string, date, or year/month/day components.

Parameters:

Name Type Description Default
value Any

Either a string in %d-%b-%Y format, a date object, a SeretoDate, or year (int).

None
month int | None

Month (1-12), required when value is year.

None
day int | None

Day (1-31), required when value is year.

None

Returns:

Type Description
SeretoDate

A new SeretoDate instance.

Raises:

Type Description
ValueError

If the string format is invalid or type is unsupported.

Source code in sereto/models/date.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def __new__(cls, value: Any = None, month: int | None = None, day: int | None = None) -> SeretoDate:
    """Create a SeretoDate from a string, date, or year/month/day components.

    Args:
        value: Either a string in `%d-%b-%Y` format, a date object, a SeretoDate, or year (int).
        month: Month (1-12), required when value is year.
        day: Day (1-31), required when value is year.

    Returns:
        A new SeretoDate instance.

    Raises:
        ValueError: If the string format is invalid or type is unsupported.
    """
    # Handle (year, month, day) calling convention used by date.today(), date.fromtimestamp(), etc.
    if isinstance(value, int) and month is not None and day is not None:
        return super().__new__(cls, value, month, day)

    match value:
        case SeretoDate():
            return value
        case date():
            return super().__new__(cls, value.year, value.month, value.day)
        case str():
            d = datetime.strptime(value, SERETO_DATE_FORMAT).date()
            return super().__new__(cls, d.year, d.month, d.day)
        case _:
            raise ValueError(f"invalid type for SeretoDate: {type(value).__name__}")