-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathmin_max_datetime.py
More file actions
127 lines (112 loc) · 5.98 KB
/
min_max_datetime.py
File metadata and controls
127 lines (112 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#
import datetime as dt
from dataclasses import InitVar, dataclass, field
from typing import Any, Mapping, Optional, Union
from airbyte_cdk.sources.declarative.datetime.datetime_parser import DatetimeParser
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
from airbyte_cdk.utils.datetime_helpers import ab_datetime_try_parse
@dataclass
class MinMaxDatetime:
"""
Compares the provided date against optional minimum or maximum times. If date is earlier than
min_date, then min_date is returned. If date is greater than max_date, then max_date is returned.
If neither, the input date is returned.
The timestamp format accepts the same format codes as datetime.strfptime, which are
all the format codes required by the 1989 C standard.
Full list of accepted format codes: https://man7.org/linux/man-pages/man3/strftime.3.html
Attributes:
datetime (Union[InterpolatedString, str]): InterpolatedString or string representing the datetime in the format specified by `datetime_format`
datetime_format (str): Format of the datetime passed as argument
min_datetime (Union[InterpolatedString, str]): Represents the minimum allowed datetime value.
max_datetime (Union[InterpolatedString, str]): Represents the maximum allowed datetime value.
"""
datetime: Union[InterpolatedString, str]
parameters: InitVar[Mapping[str, Any]]
# datetime_format is a unique case where we inherit it from the parent if it is not specified before using the default value
# which is why we need dedicated getter/setter methods and private dataclass field
datetime_format: str
_datetime_format: str = field(init=False, repr=False, default="")
min_datetime: Union[InterpolatedString, str] = ""
max_datetime: Union[InterpolatedString, str] = ""
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
self.datetime = InterpolatedString.create(self.datetime, parameters=parameters or {})
self._parser = DatetimeParser()
self.min_datetime = (
InterpolatedString.create(self.min_datetime, parameters=parameters) # type: ignore [assignment] # expression has type "InterpolatedString | None", variable has type "InterpolatedString | str"
if self.min_datetime
else None
) # type: ignore
self.max_datetime = (
InterpolatedString.create(self.max_datetime, parameters=parameters) # type: ignore [assignment] # expression has type "InterpolatedString | None", variable has type "InterpolatedString | str"
if self.max_datetime
else None
) # type: ignore
def get_datetime(
self, config: Mapping[str, Any], **additional_parameters: Mapping[str, Any]
) -> dt.datetime:
"""
Evaluates and returns the datetime
:param config: The user-provided configuration as specified by the source's spec
:param additional_parameters: Additional arguments to be passed to the strings for interpolation
:return: The evaluated datetime
"""
# We apply a default datetime format here instead of at instantiation, so it can be set by the parent first
datetime_format = self._datetime_format
if not datetime_format:
datetime_format = "%Y-%m-%dT%H:%M:%S.%f%z"
datetime_str = str(
self.datetime.eval( # type: ignore[union-attr] # str has no attribute "eval"
config,
**additional_parameters,
)
)
try:
time = self._parser.parse(datetime_str, datetime_format)
except ValueError:
parsed_dt = ab_datetime_try_parse(datetime_str)
if parsed_dt is not None:
time = parsed_dt
else:
raise ValueError(
f"Unable to parse datetime '{datetime_str}' with format '{datetime_format}' or robust parsing"
)
if self.min_datetime:
min_time = str(self.min_datetime.eval(config, **additional_parameters)) # type: ignore # min_datetime is always cast to an interpolated string
if min_time:
min_datetime = self._parser.parse(min_time, datetime_format) # type: ignore # min_datetime is always cast to an interpolated string
time = max(time, min_datetime)
if self.max_datetime:
max_time = str(self.max_datetime.eval(config, **additional_parameters)) # type: ignore # max_datetime is always cast to an interpolated string
if max_time:
max_datetime = self._parser.parse(max_time, datetime_format)
time = min(time, max_datetime)
return time
@property # type: ignore # properties don't play well with dataclasses...
def datetime_format(self) -> str:
"""The format of the string representing the datetime"""
return self._datetime_format
@datetime_format.setter
def datetime_format(self, value: str) -> None:
"""Setter for the datetime format"""
# Covers the case where datetime_format is not provided in the constructor, which causes the property object
# to be set which we need to avoid doing
if not isinstance(value, property):
self._datetime_format = value
@classmethod
def create(
cls,
interpolated_string_or_min_max_datetime: Union[InterpolatedString, str, "MinMaxDatetime"],
parameters: Optional[Mapping[str, Any]] = None,
) -> "MinMaxDatetime":
if parameters is None:
parameters = {}
if isinstance(interpolated_string_or_min_max_datetime, InterpolatedString) or isinstance(
interpolated_string_or_min_max_datetime, str
):
return MinMaxDatetime( # type: ignore [call-arg]
datetime=interpolated_string_or_min_max_datetime, parameters=parameters
)
else:
return interpolated_string_or_min_max_datetime