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
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))

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__}")