Skip to content

Commit 8b0e14e

Browse files
committed
chore: add more doc strings
1 parent beec3ad commit 8b0e14e

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

aws_lambda_powertools/utilities/feature_flags/schema.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
CONDITION_VALUE = "value"
1919
CONDITION_ACTION = "action"
2020
FEATURE_DEFAULT_VAL_TYPE_KEY = "boolean_type"
21-
TIME_RANGE_FORMAT = "%H:%M"
22-
TIME_RANGE_RE_PATTERN = re.compile(r"2[0-3]:[0-5]\d|[0-1]\d:[0-5]\d")
21+
TIME_RANGE_FORMAT = "%H:%M" # hour:min 24 hours clock
22+
TIME_RANGE_RE_PATTERN = re.compile(r"2[0-3]:[0-5]\d|[0-1]\d:[0-5]\d") # 24 hour clock
2323
HOUR_MIN_SEPARATOR = ":"
2424

2525

@@ -38,18 +38,26 @@ class RuleAction(Enum):
3838
KEY_NOT_IN_VALUE = "KEY_NOT_IN_VALUE"
3939
VALUE_IN_KEY = "VALUE_IN_KEY"
4040
VALUE_NOT_IN_KEY = "VALUE_NOT_IN_KEY"
41-
SCHEDULE_BETWEEN_TIME_RANGE = "SCHEDULE_BETWEEN_TIME_RANGE" # hour:min 24 hours clock UTC time
42-
SCHEDULE_BETWEEN_DATETIME_RANGE = "SCHEDULE_BETWEEN_DATETIME_RANGE" # full datetime format
43-
SCHEDULE_BETWEEN_DAYS_OF_WEEK = "SCHEDULE_BETWEEN_DAYS_OF_WEEK"
41+
SCHEDULE_BETWEEN_TIME_RANGE = "SCHEDULE_BETWEEN_TIME_RANGE" # hour:min 24 hours clock
42+
SCHEDULE_BETWEEN_DATETIME_RANGE = "SCHEDULE_BETWEEN_DATETIME_RANGE" # full datetime format, excluding timezone
43+
SCHEDULE_BETWEEN_DAYS_OF_WEEK = "SCHEDULE_BETWEEN_DAYS_OF_WEEK" # MONDAY, TUESDAY, .... see TimeValues enum
4444

4545

4646
class TimeKeys(Enum):
47+
"""
48+
Possible keys when using time rules
49+
"""
50+
4751
CURRENT_TIME = "CURRENT_TIME"
4852
CURRENT_DAY_OF_WEEK = "CURRENT_DAY_OF_WEEK"
4953
CURRENT_DATETIME = "CURRENT_DATETIME"
5054

5155

5256
class TimeValues(Enum):
57+
"""
58+
Possible values when using time rules
59+
"""
60+
5361
START = "START"
5462
END = "END"
5563
TIMEZONE = "TIMEZONE"
@@ -294,6 +302,11 @@ def validate_condition_key(condition: Dict[str, Any], rule_name: str):
294302
key = condition.get(CONDITION_KEY, "")
295303
if not key or not isinstance(key, str):
296304
raise SchemaValidationError(f"'key' value must be a non empty string, rule={rule_name}")
305+
306+
# time actions need to have very specific keys
307+
# SCHEDULE_BETWEEN_TIME_RANGE => CURRENT_TIME
308+
# SCHEDULE_BETWEEN_DATETIME_RANGE => CURRENT_DATETIME
309+
# SCHEDULE_BETWEEN_DAYS_OF_WEEK => CURRENT_DAY_OF_WEEK
297310
action = condition.get(CONDITION_ACTION, "")
298311
if action == RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value and key != TimeKeys.CURRENT_TIME.value:
299312
raise SchemaValidationError(
@@ -314,7 +327,8 @@ def validate_condition_value(condition: Dict[str, Any], rule_name: str):
314327
if not value:
315328
raise SchemaValidationError(f"'value' key must not be empty, rule={rule_name}")
316329
action = condition.get(CONDITION_ACTION, "")
317-
# time actions
330+
331+
# time actions need to be parsed to make sure date and time format is valid and timezone is recognized
318332
if action == RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value:
319333
ConditionsValidator._validate_schedule_between_time_and_datetime_ranges(
320334
value, rule_name, action, ConditionsValidator._validate_time_value
@@ -330,9 +344,14 @@ def validate_condition_value(condition: Dict[str, Any], rule_name: str):
330344
def _validate_datetime_value(datetime_str: str, rule_name: str):
331345
date = None
332346

347+
# We try to parse first with timezone information in order to return the correct error messages
348+
# when a timestamp with timezone is used. Otherwise, the user would get the first error "must be a valid
349+
# ISO8601 time format" which is misleading
350+
333351
try:
334352
# python < 3.11 don't support the Z timezone on datetime.fromisoformat,
335353
# so we replace any Z with the equivalent "+00:00"
354+
# datetime.fromisoformat is orders of magnitude faster than datetime.strptime
336355
date = datetime.fromisoformat(datetime_str.replace("Z", "+00:00"))
337356
except Exception:
338357
raise SchemaValidationError(f"'START' and 'END' must be a valid ISO8601 time format, rule={rule_name}")
@@ -383,6 +402,8 @@ def _validate_schedule_between_days_of_week(value: Any, rule_name: str):
383402
timezone = value.get(TimeValues.TIMEZONE.value, "UTC")
384403
if not isinstance(timezone, str):
385404
raise SchemaValidationError(error_str)
405+
406+
# try to see if the timezone string corresponds to any known timezone
386407
if not tz.gettz(timezone):
387408
raise SchemaValidationError(f"'TIMEZONE' value must represent a valid IANA timezone, rule={rule_name}")
388409

@@ -393,17 +414,21 @@ def _validate_schedule_between_time_and_datetime_ranges(
393414
error_str = f"condition with a '{action_name}' action must have a condition value type dictionary with 'START' and 'END' keys, rule={rule_name}" # noqa: E501
394415
if not isinstance(value, dict):
395416
raise SchemaValidationError(error_str)
417+
396418
start_time = value.get(TimeValues.START.value)
397419
end_time = value.get(TimeValues.END.value)
398420
if not start_time or not end_time:
399421
raise SchemaValidationError(error_str)
400422
if not isinstance(start_time, str) or not isinstance(end_time, str):
401423
raise SchemaValidationError(f"'START' and 'END' must be a non empty string, rule={rule_name}")
424+
402425
validator(start_time, rule_name)
403426
validator(end_time, rule_name)
404427

405428
timezone = value.get(TimeValues.TIMEZONE.value, "UTC")
406429
if not isinstance(timezone, str):
407430
raise SchemaValidationError(f"'TIMEZONE' must be a string, rule={rule_name}")
431+
432+
# try to see if the timezone string corresponds to any known timezone
408433
if not tz.gettz(timezone):
409434
raise SchemaValidationError(f"'TIMEZONE' value must represent a valid IANA timezone, rule={rule_name}")

aws_lambda_powertools/utilities/feature_flags/time_conditions.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@
77

88

99
def _get_now_from_timezone(timezone: Optional[tzinfo]) -> datetime:
10+
"""
11+
Returns now in the specified timezone. Defaults to UTC if not present.
12+
At this stage, we already validated that the passed timezone string is valid, so we assume that
13+
gettz() will return a tzinfo object.
14+
"""
1015
timezone = gettz("UTC") if timezone is None else timezone
1116
return datetime.now(timezone)
1217

1318

1419
def compare_days_of_week(action: str, values: Dict) -> bool:
1520
timezone_name = values.get(TimeValues.TIMEZONE.value, "UTC")
21+
22+
# %A = Weekday as locale’s full name.
1623
current_day = _get_now_from_timezone(gettz(timezone_name)).strftime("%A").upper()
1724

1825
days = values.get(TimeValues.DAYS.value, [])
@@ -27,9 +34,9 @@ def compare_datetime_range(action: str, values: Dict) -> bool:
2734
start_date_str = values.get(TimeValues.START.value, "")
2835
end_date_str = values.get(TimeValues.END.value, "")
2936

30-
# Since start_date and end_date don't include timezone information, we mark the timestamp
37+
# Since start_date and end_date doesn't include timezone information, we mark the timestamp
3138
# with the same timezone as the current_time. This way all the 3 timestamps will be on
32-
# the same timezone
39+
# the same timezone.
3340
start_date = datetime.fromisoformat(start_date_str).replace(tzinfo=timezone)
3441
end_date = datetime.fromisoformat(end_date_str).replace(tzinfo=timezone)
3542
return start_date <= current_time <= end_date

0 commit comments

Comments
 (0)