Skip to content

Commit

Permalink
consolidate use of pendulum in tests/: part 1 (#17106)
Browse files Browse the repository at this point in the history
  • Loading branch information
zzstoatzz authored Feb 18, 2025
1 parent ed3c9aa commit cdf3f2b
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 126 deletions.
19 changes: 12 additions & 7 deletions tests/blocks/test_system.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import pendulum
from typing import Any

import pytest
from pydantic import Secret as PydanticSecret
from pydantic import SecretStr

from prefect.blocks.system import DateTime, Secret
from prefect.types import DateTime as PydanticDateTime
from prefect.types._datetime import DateTime as PydanticDateTime
from prefect.types._datetime import Timezone


def test_datetime(ignore_prefect_deprecation_warnings):
DateTime(value=PydanticDateTime(2022, 1, 1)).save(name="test")
@pytest.mark.usefixtures("ignore_prefect_deprecation_warnings")
def test_datetime():
DateTime(value=PydanticDateTime(2022, 1, 1, tzinfo=Timezone("UTC"))).save(
name="test"
)
api_block = DateTime.load("test")
assert api_block.value == pendulum.datetime(2022, 1, 1)
assert api_block.value == PydanticDateTime(2022, 1, 1, tzinfo=Timezone("UTC"))


@pytest.mark.parametrize(
"value",
["test", {"key": "value"}, ["test"]],
ids=["string", "dict", "list"],
)
def test_secret_block(value):
def test_secret_block(value: Any):
Secret(value=value).save(name="test")
api_block = Secret.load("test")
assert isinstance(api_block.value, PydanticSecret)
Expand All @@ -35,7 +40,7 @@ def test_secret_block(value):
],
ids=["secret_string", "secret_dict", "secret_list"],
)
def test_secret_block_with_pydantic_secret(value):
def test_secret_block_with_pydantic_secret(value: Any):
Secret(value=value).save(name="test")
api_block = Secret.load("test")
assert isinstance(api_block.value, PydanticSecret)
Expand Down
44 changes: 20 additions & 24 deletions tests/cli/deployment/test_deployment_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
from typing import Any, Generator
from unittest.mock import ANY, AsyncMock

import pendulum
import pytest
from pendulum.duration import Duration

import prefect
from prefect.client.schemas.objects import Deployment, FlowRun
from prefect.exceptions import FlowRunWaitTimeout
from prefect.states import Completed, Failed
from prefect.testing.cli import invoke_and_assert
from prefect.types._datetime import DateTime, local_timezone
from prefect.types._datetime import DateTime, Duration, local_timezone, parse_datetime
from prefect.utilities.asyncutils import run_sync_in_worker_thread


Expand Down Expand Up @@ -134,11 +132,11 @@ async def test_start_at_option_displays_scheduled_start_time(
@pytest.mark.parametrize(
"start_at,expected_start_time",
[
("5-17-43 5:30pm UTC", pendulum.parse("2043-05-17T17:30:00")),
("5-20-2020 5:30pm EDT", pendulum.parse("2020-05-20T17:30:00", tz="EST5EDT")),
("01/31/43 5:30 CST", pendulum.parse("2043-01-31T05:30:00", tz="CST6CDT")),
("5-20-43 5:30pm PDT", pendulum.parse("2043-05-20T17:30:00", tz="PST8PDT")),
("01/31/43 5:30 PST", pendulum.parse("2043-01-31T05:30:00", tz="PST8PDT")),
("5-17-43 5:30pm UTC", parse_datetime("2043-05-17T17:30:00")),
("5-20-2020 5:30pm EDT", parse_datetime("2020-05-20T17:30:00", tz="EST5EDT")),
("01/31/43 5:30 CST", parse_datetime("2043-01-31T05:30:00", tz="CST6CDT")),
("5-20-43 5:30pm PDT", parse_datetime("2043-05-20T17:30:00", tz="PST8PDT")),
("01/31/43 5:30 PST", parse_datetime("2043-01-31T05:30:00", tz="PST8PDT")),
],
)
async def test_start_at_option_with_tz_displays_scheduled_start_time(
Expand Down Expand Up @@ -214,11 +212,11 @@ async def test_start_at_option_schedules_flow_run(
@pytest.mark.parametrize(
"start_at,expected_start_time",
[
("5-17-43 5:30pm UTC", pendulum.parse("2043-05-17T17:30:00")),
("5-20-2020 5:30pm EDT", pendulum.parse("2020-05-20T17:30:00", tz="EST5EDT")),
("01/31/43 5:30 CST", pendulum.parse("2043-01-31T05:30:00", tz="CST6CDT")),
("5-20-43 5:30pm PDT", pendulum.parse("2043-05-20T17:30:00", tz="PST8PDT")),
("01/31/43 5:30 PST", pendulum.parse("2043-01-31T05:30:00", tz="PST8PDT")),
("5-17-43 5:30pm UTC", parse_datetime("2043-05-17T17:30:00")),
("5-20-2020 5:30pm EDT", parse_datetime("2020-05-20T17:30:00", tz="EST5EDT")),
("01/31/43 5:30 CST", parse_datetime("2043-01-31T05:30:00", tz="CST6CDT")),
("5-20-43 5:30pm PDT", parse_datetime("2043-05-20T17:30:00", tz="PST8PDT")),
("01/31/43 5:30 PST", parse_datetime("2043-01-31T05:30:00", tz="PST8PDT")),
],
)
async def test_start_at_option_with_tz_schedules_flow_run(
Expand All @@ -227,7 +225,7 @@ async def test_start_at_option_with_tz_schedules_flow_run(
expected_start_time: DateTime,
prefect_client: prefect.client.orchestration.PrefectClient,
):
expected_start_time_local = expected_start_time.in_tz(pendulum.tz.local_timezone())
expected_start_time_local = expected_start_time.in_tz(local_timezone())
expected_display = (
expected_start_time_local.to_datetime_string()
+ " "
Expand Down Expand Up @@ -315,13 +313,13 @@ async def test_start_in_option_displays_scheduled_start_time(
@pytest.mark.parametrize(
"start_in,expected_duration",
[
("10 minutes", pendulum.duration(minutes=10)),
("5 days", pendulum.duration(days=5)),
("3 seconds", pendulum.duration(seconds=3)),
(None, pendulum.duration(seconds=0)),
("1 year and 3 months", pendulum.duration(years=1, months=3)),
("2 weeks & 1 day", pendulum.duration(weeks=2, days=1)),
("27 hours + 4 mins", pendulum.duration(days=1, hours=3, minutes=4)),
("10 minutes", Duration(minutes=10)),
("5 days", Duration(days=5)),
("3 seconds", Duration(seconds=3)),
(None, Duration(seconds=0)),
("1 year and 3 months", Duration(years=1, months=3)),
("2 weeks & 1 day", Duration(weeks=2, days=1)),
("27 hours + 4 mins", Duration(days=1, hours=3, minutes=4)),
],
)
async def test_start_in_option_schedules_flow_run(
Expand All @@ -332,9 +330,7 @@ async def test_start_in_option_schedules_flow_run(
expected_duration: Duration,
):
expected_start_time = frozen_now + expected_duration
expected_display = expected_start_time.in_tz(
pendulum.tz.local_timezone()
).to_datetime_string()
expected_display = expected_start_time.in_tz(local_timezone()).to_datetime_string()

await run_sync_in_worker_thread(
invoke_and_assert,
Expand Down
82 changes: 60 additions & 22 deletions tests/cli/test_artifact.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import sys
from typing import TYPE_CHECKING
from uuid import uuid4

import pendulum
import pytest
from typer import Exit

from prefect.server import models, schemas
from prefect.testing.cli import invoke_and_assert
from prefect.types._datetime import create_datetime_instance, human_friendly_diff

if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession


@pytest.fixture(autouse=True)
def interactive_console(monkeypatch):
def interactive_console(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr("prefect.cli.artifact.is_interactive", lambda: True)

# `readchar` does not like the fake stdin provided by typer isolation so we provide
Expand All @@ -29,7 +33,7 @@ def readchar():


@pytest.fixture
async def artifact(session):
async def artifact(session: "AsyncSession"):
artifact_schema = schemas.core.Artifact(
key="voltaic", data={"a": 1}, type="table", description="opens many doors"
)
Expand All @@ -45,7 +49,7 @@ async def artifact(session):


@pytest.fixture
async def artifact_null_field(session):
async def artifact_null_field(session: "AsyncSession"):
artifact_schema = schemas.core.Artifact(
key="voltaic", data=1, metadata_={"description": "opens many doors"}
)
Expand All @@ -61,7 +65,7 @@ async def artifact_null_field(session):


@pytest.fixture
async def artifacts(session):
async def artifacts(session: "AsyncSession"):
model1 = await models.artifacts.create_artifact(
session=session,
artifact=schemas.core.Artifact(
Expand Down Expand Up @@ -98,37 +102,47 @@ def test_listing_artifacts_when_none_exist():
)


def test_listing_artifacts_after_creating_artifacts(artifact):
def test_listing_artifacts_after_creating_artifacts(
artifact: models.artifacts.Artifact,
):
assert artifact.id is not None, "artifact id should not be None"
assert artifact.key is not None, "artifact key should not be None"
assert artifact.updated is not None, "artifact updated should not be None"

invoke_and_assert(
["artifact", "ls"],
expected_output_contains=f"""
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ ID ┃ Key ┃ Type ┃ Updated ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
{artifact.id}{artifact.key}{artifact.type}{pendulum.instance(artifact.updated).diff_for_humans()}
{artifact.id}{artifact.key}{artifact.type}{human_friendly_diff(create_datetime_instance(artifact.updated))}
└──────────────────────────────────────┴─────────┴───────┴───────────────────┘
""",
)


def test_listing_artifacts_after_creating_artifacts_with_null_fields(
artifact_null_field,
artifact_null_field: models.artifacts.Artifact,
):
artifact = artifact_null_field
assert artifact.id is not None, "artifact id should not be None"
assert artifact.key is not None, "artifact key should not be None"
assert artifact.updated is not None, "artifact updated should not be None"

invoke_and_assert(
["artifact", "ls"],
expected_output_contains=f"""
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ ID ┃ Key ┃ Type ┃ Updated ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
{artifact.id}{artifact.key} │ │ {pendulum.instance(artifact.updated).diff_for_humans()}
{artifact.id}{artifact.key} │ │ {human_friendly_diff(create_datetime_instance(artifact.updated))}
└──────────────────────────────────────┴─────────┴──────┴───────────────────┘
""",
)


def test_listing_artifacts_with_limit(
artifacts,
artifacts: list[models.artifacts.Artifact],
):
expected_output = artifacts[2].key
invoke_and_assert(
Expand All @@ -138,7 +152,9 @@ def test_listing_artifacts_with_limit(
)


def test_listing_artifacts_lists_only_latest_versions(artifacts):
def test_listing_artifacts_lists_only_latest_versions(
artifacts: list[models.artifacts.Artifact],
):
expected_output = (
f"{artifacts[2].id}",
f"{artifacts[1].id}",
Expand All @@ -152,7 +168,9 @@ def test_listing_artifacts_lists_only_latest_versions(artifacts):
)


def test_listing_artifacts_with_all_set_to_true(artifacts):
def test_listing_artifacts_with_all_set_to_true(
artifacts: list[models.artifacts.Artifact],
):
expected_output = (
f"{artifacts[0].id}",
f"{artifacts[1].id}",
Expand All @@ -166,7 +184,9 @@ def test_listing_artifacts_with_all_set_to_true(artifacts):
)


def test_listing_artifacts_with_all_set_to_false(artifacts):
def test_listing_artifacts_with_all_set_to_false(
artifacts: list[models.artifacts.Artifact],
):
expected_output = (
f"{artifacts[2].id}",
f"{artifacts[1].id}",
Expand All @@ -180,7 +200,9 @@ def test_listing_artifacts_with_all_set_to_false(artifacts):
)


def test_inspecting_artifact_succeeds(artifacts):
def test_inspecting_artifact_succeeds(
artifacts: list[models.artifacts.Artifact],
):
"""
We expect to see all versions of the artifact.
"""
Expand Down Expand Up @@ -213,7 +235,9 @@ def test_inspecting_artifact_nonexistent_key_raises():
)


def test_inspecting_artifact_with_limit(artifacts):
def test_inspecting_artifact_with_limit(
artifacts: list[models.artifacts.Artifact],
):
expected_output = (
f"{artifacts[1].key}",
f"{artifacts[1].data}",
Expand All @@ -226,7 +250,9 @@ def test_inspecting_artifact_with_limit(artifacts):
)


def test_deleting_artifact_by_key_succeeds(artifacts):
def test_deleting_artifact_by_key_succeeds(
artifacts: list[models.artifacts.Artifact],
):
invoke_and_assert(
["artifact", "delete", str(artifacts[0].key)],
prompts_and_responses=[
Expand All @@ -247,7 +273,9 @@ def test_deleting_artifact_nonexistent_key_raises():
)


def test_deleting_artifact_by_key_without_confimation_aborts(artifacts):
def test_deleting_artifact_by_key_without_confimation_aborts(
artifacts: list[models.artifacts.Artifact],
):
invoke_and_assert(
["artifact", "delete", str(artifacts[0].key)],
user_input="n",
Expand All @@ -256,7 +284,9 @@ def test_deleting_artifact_by_key_without_confimation_aborts(artifacts):
)


def test_deleting_artifact_by_id_succeeds(artifacts):
def test_deleting_artifact_by_id_succeeds(
artifacts: list[models.artifacts.Artifact],
):
invoke_and_assert(
["artifact", "delete", "--id", str(artifacts[0].id)],
user_input="y",
Expand All @@ -275,7 +305,9 @@ def test_deleting_artifact_nonexistent_id_raises():
)


def test_deleting_artifact_by_id_without_confimation_aborts(artifacts):
def test_deleting_artifact_by_id_without_confimation_aborts(
artifacts: list[models.artifacts.Artifact],
):
invoke_and_assert(
["artifact", "delete", "--id", str(artifacts[0].id)],
user_input="n",
Expand All @@ -284,17 +316,23 @@ def test_deleting_artifact_by_id_without_confimation_aborts(artifacts):
)


def test_deleting_artifact_with_key_and_id_raises(artifacts):
def test_deleting_artifact_with_key_and_id_raises(
artifacts: list[models.artifacts.Artifact],
):
assert artifacts[1].key is not None, "artifact key should not be None"
assert artifacts[1].id is not None, "artifact id should not be None"
invoke_and_assert(
["artifact", "delete", artifacts[1].key, "--id", artifacts[1].id],
["artifact", "delete", artifacts[1].key, "--id", str(artifacts[1].id)],
expected_output_contains=(
"Please provide either a key or an artifact_id but not both."
),
expected_code=1,
)


def test_deleting_artifact_without_key_or_id_raises(artifacts):
def test_deleting_artifact_without_key_or_id_raises(
artifacts: list[models.artifacts.Artifact],
):
invoke_and_assert(
["artifact", "delete"],
expected_output_contains="Please provide a key or an artifact_id.",
Expand Down
Loading

0 comments on commit cdf3f2b

Please sign in to comment.