Repository: meltano/meltano

Issue:
feat: consider clearing the catalog cache when `--full-refresh` present
This has come up in slack a bunch of times but most recently in https://meltano.slack.com/archives/C01TCRBBJD7/p1689207554179589

Lots of users run into this where the catalog is being cached so the output theyre seeing is not as expected, we end up recommending that they clear their `.meltano/run/tap-x` directory so it regenerates the catalog. If someone runs with the `--full-refresh` flag its probably because something in the source changed so they need to re-run the replication but the cached catalog is blocking those changes from propogating.


Related to:
- https://github.com/meltano/meltano/issues/6292
- https://github.com/meltano/meltano/issues/6763
- https://github.com/meltano/meltano/issues/2856
- https://github.com/meltano/meltano/issues/2848




Discussion:

Resolving patch:
diff --git a/src/meltano/cli/__init__.py b/src/meltano/cli/__init__.py
--- a/src/meltano/cli/__init__.py
+++ b/src/meltano/cli/__init__.py
@@ -15,6 +15,7 @@
     dragon,
     elt,
     environment,
+    hub,
     initialize,
     install,
     invoke,
@@ -48,6 +49,7 @@
 cli.add_command(dragon.dragon)
 cli.add_command(elt.elt)
 cli.add_command(environment.meltano_environment)
+cli.add_command(hub.hub)
 cli.add_command(initialize.init)
 cli.add_command(install.install)
 cli.add_command(invoke.invoke)
diff --git a/src/meltano/cli/hub.py b/src/meltano/cli/hub.py
new file mode 100644
--- /dev/null
+++ b/src/meltano/cli/hub.py
@@ -0,0 +1,53 @@
+"""Meltano Hub command."""
+
+from __future__ import annotations
+
+import typing as t
+
+import click
+
+from meltano.cli.params import pass_project
+from meltano.cli.utils import CliEnvironmentBehavior, InstrumentedCmd, InstrumentedGroup
+from meltano.core.plugin import PluginType
+
+if t.TYPE_CHECKING:
+    from meltano.core.project import Project
+
+
+@click.group(
+    cls=InstrumentedGroup,
+    short_help="Interact with Meltano Hub.",
+    environment_behavior=CliEnvironmentBehavior.environment_optional_use_default,
+)
+def hub():
+    """
+    Interact with Meltano Hub.
+    Read more at https://docs.meltano.com/reference/command-line-interface#hub
+    """  # noqa: D205
+
+
+@hub.command(
+    cls=InstrumentedCmd,
+    short_help="Ping Meltano Hub.",
+)
+@pass_project()
+def ping(project: Project):
+    """
+    Ping Meltano Hub. This can be useful for checking if a custom Hub URL is reachable.
+    Read more at https://docs.meltano.com/reference/command-line-interface#hub
+    """  # noqa: D205
+    try:
+        # We want to ensure that we can actually communicate with the Hub.
+        # Requesting a list of plugins is a good way to do that, but we don't
+        # want to waste bandwidth, so we request the list of orchestrators,
+        # which is currently very small.
+        project.hub_service.get_plugins_of_type(PluginType.ORCHESTRATORS)
+    except Exception as ex:
+        raise click.ClickException(
+            f"Failed to connect to the Hub at {project.hub_service.hub_api_url!r}",
+        ) from ex
+    else:
+        click.secho(
+            f"Successfully connected to the Hub at {project.hub_service.hub_api_url!r}",
+            fg="green",
+        )


Test patch:
diff --git a/tests/meltano/cli/test_hub.py b/tests/meltano/cli/test_hub.py
new file mode 100644
--- /dev/null
+++ b/tests/meltano/cli/test_hub.py
@@ -0,0 +1,55 @@
+from __future__ import annotations
+
+import typing as t
+from collections import Counter
+
+import mock
+from click.testing import CliRunner
+
+from asserts import assert_cli_runner
+from meltano.cli import cli
+from meltano.core.project import Project
+
+if t.TYPE_CHECKING:
+    from requests_mock import Mocker as RequestsMocker
+
+
+class TestCliHub:
+    def test_ping(
+        self,
+        project: Project,
+        cli_runner: CliRunner,
+        hub_request_counter: Counter,
+    ):
+        with mock.patch(
+            "requests.adapters.HTTPAdapter.send",
+            project.hub_service.session.get_adapter(
+                project.hub_service.hub_api_url,
+            ).send,
+        ):
+            result = cli_runner.invoke(cli, ("hub", "ping"))
+
+        assert_cli_runner(result)
+
+        assert hub_request_counter["/orchestrators/index"] == 1
+        assert (
+            f"Successfully connected to the Hub at {project.hub_service.hub_api_url!r}"
+            in result.stdout
+        )
+
+    def test_ping_unreachable(
+        self,
+        project: Project,
+        cli_runner: CliRunner,
+        hub_request_counter: Counter,
+        requests_mock: RequestsMocker,
+    ):
+        hub_api = project.hub_service.hub_api_url
+        requests_mock.get(  # noqa: S113
+            hub_api,
+            exc=ConnectionError("Connection refused"),
+        )
+        result = cli_runner.invoke(cli, ("hub", "ping"))
+        assert f"Error: Failed to connect to the Hub at {hub_api!r}" in result.stderr
+        assert result.exit_code == 1
+        assert not hub_request_counter