Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Plugins are now auto-installed when commands require them #8482

Merged
merged 72 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
fbbfaa0
Allow `install_plugins` call to optionally skip plugins that are alre…
ReubenFrankel Apr 6, 2024
7b9bf26
Make plugin install methods async
ReubenFrankel Apr 6, 2024
295bdba
Reuse existing skipped plugin install paradigm
ReubenFrankel Apr 6, 2024
dace002
Implement for `invoke`
ReubenFrankel Apr 6, 2024
9488b73
Implement for `run`
ReubenFrankel Apr 7, 2024
6209de7
Remove all files in a plugin run dir, not including the dir itself
ReubenFrankel Apr 7, 2024
5d82276
POC: Disable install results output for `invoke`
ReubenFrankel Apr 7, 2024
86a8efc
Fix `invoke` failing tests
ReubenFrankel Apr 7, 2024
f11d4b7
Address coverage warnings
ReubenFrankel Apr 7, 2024
f1d4cda
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 8, 2024
27aa983
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 11, 2024
44680fa
Implement for `el`/`elt`
ReubenFrankel Apr 11, 2024
d7dcb2e
Default to showing plugin install status and results
ReubenFrankel Apr 12, 2024
c48d136
Implement for `test`
ReubenFrankel Apr 12, 2024
4870efa
Implement for `config <plugin> test`
ReubenFrankel Apr 12, 2024
19f43ec
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 12, 2024
06d6f81
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 15, 2024
5ab1b61
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 16, 2024
5928b81
POC: Display installation state/progress as logs if a logger is provi…
ReubenFrankel Apr 16, 2024
fa0b1f6
Convert some CLI module logging to `structlog` to resolve mypy errors
ReubenFrankel Apr 17, 2024
b42b683
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 23, 2024
d390810
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 24, 2024
55d4585
Add `--[no-]install` option to all affected commands and reuse for `add`
ReubenFrankel Apr 25, 2024
89f1848
Update `install_plugins` patch
ReubenFrankel Apr 25, 2024
6fac1d3
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Apr 30, 2024
ab5b9e8
Implement for `select --list`
ReubenFrankel May 1, 2024
7aeddf3
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel May 1, 2024
90e0d67
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel May 3, 2024
6fb26f6
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel May 7, 2024
edc1cd0
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel May 24, 2024
5e0aa7a
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon May 29, 2024
7f5c488
Use singular reason for just-in-time installs
ReubenFrankel May 29, 2024
21cb02d
Use plugin fingerprint to determine if install is required
ReubenFrankel May 31, 2024
7a61121
Apply fixture to reset project context
ReubenFrankel May 31, 2024
69f8295
Prevent test failure when `VenvService` is instantiated more that once
ReubenFrankel May 31, 2024
19962ee
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jun 1, 2024
34be01b
Safer to remove from explicit project root, rather than current worki…
ReubenFrankel Jun 1, 2024
46707f0
Ignore missing test coverage
ReubenFrankel Jun 1, 2024
e7f1847
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jun 10, 2024
9e7ac42
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jun 11, 2024
1374882
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jun 18, 2024
57fe535
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jun 19, 2024
ba9a469
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jun 19, 2024
786952c
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jun 24, 2024
6a3df48
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jun 25, 2024
d868fd3
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 2, 2024
9df74c3
Fix typing issues
edgarrmondragon Jul 2, 2024
559568b
Apply suggestions from code review
ReubenFrankel Jul 2, 2024
ed636eb
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jul 3, 2024
d2f218b
Move plugin fingerprint read/write methods to `VirtualEnv`
ReubenFrankel Jul 3, 2024
de43be9
Fix `WPS214` error
ReubenFrankel Jul 3, 2024
48da3d5
Add docs on `--[no-]install` usage for relevant commands
ReubenFrankel Jul 3, 2024
17ff66d
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jul 3, 2024
fe37d78
Address missing coverage
edgarrmondragon Jul 3, 2024
b5ff57f
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 3, 2024
bdbcb07
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 7, 2024
f90d03d
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 8, 2024
e71fac2
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 8, 2024
fd427aa
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 8, 2024
630eebf
Do not require install if env vars are missing when expanding pip URL
ReubenFrankel Jul 9, 2024
09f1190
Merge branch 'main' into 2089-just-in-time-install
ReubenFrankel Jul 9, 2024
1edc21c
Warn user when auto-install will not be performed due to missing env …
ReubenFrankel Jul 9, 2024
cd51b04
Update `skip_installed` kwarg to more fitting `auto_install`
ReubenFrankel Jul 9, 2024
c0159a4
Merge branch 'main' into 2089-just-in-time-install
edgarrmondragon Jul 9, 2024
ddf69af
Allow configuration of auto-install behaviour globally via env/`melta…
ReubenFrankel Jul 10, 2024
e658a38
Change `JIT` install reason to `AUTO`
ReubenFrankel Jul 10, 2024
52e67d2
Use `AUTO` reason to indicate an auto-install, rather than explicit `…
ReubenFrankel Jul 10, 2024
6becf2b
Prevent `'NoneType' object has no attribute 'settings'` error when ru…
ReubenFrankel Jul 10, 2024
bc45ecb
Downgrade some install status/results logs to `DEBUG` when auto-insta…
ReubenFrankel Jul 10, 2024
bc428d3
Don't show "Installing" message for a plugin that will be skipped imm…
ReubenFrankel Jul 10, 2024
f2f627c
Update manifests
ReubenFrankel Jul 10, 2024
510b9fe
Add schema entry for `auto_install`
ReubenFrankel Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions src/meltano/cli/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
import click
import requests

from meltano.cli.params import pass_project
from meltano.cli.params import InstallPlugins, install_option, pass_project
from meltano.cli.utils import (
CliError,
PartialInstrumentedCmd,
add_plugin,
add_required_plugins,
check_dependencies_met,
install_plugins,
)
from meltano.core.plugin import PluginRef, PluginType
from meltano.core.plugin_install_service import PluginInstallReason
from meltano.core.project_add_service import ProjectAddService
from meltano.core.tracking.contexts import CliEvent, PluginsTrackingContext
from meltano.core.utils import run_async
from meltano.core.yaml import yaml

if t.TYPE_CHECKING:
Expand Down Expand Up @@ -102,23 +102,21 @@ def _load_yaml_from_ref(_ctx, _param, value: str | None) -> dict | None:
is_flag=True,
help="Update an existing plugin.",
)
@click.option(
"--no-install",
is_flag=True,
help="Do not install the plugin after adding it to the project.",
)
@install_option
@click.option(
"--force-install",
is_flag=True,
help="Ignore the required Python version declared by the plugins.",
)
@pass_project()
@click.pass_context
def add( # noqa: WPS238
@run_async
async def add( # noqa: C901 WPS238
ctx,
project: Project,
plugin_type: str,
plugin_name: str,
install_plugins: InstallPlugins,
inherit_from: str | None = None,
variant: str | None = None,
as_name: str | None = None,
Expand Down Expand Up @@ -197,17 +195,16 @@ def add( # noqa: WPS238
)
tracker.track_command_event(CliEvent.inflight)

if not flags.get("no_install"):
success = install_plugins(
project,
plugins,
reason=PluginInstallReason.ADD,
force=flags.get("force_install", False),
)
success = await install_plugins(
project,
plugins,
reason=PluginInstallReason.ADD,
force=flags.get("force_install", False),
)

if not success:
tracker.track_command_event(CliEvent.failed)
raise CliError("Failed to install plugin(s)") # noqa: EM101
if success is False:
tracker.track_command_event(CliEvent.failed)
raise CliError("Failed to install plugin(s)") # noqa: EM101

_print_plugins(plugins)
tracker.track_command_event(CliEvent.completed)
Expand Down
28 changes: 20 additions & 8 deletions src/meltano/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import asyncio
import json
import tempfile
import typing as t
Expand All @@ -14,7 +13,7 @@
import structlog

from meltano.cli.interactive import InteractiveConfig
from meltano.cli.params import pass_project
from meltano.cli.params import InstallPlugins, install_option, pass_project
from meltano.cli.utils import (
CliEnvironmentBehavior,
CliError,
Expand All @@ -25,11 +24,13 @@
from meltano.core.plugin import PluginType
from meltano.core.plugin.error import PluginNotFoundError
from meltano.core.plugin.settings_service import PluginSettingsService
from meltano.core.plugin_install_service import PluginInstallReason
from meltano.core.plugin_invoker import PluginInvoker
from meltano.core.plugin_test_service import PluginTestServiceFactory
from meltano.core.settings_service import SettingValueStore
from meltano.core.settings_store import StoreNotSupportedError
from meltano.core.tracking.contexts import CliEvent, PluginsTrackingContext
from meltano.core.utils import run_async

if t.TYPE_CHECKING:
from meltano.core.project import Project
Expand Down Expand Up @@ -393,8 +394,15 @@ def set_(


@config.command(cls=PartialInstrumentedCmd, name="test")
@pass_project(migrate=True)
@click.pass_context
def test(ctx):
@install_option
@run_async
async def test(
ctx,
project: Project,
install_plugins: InstallPlugins,
):
"""Test the configuration of a plugin."""
invoker = ctx.obj["invoker"]
tracker = ctx.obj["tracker"]
Expand All @@ -403,14 +411,18 @@ def test(ctx):
raise CliError("Testing of the Meltano project configuration is not supported") # noqa: EM101

session = ctx.obj["session"]
plugin_test_service = PluginTestServiceFactory(invoker).get_test_service()

async def _validate(): # noqa: WPS430
plugin_test_service = PluginTestServiceFactory(invoker).get_test_service()
async with plugin_test_service.plugin_invoker.prepared(session):
return await plugin_test_service.validate()
await install_plugins(
project,
[plugin_test_service.plugin_invoker.plugin],
reason=PluginInstallReason.JIT,
skip_installed=True,
)

try:
is_valid, detail = asyncio.run(_validate())
async with plugin_test_service.plugin_invoker.prepared(session):
is_valid, detail = await plugin_test_service.validate()
except Exception:
tracker.track_command_event(CliEvent.failed)
raise
Expand Down
47 changes: 42 additions & 5 deletions src/meltano/cli/elt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import click
from structlog import stdlib as structlog_stdlib

from meltano.cli.params import pass_project
from meltano.cli.params import InstallPlugins, install_option, pass_project
from meltano.cli.utils import CliEnvironmentBehavior, CliError, PartialInstrumentedCmd
from meltano.core.db import project_engine
from meltano.core.elt_context import ELTContextBuilder
Expand All @@ -20,6 +20,7 @@
from meltano.core.logging import JobLoggingService, OutputLogger
from meltano.core.plugin import PluginType
from meltano.core.plugin.error import PluginNotFoundError
from meltano.core.plugin_install_service import PluginInstallReason
from meltano.core.runner import RunnerError
from meltano.core.runner.dbt import DbtRunner
from meltano.core.runner.singer import SingerRunner
Expand Down Expand Up @@ -116,6 +117,7 @@ class ELOptions:
@ELOptions.state_id
@ELOptions.force
@ELOptions.merge_state
@install_option
@click.pass_context
@pass_project(migrate=True)
@run_async
Expand All @@ -134,6 +136,7 @@ async def el( # WPS408
state_id: str,
force: bool,
merge_state: bool,
install_plugins: InstallPlugins,
):
"""
Run an EL pipeline to Extract and Load data.
Expand Down Expand Up @@ -161,6 +164,7 @@ async def el( # WPS408
state_id,
force,
merge_state,
install_plugins,
)


Expand All @@ -182,6 +186,7 @@ async def el( # WPS408
@ELOptions.state_id
@ELOptions.force
@ELOptions.merge_state
@install_option
@click.pass_context
@pass_project(migrate=True)
@run_async
Expand All @@ -201,6 +206,7 @@ async def elt( # WPS408
state_id: str,
force: bool,
merge_state: bool,
install_plugins: InstallPlugins,
):
"""
Run an ELT pipeline to Extract, Load, and Transform data.
Expand Down Expand Up @@ -229,6 +235,7 @@ async def elt( # WPS408
state_id,
force,
merge_state,
install_plugins,
)


Expand All @@ -248,6 +255,7 @@ async def _run_el_command(
state_id: str,
force: bool,
merge_state: bool,
install_plugins: InstallPlugins,
):
if platform.system() == "Windows":
raise CliError(
Expand Down Expand Up @@ -294,7 +302,15 @@ async def _run_el_command(
if dump:
await dump_file(context_builder, dump)
else:
await _run_job(tracker, project, job, session, context_builder, force=force)
await _run_job(
tracker,
project,
job,
session,
context_builder,
install_plugins,
force=force,
)
except Exception as err:
tracker.track_command_event(CliEvent.failed)
raise err
Expand Down Expand Up @@ -358,7 +374,15 @@ async def dump_file(context_builder, dumpable):
raise CliError(f"Could not dump {dumpable}: {err}") from err # noqa: EM102


async def _run_job(tracker, project, job, session, context_builder, force=False):
async def _run_job(
tracker,
project,
job,
session,
context_builder,
install_plugins: InstallPlugins,
force=False,
):
fail_stale_jobs(session, job.job_name)

if not force and (existing := JobFinder(job.job_name).latest_running(session)):
Expand All @@ -377,7 +401,7 @@ async def _run_job(tracker, project, job, session, context_builder, force=False)

log = logger.bind(name="meltano", run_id=str(job.run_id), state_id=job.job_name)

await _run_elt(tracker, log, context_builder, output_logger)
await _run_elt(tracker, log, context_builder, output_logger, install_plugins)


@asynccontextmanager
Expand Down Expand Up @@ -405,10 +429,23 @@ async def _run_elt(
log: structlog.BoundLogger,
context_builder: ELTContextBuilder,
output_logger: OutputLogger,
install_plugins: InstallPlugins,
):
elt_context = context_builder.context()
plugins = [elt_context.extractor, elt_context.loader]

if elt_context.only_transform:
plugins.append(elt_context.transformer)

await install_plugins(
elt_context.project,
plugins,
reason=PluginInstallReason.JIT,
skip_installed=True,
)

async with _redirect_output(log, output_logger):
try:
elt_context = context_builder.context()
tracker.add_contexts(PluginsTrackingContext.from_elt_context(elt_context))
tracker.track_command_event(CliEvent.inflight)

Expand Down
6 changes: 4 additions & 2 deletions src/meltano/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from meltano.core.plugin import PluginType
from meltano.core.schedule_service import ScheduleService
from meltano.core.tracking.contexts import CliEvent, PluginsTrackingContext
from meltano.core.utils import run_async

if t.TYPE_CHECKING:
from meltano.core.project import Project
Expand Down Expand Up @@ -59,7 +60,8 @@
)
@click.pass_context
@pass_project(migrate=True)
def install( # noqa: C901
@run_async
async def install( # noqa: C901
project: Project,
ctx: click.Context,
plugin_type: str,
Expand Down Expand Up @@ -101,7 +103,7 @@ def install( # noqa: C901
)
tracker.track_command_event(CliEvent.inflight)

success = install_plugins(
success = await install_plugins(
project,
plugins,
parallelism=parallelism,
Expand Down
Loading