Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
* Do not log the `fetching` log lines for a workflow with 0 runs. The result would be logging the last non-zero count.
* The current only workflows_lib test actually tests request_branches not workflows.
* Fix an issue with datetime parsing when verify_only is active. Also add a test.
  • Loading branch information
helly25 committed Sep 2, 2024
1 parent d0b100d commit 6d8077e
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 30 deletions.
2 changes: 2 additions & 0 deletions circleci/workflows_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ def Main(self) -> None:
)
if self.args.fetch_workflow_details:
Log(f"Fetching {len(runs)} workflow run details for '{workflow}'.")
if not runs:
continue
run_count += len(runs)
for run_index, run in enumerate(runs, 1):
run["workflow"] = workflow
Expand Down
9 changes: 5 additions & 4 deletions circleci/workflows_lib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,26 @@ def test_CommandList(self):
set(Command._commands.keys()).issuperset(
{
"combine",
"fetch_details",
"fetch",
"fetch_details",
"filter",
"help",
"request_branches",
"request_workflow",
"request_workflows",
}
)
)

def test_Workflows(self):
def test_RequestBranches(self):
"""Lower level mocking to prove the pieces work together.
Here we bypass the CircleCiApiV2 almost completey as we mock the highest level function we
call there. That function `RequestBranches` will be mocked, by injection, through mocking
the `CircleCiCommand._InitCircleCiClient`.
Now we can call the `branches` sub-command normally by providing an appropriate argv to
`Command.Run`. The mocked return_value for the `RequestBranches` will be our result.
Now we can call the `request_branches` sub-command normally by providing an appropriate argv
to `Command.Run`. The mocked return_value for the `RequestBranches` will be our result.
"""
with open(os.devnull, "w") as err:
with redirect_stderr(err):
Expand Down
61 changes: 35 additions & 26 deletions mbo/app/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,15 @@ class ActionDateTimeOrTimeDelta(argparse.Action):
If `value` starts with either `-` or `+`, then it will be parsed as `timedelta`.
Otherwise `value` will be parsed as Python [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
The resulting value is either a `datetime` or a `str` value if `verify_only==True`.
This action has the additional config:
* default: The value to use if `value` is empty (defaults to `datetime.now`).
This must be `None` or of type `datetime`, `str`.
* midnight: Whether to adjust the date/time to midnight of the day.
* reference: The reference datetime to use for time deltas (defaults to `datetime.now`).
* tz: Fallback timezone.
This must be `None` or of type `datetime`, `str`.
* tz: Fallback timezone (defaults to `timezone.utc`).
* verify_only: If True (False is default), then the input will only be verified. The resulting
value is the input and its type is `str`. The sole purpose is to verify an input
but process it later, which allows one flag to provide its value as a reference
Expand All @@ -268,28 +272,6 @@ class ActionDateTimeOrTimeDelta(argparse.Action):
```
"""

def _ParseDateTimeStrict(
self,
name: str,
value: Any,
midnight: bool = False,
tz: tzinfo = timezone.utc,
) -> datetime | None:
if value is None or value == "":
return None
elif not isinstance(value, str) and not isinstance(value, datetime):
raise argparse.ArgumentError(
self,
f"{name.capitalize()} value must be None or of type `datetime` or `str`, provided is `{type(value)}`.",
)
try:
return _ParseDateTime(value, midnight=self._midnight, tz=self._tz)
except ValueError as error:
raise argparse.ArgumentError(
self,
f"{name.capitalize()} value `{value}` cannot be parsed as `datetime`.",
)

def __init__(self, **kwargs) -> None:
self._verify_only = kwargs.pop("verify_only", False)
self._midnight = kwargs.pop("midnight", False)
Expand All @@ -311,7 +293,8 @@ def __init__(self, **kwargs) -> None:
raise argparse.ArgumentError(
self, f"Type must be `datetime`, provided type is `{self._type}`."
)
self.default: datetime = (
# Property `_default_dt` (DateTime) is required for further parsing.
self._default_dt: datetime = (
self._ParseDateTimeStrict(
name="default",
value=default_v,
Expand All @@ -320,6 +303,10 @@ def __init__(self, **kwargs) -> None:
)
or now
)
# The actual default must be set to a string value for `verify_only`.
self.default: datetime | str = (
str(self._default_dt) if self._verify_only else self._default_dt
)
self._reference: datetime = (
self._ParseDateTimeStrict(
name="reference",
Expand All @@ -330,11 +317,33 @@ def __init__(self, **kwargs) -> None:
or now
)

def _parse(self, value) -> datetime:
def _ParseDateTimeStrict(
self,
name: str,
value: Any,
midnight: bool = False,
tz: tzinfo = timezone.utc,
) -> datetime | None:
if value is None or value == "":
return None
elif not isinstance(value, str) and not isinstance(value, datetime):
raise argparse.ArgumentError(
self,
f"{name.capitalize()} value must be None or of type `datetime` or `str`, provided is `{type(value)}`.",
)
try:
return _ParseDateTime(value, midnight=self._midnight, tz=self._tz)
except ValueError as error:
raise argparse.ArgumentError(
self,
f"{name.capitalize()} value `{value}` cannot be parsed as `datetime`.",
)

def _parse(self, value: str) -> datetime:
try:
return ParseDateTimeOrTimeDelta(
value=value,
default=self.default,
default=self._default_dt,
midnight=self._midnight,
reference=self._reference,
tz=self._tz,
Expand Down
12 changes: 12 additions & 0 deletions mbo/app/flags_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def FlagTest(self, test: FlagTestData) -> None:
self.assertEqual(
test.expected, args.flag, "Bad value in test: " + test.test
)
self.assertIsInstance(args.flag, type(test.expected))
except argparse.ArgumentError as error:
self.assertIsNotNone(test.expected_error, error)
if test.expected_error:
Expand Down Expand Up @@ -452,6 +453,17 @@ def test_EnumListAction(self, test: FlagTestData):
),
input=[],
),
FlagTestData(
test="Non present flag with default.",
expected=str(_NOW_DATETIME),
expected_error=argparse.ArgumentError,
action=ActionArgs(
"--flag",
action=mbo.app.flags.ActionDateTimeOrTimeDelta,
verify_only=True,
),
input=[],
),
FlagTestData(
test="Parse from short datetime, bad input.",
expected="argument flag: Invalid date string: '20240230', day is out of range for month",
Expand Down

0 comments on commit 6d8077e

Please sign in to comment.