Repository: PrefectHQ/prefect

Issue:
Remove `prefect deployment build` CLI from the `main` branch
Remove the `prefect.cli.deployment.build` function and any references to it. Any private code that was used solely for that function should also be deleted. This CLI command will be included in 2.x releases, but will not be included in the 3.0 release.


Discussion:

Resolving patch:
diff --git a/src/prefect/cli/deployment.py b/src/prefect/cli/deployment.py
--- a/src/prefect/cli/deployment.py
+++ b/src/prefect/cli/deployment.py
@@ -6,7 +6,7 @@
 import sys
 import textwrap
 import warnings
-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timezone
 from pathlib import Path
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
 from uuid import UUID
@@ -41,13 +41,9 @@
     exception_traceback,
 )
 from prefect.flow_runs import wait_for_flow_run
-from prefect.flows import load_flow_from_entrypoint
 from prefect.settings import PREFECT_UI_URL
 from prefect.states import Scheduled
-from prefect.utilities.asyncutils import run_sync_in_worker_thread
 from prefect.utilities.collections import listrepr
-from prefect.utilities.dispatch import get_registry_for_type
-from prefect.utilities.filesystem import create_default_ignore_file
 
 if TYPE_CHECKING:
     from prefect.client.orchestration import PrefectClient
@@ -1272,436 +1268,6 @@ async def delete(
             exit_with_error("Must provide a deployment name or id")
 
 
-builtin_infrastructure_types = [
-    slug
-    for slug, block in get_registry_for_type(Block).items()
-    if "run-infrastructure" in block.get_block_capabilities()
-]
-
-
-@deployment_app.command(
-    deprecated=True,
-    deprecated_start_date="Mar 2024",
-    deprecated_name="deployment build",
-    deprecated_help="Use 'prefect deploy' to deploy flows via YAML instead.",
-)
-async def build(
-    entrypoint: str = typer.Argument(
-        ...,
-        help=(
-            "The path to a flow entrypoint, in the form of"
-            " `./path/to/file.py:flow_func_name`"
-        ),
-    ),
-    name: str = typer.Option(
-        None, "--name", "-n", help="The name to give the deployment."
-    ),
-    description: str = typer.Option(
-        None,
-        "--description",
-        "-d",
-        help=(
-            "The description to give the deployment. If not provided, the description"
-            " will be populated from the flow's description."
-        ),
-    ),
-    version: str = typer.Option(
-        None, "--version", "-v", help="A version to give the deployment."
-    ),
-    tags: List[str] = typer.Option(
-        None,
-        "-t",
-        "--tag",
-        help=(
-            "One or more optional tags to apply to the deployment. Note: tags are used"
-            " only for organizational purposes. For delegating work to agents, use the"
-            " --work-queue flag."
-        ),
-    ),
-    work_queue_name: str = typer.Option(
-        None,
-        "-q",
-        "--work-queue",
-        help=(
-            "The work queue that will handle this deployment's runs. "
-            "It will be created if it doesn't already exist. Defaults to `None`. "
-            "Note that if a work queue is not set, work will not be scheduled."
-        ),
-    ),
-    work_pool_name: str = typer.Option(
-        None,
-        "-p",
-        "--pool",
-        help="The work pool that will handle this deployment's runs.",
-    ),
-    work_queue_concurrency: int = typer.Option(
-        None,
-        "--limit",
-        "-l",
-        help=(
-            "Sets the concurrency limit on the work queue that handles this"
-            " deployment's runs"
-        ),
-    ),
-    infra_type: str = typer.Option(
-        None,
-        "--infra",
-        "-i",
-        help="The infrastructure type to use, prepopulated with defaults. For example: "
-        + listrepr(builtin_infrastructure_types, sep=", "),
-    ),
-    infra_block: str = typer.Option(
-        None,
-        "--infra-block",
-        "-ib",
-        help="The slug of the infrastructure block to use as a template.",
-    ),
-    overrides: List[str] = typer.Option(
-        None,
-        "--override",
-        help=(
-            "One or more optional infrastructure overrides provided as a dot delimited"
-            " path, e.g., `env.env_key=env_value`"
-        ),
-    ),
-    storage_block: str = typer.Option(
-        None,
-        "--storage-block",
-        "-sb",
-        help=(
-            "The slug of a remote storage block. Use the syntax:"
-            " 'block_type/block_name', where block_type is one of 'github', 's3',"
-            " 'gcs', 'azure', 'smb', or a registered block from a library that"
-            " implements the WritableDeploymentStorage interface such as"
-            " 'gitlab-repository', 'bitbucket-repository', 's3-bucket',"
-            " 'gcs-bucket'"
-        ),
-    ),
-    skip_upload: bool = typer.Option(
-        False,
-        "--skip-upload",
-        help=(
-            "A flag that, when provided, skips uploading this deployment's files to"
-            " remote storage."
-        ),
-    ),
-    cron: str = typer.Option(
-        None,
-        "--cron",
-        help="A cron string that will be used to set a CronSchedule on the deployment.",
-    ),
-    interval: int = typer.Option(
-        None,
-        "--interval",
-        help=(
-            "An integer specifying an interval (in seconds) that will be used to set an"
-            " IntervalSchedule on the deployment."
-        ),
-    ),
-    interval_anchor: Optional[str] = typer.Option(
-        None, "--anchor-date", help="The anchor date for an interval schedule"
-    ),
-    rrule: str = typer.Option(
-        None,
-        "--rrule",
-        help="An RRule that will be used to set an RRuleSchedule on the deployment.",
-    ),
-    timezone: str = typer.Option(
-        None,
-        "--timezone",
-        help="Deployment schedule timezone string e.g. 'America/New_York'",
-    ),
-    path: str = typer.Option(
-        None,
-        "--path",
-        help=(
-            "An optional path to specify a subdirectory of remote storage to upload to,"
-            " or to point to a subdirectory of a locally stored flow."
-        ),
-    ),
-    output: str = typer.Option(
-        None,
-        "--output",
-        "-o",
-        help="An optional filename to write the deployment file to.",
-    ),
-    _apply: bool = typer.Option(
-        False,
-        "--apply",
-        "-a",
-        help=(
-            "An optional flag to automatically register the resulting deployment with"
-            " the API."
-        ),
-    ),
-    param: List[str] = typer.Option(
-        None,
-        "--param",
-        help=(
-            "An optional parameter override, values are parsed as JSON strings e.g."
-            " --param question=ultimate --param answer=42"
-        ),
-    ),
-    params: str = typer.Option(
-        None,
-        "--params",
-        help=(
-            "An optional parameter override in a JSON string format e.g."
-            ' --params=\'{"question": "ultimate", "answer": 42}\''
-        ),
-    ),
-    no_schedule: bool = typer.Option(
-        False,
-        "--no-schedule",
-        help="An optional flag to disable scheduling for this deployment.",
-    ),
-):
-    """
-    Generate a deployment YAML from /path/to/file.py:flow_function
-    """
-    # validate inputs
-    if not name:
-        exit_with_error(
-            "A name for this deployment must be provided with the '--name' flag."
-        )
-
-    if (
-        len([value for value in (cron, rrule, interval) if value is not None])
-        + (1 if no_schedule else 0)
-        > 1
-    ):
-        exit_with_error("Only one schedule type can be provided.")
-
-    if infra_block and infra_type:
-        exit_with_error(
-            "Only one of `infra` or `infra_block` can be provided, please choose one."
-        )
-
-    output_file = None
-    if output:
-        output_file = Path(output)
-        if output_file.suffix and output_file.suffix != ".yaml":
-            exit_with_error("Output file must be a '.yaml' file.")
-        else:
-            output_file = output_file.with_suffix(".yaml")
-
-    # validate flow
-    try:
-        fpath, obj_name = entrypoint.rsplit(":", 1)
-    except ValueError as exc:
-        if str(exc) == "not enough values to unpack (expected 2, got 1)":
-            missing_flow_name_msg = (
-                "Your flow entrypoint must include the name of the function that is"
-                f" the entrypoint to your flow.\nTry {entrypoint}:<flow_name>"
-            )
-            exit_with_error(missing_flow_name_msg)
-        else:
-            raise exc
-    try:
-        flow = await run_sync_in_worker_thread(load_flow_from_entrypoint, entrypoint)
-    except Exception as exc:
-        exit_with_error(exc)
-    app.console.print(f"Found flow {flow.name!r}", style="green")
-    job_variables = {}
-    for override in overrides or []:
-        key, value = override.split("=", 1)
-        job_variables[key] = value
-
-    if infra_block:
-        infrastructure = await Block.load(infra_block)
-    elif infra_type:
-        # Create an instance of the given type
-        infrastructure = Block.get_block_class_from_key(infra_type)()
-    else:
-        # will reset to a default of Process is no infra is present on the
-        # server-side definition of this deployment
-        infrastructure = None
-
-    if interval_anchor and not interval:
-        exit_with_error("An anchor date can only be provided with an interval schedule")
-
-    schedule = None
-    if cron:
-        cron_kwargs = {"cron": cron, "timezone": timezone}
-        schedule = CronSchedule(
-            **{k: v for k, v in cron_kwargs.items() if v is not None}
-        )
-    elif interval:
-        interval_kwargs = {
-            "interval": timedelta(seconds=interval),
-            "anchor_date": interval_anchor,
-            "timezone": timezone,
-        }
-        schedule = IntervalSchedule(
-            **{k: v for k, v in interval_kwargs.items() if v is not None}
-        )
-    elif rrule:
-        try:
-            schedule = RRuleSchedule(**json.loads(rrule))
-            if timezone:
-                # override timezone if specified via CLI argument
-                schedule.timezone = timezone
-        except json.JSONDecodeError:
-            schedule = RRuleSchedule(rrule=rrule, timezone=timezone)
-
-    # parse storage_block
-    if storage_block:
-        block_type, block_name, *block_path = storage_block.split("/")
-        if block_path and path:
-            exit_with_error(
-                "Must provide a `path` explicitly or provide one on the storage block"
-                " specification, but not both."
-            )
-        elif not path:
-            path = "/".join(block_path)
-        storage_block = f"{block_type}/{block_name}"
-        storage = await Block.load(storage_block)
-    else:
-        storage = None
-
-    if create_default_ignore_file(path="."):
-        app.console.print(
-            (
-                "Default '.prefectignore' file written to"
-                f" {(Path('.') / '.prefectignore').absolute()}"
-            ),
-            style="green",
-        )
-
-    if param and (params is not None):
-        exit_with_error("Can only pass one of `param` or `params` options")
-
-    parameters = dict()
-
-    if param:
-        for p in param or []:
-            k, unparsed_value = p.split("=", 1)
-            try:
-                v = json.loads(unparsed_value)
-                app.console.print(
-                    f"The parameter value {unparsed_value} is parsed as a JSON string"
-                )
-            except json.JSONDecodeError:
-                v = unparsed_value
-            parameters[k] = v
-
-    if params is not None:
-        parameters = json.loads(params)
-
-    # set up deployment object
-    entrypoint = (
-        f"{Path(fpath).absolute().relative_to(Path('.').absolute())}:{obj_name}"
-    )
-
-    init_kwargs = dict(
-        path=path,
-        entrypoint=entrypoint,
-        version=version,
-        storage=storage,
-        job_variables=job_variables or {},
-    )
-
-    if parameters:
-        init_kwargs["parameters"] = parameters
-
-    if description:
-        init_kwargs["description"] = description
-
-    # if a schedule, tags, work_queue_name, or infrastructure are not provided via CLI,
-    # we let `build_from_flow` load them from the server
-    if schedule or no_schedule:
-        init_kwargs.update(schedule=schedule)
-    if tags:
-        init_kwargs.update(tags=tags)
-    if infrastructure:
-        init_kwargs.update(infrastructure=infrastructure)
-    if work_queue_name:
-        init_kwargs.update(work_queue_name=work_queue_name)
-    if work_pool_name:
-        init_kwargs.update(work_pool_name=work_pool_name)
-
-    deployment_loc = output_file or f"{obj_name}-deployment.yaml"
-    deployment = await Deployment.build_from_flow(
-        flow=flow,
-        name=name,
-        output=deployment_loc,
-        skip_upload=skip_upload,
-        apply=False,
-        **init_kwargs,
-    )
-    app.console.print(
-        f"Deployment YAML created at '{Path(deployment_loc).absolute()!s}'.",
-        style="green",
-    )
-
-    await create_work_queue_and_set_concurrency_limit(
-        deployment.work_queue_name, deployment.work_pool_name, work_queue_concurrency
-    )
-
-    # we process these separately for informative output
-    if not skip_upload:
-        if (
-            deployment.storage
-            and "put-directory" in deployment.storage.get_block_capabilities()
-        ):
-            file_count = await deployment.upload_to_storage()
-            if file_count:
-                app.console.print(
-                    (
-                        f"Successfully uploaded {file_count} files to"
-                        f" {deployment.location}"
-                    ),
-                    style="green",
-                )
-        else:
-            app.console.print(
-                (
-                    f"Deployment storage {deployment.storage} does not have upload"
-                    " capabilities; no files uploaded.  Pass --skip-upload to suppress"
-                    " this warning."
-                ),
-                style="green",
-            )
-
-    if _apply:
-        async with get_client() as client:
-            await check_work_pool_exists(
-                work_pool_name=deployment.work_pool_name, client=client
-            )
-            deployment_id = await deployment.apply()
-            app.console.print(
-                (
-                    f"Deployment '{deployment.flow_name}/{deployment.name}'"
-                    f" successfully created with id '{deployment_id}'."
-                ),
-                style="green",
-            )
-            if deployment.work_pool_name is not None:
-                await _print_deployment_work_pool_instructions(
-                    work_pool_name=deployment.work_pool_name, client=client
-                )
-
-            elif deployment.work_queue_name is not None:
-                app.console.print(
-                    "\nTo execute flow runs from this deployment, start an agent that"
-                    f" pulls work from the {deployment.work_queue_name!r} work queue:"
-                )
-                app.console.print(
-                    f"$ prefect agent start -q {deployment.work_queue_name!r}",
-                    style="blue",
-                )
-            else:
-                app.console.print(
-                    (
-                        "\nThis deployment does not specify a work queue name, which"
-                        " means agents will not be able to pick up its runs. To add a"
-                        " work queue, edit the deployment spec and re-run this command,"
-                        " or visit the deployment in the UI."
-                    ),
-                    style="red",
-                )
-
-
 def _load_json_key_values(
     cli_input: List[str], display_name: str
 ) -> Dict[str, Union[dict, str, int]]:


Test patch:
diff --git a/tests/cli/deployment/test_deployment_build.py b/tests/cli/deployment/test_deployment_build.py
deleted file mode 100644
--- a/tests/cli/deployment/test_deployment_build.py
+++ /dev/null
@@ -1,1311 +0,0 @@
-from datetime import timedelta
-from pathlib import Path
-from textwrap import dedent
-from unittest.mock import Mock
-
-import pendulum
-import pytest
-
-import prefect.server.models as models
-import prefect.server.schemas as schemas
-from prefect import flow
-from prefect.deployments import Deployment
-from prefect.filesystems import LocalFileSystem
-from prefect.infrastructure import Process
-from prefect.testing.cli import invoke_and_assert
-from prefect.testing.utilities import AsyncMock
-from prefect.utilities.asyncutils import run_sync_in_worker_thread
-
-
-@pytest.fixture
-def patch_import(monkeypatch):
-    @flow(description="Need a non-trivial description here.", version="A")
-    def fn():
-        pass
-
-    monkeypatch.setattr(
-        "prefect.cli.deployment.load_flow_from_entrypoint", lambda path: fn
-    )
-    return fn
-
-
-@pytest.fixture
-def dep_path():
-    return "./dog.py"
-
-
-@pytest.fixture
-def built_deployment_with_queue_and_limit_overrides(patch_import, tmp_path):
-    d = Deployment(
-        name="TEST",
-        flow_name="fn",
-    )
-    d.apply()
-
-    invoke_and_assert(
-        [
-            "deployment",
-            "build",
-            "fake-path.py:fn",
-            "-n",
-            "TEST",
-            "-o",
-            str(tmp_path / "test.yaml"),
-            "-q",
-            "the-queue-to-end-all-queues",
-            "--limit",
-            "424242",
-        ],
-        expected_code=0,
-        temp_dir=tmp_path,
-    )
-
-
-@pytest.fixture
-def applied_deployment_with_queue_and_limit_overrides(patch_import, tmp_path):
-    d = Deployment(
-        name="TEST",
-        flow_name="fn",
-    )
-    d.apply()
-
-    invoke_and_assert(
-        [
-            "deployment",
-            "build",
-            "fake-path.py:fn",
-            "-n",
-            "TEST",
-            "-o",
-            str(tmp_path / "test.yaml"),
-            "-q",
-            "the-mother-of-all-queues",
-        ],
-        expected_code=0,
-        temp_dir=tmp_path,
-    )
-    invoke_and_assert(
-        [
-            "deployment",
-            "apply",
-            str(tmp_path / "test.yaml"),
-            "-l",
-            "4242",
-        ],
-        expected_code=0,
-        temp_dir=tmp_path,
-    )
-
-
-@pytest.fixture
-def storage_block(tmp_path):
-    storage = LocalFileSystem(basepath=tmp_path / "storage")
-    storage.save(name="test-storage-block")
-    return storage
-
-
-@pytest.fixture
-def infra_block(tmp_path):
-    infra = Process()
-    infra.save(name="test-infra-block")
-    return infra
-
-
-@pytest.fixture
-def mock_build_from_flow(monkeypatch):
-    mock_build_from_flow = AsyncMock()
-
-    # needed to handle `if deployment.storage` check
-    ret = Mock()
-    ret.storage = None
-    mock_build_from_flow.return_value = ret
-
-    monkeypatch.setattr(
-        "prefect.cli.deployment.Deployment.build_from_flow", mock_build_from_flow
-    )
-
-    # not needed for test
-    monkeypatch.setattr(
-        "prefect.cli.deployment.create_work_queue_and_set_concurrency_limit",
-        AsyncMock(),
-    )
-
-    return mock_build_from_flow
-
-
-@pytest.fixture(autouse=True)
-def mock_create_default_ignore_file(monkeypatch):
-    mock_create_default_ignore_file = Mock(return_value=True)
-
-    monkeypatch.setattr(
-        "prefect.cli.deployment.create_default_ignore_file",
-        mock_create_default_ignore_file,
-    )
-
-    return mock_create_default_ignore_file
-
-
-@pytest.fixture(autouse=True)
-async def ensure_default_agent_pool_exists(session):
-    # The default agent work pool is created by a migration, but is cleared on
-    # consecutive test runs. This fixture ensures that the default agent work
-    # pool exists before each test.
-    default_work_pool = await models.workers.read_work_pool_by_name(
-        session=session, work_pool_name=models.workers.DEFAULT_AGENT_WORK_POOL_NAME
-    )
-    if default_work_pool is None:
-        default_work_pool = await models.workers.create_work_pool(
-            session=session,
-            work_pool=schemas.actions.WorkPoolCreate(
-                name=models.workers.DEFAULT_AGENT_WORK_POOL_NAME, type="prefect-agent"
-            ),
-        )
-        await session.commit()
-    assert default_work_pool is not None
-
-
-def test_deployment_build_prints_deprecation_warning(tmp_path, patch_import):
-    invoke_and_assert(
-        [
-            "deployment",
-            "build",
-            "fake-path.py:fn",
-            "-n",
-            "TEST",
-            "-o",
-            str(tmp_path / "test.yaml"),
-            "--no-schedule",
-        ],
-        temp_dir=tmp_path,
-        expected_output_contains=(
-            "WARNING: The 'deployment build' command has been deprecated.",
-            "It will not be available after Sep 2024.",
-            "Use 'prefect deploy' to deploy flows via YAML instead.",
-        ),
-    )
-
-
-class TestSchedules:
-    def test_passing_no_schedule_and_cron_schedules_to_build_exits_with_error(
-        self, patch_import, tmp_path
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--no-schedule",
-                "--cron",
-                "0 4 * * *",
-                "--timezone",
-                "Europe/Berlin",
-            ],
-            expected_code=1,
-            expected_output_contains="Only one schedule type can be provided.",
-            temp_dir=tmp_path,
-        )
-
-    def test_passing_no_schedule_to_build(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--no-schedule",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.schedules == []
-
-    def test_passing_cron_schedules_to_build(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--cron",
-                "0 4 * * *",
-                "--timezone",
-                "Europe/Berlin",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        schedule = deployment.schedules[0].schedule
-        assert schedule.cron == "0 4 * * *"
-        assert schedule.timezone == "Europe/Berlin"
-
-    def test_passing_interval_schedules_to_build(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--interval",
-                "42",
-                "--anchor-date",
-                "2040-02-02",
-                "--timezone",
-                "America/New_York",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        schedule = deployment.schedules[0].schedule
-        assert schedule.interval == timedelta(seconds=42)
-        assert schedule.anchor_date == pendulum.parse("2040-02-02")
-        assert schedule.timezone == "America/New_York"
-
-    def test_passing_anchor_without_interval_exits(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--anchor-date",
-                "2018-02-02",
-            ],
-            expected_code=1,
-            temp_dir=tmp_path,
-            expected_output_contains=(
-                "An anchor date can only be provided with an interval schedule"
-            ),
-        )
-
-    def test_parsing_rrule_schedule_string_literal(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--rrule",
-                "DTSTART:20220910T110000\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        schedule = deployment.schedules[0].schedule
-        assert (
-            schedule.rrule
-            == "DTSTART:20220910T110000\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17"
-        )
-
-    def test_parsing_rrule_schedule_json(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--rrule",
-                (
-                    '{"rrule":'
-                    ' "DTSTART:20220910T110000\\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17",'
-                    ' "timezone": "America/New_York"}'
-                ),
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        schedule = deployment.schedules[0].schedule
-        assert (
-            schedule.rrule
-            == "DTSTART:20220910T110000\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17"
-        )
-        assert schedule.timezone == "America/New_York"
-
-    def test_parsing_rrule_timezone_overrides_if_passed_explicitly(
-        self, patch_import, tmp_path
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--rrule",
-                (
-                    '{"rrule":'
-                    ' "DTSTART:20220910T110000\\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17",'
-                    ' "timezone": "America/New_York"}'
-                ),
-                "--timezone",
-                "Europe/Berlin",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        schedule = deployment.schedules[0].schedule
-        assert (
-            schedule.rrule
-            == "DTSTART:20220910T110000\nRRULE:FREQ=HOURLY;BYDAY=MO,TU,WE,TH,FR,SA;BYHOUR=9,10,11,12,13,14,15,16,17"
-        )
-        assert schedule.timezone == "Europe/Berlin"
-
-    @pytest.mark.parametrize(
-        "schedules",
-        [
-            ["--cron", "cron-str", "--interval", 42],
-            ["--rrule", "rrule-str", "--interval", 42],
-            ["--rrule", "rrule-str", "--cron", "cron-str"],
-            ["--rrule", "rrule-str", "--cron", "cron-str", "--interval", 42],
-        ],
-    )
-    def test_providing_multiple_schedules_exits_with_error(
-        self, patch_import, tmp_path, schedules
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        cmd += schedules
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains="Only one schedule type can be provided.",
-        )
-
-
-class TestParameterOverrides:
-    def test_param_overrides(self, patch_import, tmp_path):
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-        )
-        d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--param",
-                "foo=bar",
-                "--param",
-                'greenman_says={"parsed as json": "I am"}',
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.parameters["foo"] == "bar"
-        assert deployment.parameters["greenman_says"] == {"parsed as json": "I am"}
-
-    def test_parameters_override(self, patch_import, tmp_path):
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-        )
-        d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--params",
-                '{"greenman_says": {"parsed as json": "I am"}}',
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.parameters["greenman_says"] == {"parsed as json": "I am"}
-
-    def test_mixing_parameter_overrides(self, patch_import, tmp_path):
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-        )
-        d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--params",
-                '{"which": "parameter"}',
-                "--param",
-                "should-be:used",
-            ],
-            expected_code=1,
-            temp_dir=tmp_path,
-        )
-
-
-class TestFlowName:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_flow_name_called_correctly(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["name"] == name
-
-    def test_not_providing_name_exits_with_error(self, patch_import, tmp_path):
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-o",
-            output_path,
-        ]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains=(
-                "A name for this deployment must be provided with the '--name' flag.\n"
-            ),
-        )
-
-    def test_name_must_be_provided_by_default(self, dep_path):
-        invoke_and_assert(
-            ["deployment", "build", dep_path],
-            expected_output_contains=["A name for this deployment must be provided"],
-            expected_code=1,
-        )
-
-
-class TestDescription:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_description_set_correctly(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        deployment_description = "TEST DEPLOYMENT"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            "TEST",
-            "--description",
-            deployment_description,
-            "-o",
-            output_path,
-        ]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["description"] == deployment_description
-
-
-class TestEntrypoint:
-    def test_entrypoint_is_saved_as_relative_path(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.entrypoint == "fake-path.py:fn"
-
-    def test_poorly_formed_entrypoint_raises_correct_error(
-        self, patch_import, tmp_path
-    ):
-        name = "TEST"
-        file_name = "test_no_suffix"
-        str(tmp_path / file_name)
-        entrypoint = "fake-path.py"
-        cmd = ["deployment", "build", "-n", name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains=(
-                "Your flow entrypoint must include the name of the function that is"
-                f" the entrypoint to your flow.\nTry {entrypoint}:<flow_name>"
-            ),
-        )
-
-    def test_entrypoint_that_does_not_point_to_flow_raises_error(self, tmp_path):
-        code = """
-        def fn():
-            pass
-        """
-        fpath = tmp_path / "dog.py"
-        fpath.write_text(dedent(code))
-
-        name = "TEST"
-        entrypoint = f"{fpath}:fn"
-        cmd = ["deployment", "build", "-n", name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains=(
-                "Function with name 'fn' is not a flow. Make sure that it is decorated"
-                " with '@flow'"
-            ),
-        )
-
-    def test_entrypoint_that_points_to_wrong_flow_raises_error(self, tmp_path):
-        code = """
-        from prefect import flow
-
-        @flow
-        def cat():
-            pass
-        """
-        fpath = tmp_path / "dog.py"
-        fpath.write_text(dedent(code))
-
-        name = "TEST"
-        entrypoint = f"{fpath}:fn"
-        cmd = ["deployment", "build", "-n", name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains=(
-                f"Flow function with name 'fn' not found in {str(fpath)!r}"
-            ),
-        )
-
-    def test_entrypoint_that_does_not_point_to_python_file_raises_error(self, tmp_path):
-        code = """
-        def fn():
-            pass
-        """
-        fpath = tmp_path / "dog.cake"
-        fpath.write_text(dedent(code))
-
-        name = "TEST"
-        entrypoint = f"{fpath}:fn"
-        cmd = ["deployment", "build", "-n", name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains="No module named ",
-        )
-
-    def test_entrypoint_works_with_flow_with_custom_name(self, tmp_path, monkeypatch):
-        flow_code = """
-        from prefect import flow
-
-        @flow(name="SoMe CrAz_y N@me")
-        def dog():
-            pass
-        """
-        monkeypatch.chdir(tmp_path)
-        file_name = "f.py"
-        file_path = Path(tmp_path) / Path(file_name)
-        file_path.write_text(dedent(flow_code))
-
-        dep_name = "TEST"
-        entrypoint = f"{file_path.absolute()}:dog"
-        cmd = ["deployment", "build", "-n", dep_name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-    def test_entrypoint_works_with_flow_func_with_underscores(
-        self, tmp_path, monkeypatch
-    ):
-        flow_code = """
-        from prefect import flow
-
-        @flow
-        def dog_flow_func():
-            pass
-        """
-        monkeypatch.chdir(tmp_path)
-        file_name = "f.py"
-        file_path = Path(tmp_path) / Path(file_name)
-        file_path.write_text(dedent(flow_code))
-
-        dep_name = "TEST"
-        entrypoint = f"{file_path.absolute()}:dog_flow_func"
-        cmd = ["deployment", "build", "-n", dep_name]
-        cmd += [entrypoint]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-
-class TestWorkQueue:
-    def test_work_queue_name_is_populated_as_default(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.work_queue_name == "default"
-
-    def test_success_message_with_work_queue_name(self, patch_import, tmp_path):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-        invoke_and_assert(
-            [
-                "deployment",
-                "apply",
-                str(tmp_path / "test.yaml"),
-            ],
-            expected_output_contains=[
-                (
-                    "To execute flow runs from this deployment, start an agent "
-                    "that pulls work from the 'default' work queue:"
-                ),
-                "$ prefect agent start -q 'default'",
-            ],
-        )
-
-
-class TestWorkPool:
-    async def test_creates_work_queue_in_work_pool(
-        self,
-        patch_import,
-        tmp_path,
-        work_pool,
-        session,
-    ):
-        await run_sync_in_worker_thread(
-            invoke_and_assert,
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-p",
-                work_pool.name,
-                "-q",
-                "new-queue",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--apply",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = await Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.work_pool_name == work_pool.name
-        assert deployment.work_queue_name == "new-queue"
-
-        work_queue = await models.workers.read_work_queue_by_name(
-            session=session, work_pool_name=work_pool.name, work_queue_name="new-queue"
-        )
-        assert work_queue is not None
-
-
-class TestAutoApply:
-    def test_auto_apply_flag(self, patch_import, tmp_path):
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-        )
-        deployment_id = d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "--apply",
-            ],
-            expected_code=0,
-            expected_output_contains=[
-                f"Deployment '{d.flow_name}/{d.name}' successfully created with id"
-                f" '{deployment_id}'."
-            ],
-            temp_dir=tmp_path,
-        )
-
-    def test_auto_apply_work_pool_does_not_exist(
-        self,
-        patch_import,
-        tmp_path,
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "-p",
-                "gibberish",
-                "--apply",
-            ],
-            expected_code=1,
-            expected_output_contains=(
-                [
-                    (
-                        "This deployment specifies a work pool name of 'gibberish', but"
-                        " no such work pool exists."
-                    ),
-                    "To create a work pool via the CLI:",
-                    "$ prefect work-pool create 'gibberish'",
-                ]
-            ),
-            temp_dir=tmp_path,
-        )
-
-    def test_message_with_prefect_agent_work_pool(
-        self, patch_import, tmp_path, prefect_agent_work_pool
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "-p",
-                prefect_agent_work_pool.name,
-                "--apply",
-            ],
-            expected_output_contains=[
-                (
-                    "To execute flow runs from this deployment, start an agent that"
-                    f" pulls work from the {prefect_agent_work_pool.name!r} work pool:"
-                ),
-                f"$ prefect agent start -p {prefect_agent_work_pool.name!r}",
-            ],
-            temp_dir=tmp_path,
-        )
-
-    def test_message_with_process_work_pool(
-        self, patch_import, tmp_path, process_work_pool, enable_workers
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "-p",
-                process_work_pool.name,
-                "--apply",
-            ],
-            expected_output_contains=[
-                (
-                    "To execute flow runs from this deployment, start a worker "
-                    f"that pulls work from the {process_work_pool.name!r} work pool:"
-                ),
-                f"$ prefect worker start -p {process_work_pool.name!r}",
-            ],
-            temp_dir=tmp_path,
-        )
-
-    def test_message_with_process_work_pool_without_workers_enabled(
-        self, patch_import, tmp_path, process_work_pool, disable_workers
-    ):
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "-p",
-                process_work_pool.name,
-                "--apply",
-            ],
-            expected_output_contains=[
-                (
-                    "\nTo execute flow runs from this deployment, please enable "
-                    "the workers CLI and start a worker that pulls work from the "
-                    f"{process_work_pool.name!r} work pool:"
-                ),
-                (
-                    "$ prefect config set PREFECT_EXPERIMENTAL_ENABLE_WORKERS=True\n"
-                    f"$ prefect worker start -p {process_work_pool.name!r}"
-                ),
-            ],
-            temp_dir=tmp_path,
-        )
-
-
-class TestWorkQueueConcurrency:
-    async def test_setting_work_queue_concurrency_limits_with_build(
-        self, built_deployment_with_queue_and_limit_overrides, prefect_client
-    ):
-        queue = await prefect_client.read_work_queue_by_name(
-            "the-queue-to-end-all-queues"
-        )
-        assert queue.concurrency_limit == 424242
-
-    async def test_setting_work_queue_concurrency_limits_with_apply(
-        self, applied_deployment_with_queue_and_limit_overrides, prefect_client
-    ):
-        queue = await prefect_client.read_work_queue_by_name("the-mother-of-all-queues")
-        assert queue.concurrency_limit == 4242
-
-
-class TestVersionFlag:
-    def test_version_flag_takes_precedence(self, patch_import, tmp_path):
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-            version="server",
-        )
-        assert d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-                "-v",
-                "CLI-version",
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.version == "CLI-version"
-
-
-class TestLoadingSettings:
-    def test_server_side_settings_are_used_if_present(self, patch_import, tmp_path):
-        """
-        This only applies to tags, work queue name, description, schedules and default parameter values
-        """
-        d = Deployment(
-            name="TEST",
-            flow_name="fn",
-            description="server-side value",
-            version="server",
-            parameters={"key": "server"},
-            tags=["server-tag"],
-            work_queue_name="dev",
-        )
-        assert d.apply()
-
-        invoke_and_assert(
-            [
-                "deployment",
-                "build",
-                "fake-path.py:fn",
-                "-n",
-                "TEST",
-                "-o",
-                str(tmp_path / "test.yaml"),
-            ],
-            expected_code=0,
-            temp_dir=tmp_path,
-        )
-
-        deployment = Deployment.load_from_yaml(tmp_path / "test.yaml")
-        assert deployment.description == "server-side value"
-        assert deployment.tags == ["server-tag"]
-        assert deployment.parameters == dict(key="server")
-        assert deployment.work_queue_name == "dev"
-
-
-class TestSkipUpload:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    @pytest.mark.parametrize("skip_upload", ["--skip-upload", None])
-    def test_skip_upload_called_correctly(
-        self, patch_import, tmp_path, skip_upload, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-
-        if skip_upload:
-            cmd.append(skip_upload)
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-
-        if skip_upload:
-            assert build_kwargs["skip_upload"] is True
-        else:
-            assert build_kwargs["skip_upload"] is False
-
-
-class TestInfraAndInfraBlock:
-    def test_providing_infra_block_and_infra_type_exits_with_error(
-        self, patch_import, tmp_path
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        cmd += ["-i", "process", "-ib", "my-block"]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains=(
-                "Only one of `infra` or `infra_block` can be provided, please choose"
-                " one."
-            ),
-        )
-
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_infra_block_called_correctly(
-        self, patch_import, tmp_path, infra_block, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        cmd += ["-ib", "process/test-infra-block"]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["infrastructure"] == infra_block
-
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_infra_type_specifies_infra_block_on_deployment(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        cmd += ["-i", "docker-container"]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        infra = build_kwargs["infrastructure"]
-        assert infra.type == "docker-container"
-
-
-class TestInfraOverrides:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_overrides_called_correctly(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        overrides = ["my.dog=1", "your.cat=test"]
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        for override in overrides:
-            cmd += ["--override", override]
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["job_variables"] == {"my.dog": "1", "your.cat": "test"}
-
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_overrides_default_is_empty(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["job_variables"] == {}
-
-
-class TestStorageBlock:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_storage_block_called_correctly(
-        self, patch_import, tmp_path, storage_block, mock_build_from_flow
-    ):
-        name = "TEST"
-        output_path = str(tmp_path / "test.yaml")
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-            "-o",
-            output_path,
-        ]
-        cmd += ["-sb", "local-file-system/test-storage-block"]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["storage"] == storage_block
-
-
-class TestOutputFlag:
-    def test_output_file_with_wrong_suffix_exits_with_error(
-        self, patch_import, tmp_path
-    ):
-        name = "TEST"
-        file_name = "test.not_yaml"
-        output_path = str(tmp_path / file_name)
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-        ]
-        cmd += ["-o", output_path]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=1,
-            expected_output_contains="Output file must be a '.yaml' file.",
-        )
-
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_yaml_appended_to_out_file_without_suffix(
-        self, monkeypatch, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        file_name = "test_no_suffix"
-        output_path = str(tmp_path / file_name)
-        entrypoint = "fake-path.py:fn"
-        cmd = [
-            "deployment",
-            "build",
-            entrypoint,
-            "-n",
-            name,
-        ]
-        cmd += ["-o", output_path]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["output"] == Path(output_path + ".yaml")
-
-
-class TestOtherStuff:
-    @pytest.mark.filterwarnings("ignore:does not have upload capabilities")
-    def test_correct_flow_passed_to_deployment_object(
-        self, patch_import, tmp_path, mock_build_from_flow
-    ):
-        name = "TEST"
-        file_name = "test_no_suffix"
-        output_path = str(tmp_path / file_name)
-        entrypoint = "fake-path.py:fn"
-        cmd = ["deployment", "build", entrypoint, "-n", name, "-o", output_path]
-
-        invoke_and_assert(
-            cmd,
-            expected_code=0,
-        )
-
-        build_kwargs = mock_build_from_flow.call_args.kwargs
-        assert build_kwargs["flow"] == patch_import