Skip to content

Commit

Permalink
chore: Address typing issues in less problematic meltano.cli modules (
Browse files Browse the repository at this point in the history
#8564)

* chore: Address typing issues in less problematic `meltano.cli` modules

* Fix typo

* Annotate more modules

* Remove unused protocol

* Update the return type of `NoWindowsGlobbingGroup.main`
  • Loading branch information
edgarrmondragon committed Jun 19, 2024
1 parent d8150e8 commit a396e3b
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 48 deletions.
26 changes: 7 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -500,29 +500,10 @@ warn_unused_ignores = true
ignore_errors = true
module = [
"meltano.cli.add",
"meltano.cli.cli",
"meltano.cli.compile",
"meltano.cli.docs",
"meltano.cli.dragon",
"meltano.cli.elt",
"meltano.cli.environment",
"meltano.cli.hub",
"meltano.cli.initialize",
"meltano.cli.install",
"meltano.cli.interactive.*",
"meltano.cli.invoke",
"meltano.cli.job",
"meltano.cli.lock",
"meltano.cli.params",
"meltano.cli.remove",
"meltano.cli.run",
"meltano.cli.schedule",
"meltano.cli.schema",
"meltano.cli.select",
"meltano.cli.state",
"meltano.cli.upgrade",
"meltano.cli.utils",
"meltano.cli.validate",
# Way too many modules at the root of meltano.core to tackle them all at once # so listing them individually here.
"meltano.core.db",
"meltano.core.elt_context",
Expand Down Expand Up @@ -564,6 +545,13 @@ module = [
"meltano.core.utils.*",
]

[[tool.mypy.overrides]]
ignore_missing_imports = true
module = [
"click_default_group.*", # https://github.com/click-contrib/click-default-group/issues/26
"click_didyoumean.*", # Unreleased, https://github.com/click-contrib/click-didyoumean/commit/048b275077382e5a80bdf05b02830013eb37ddd1
]

[build-system]
requires = ["poetry-core==1.7.0"]
build-backend = "poetry.core.masonry.api"
Expand Down
4 changes: 2 additions & 2 deletions src/meltano/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class NoWindowsGlobbingGroup(InstrumentedGroup):
typical Meltano commands fail, e.g. `meltano select tap-gitlab tags "*"`.
"""

def main(self, *args, **kwargs) -> t.NoReturn:
def main(self, *args, **kwargs) -> t.Any:
"""Invoke the Click CLI with Windows globbing disabled.
Args:
Expand All @@ -56,7 +56,7 @@ def main(self, *args, **kwargs) -> t.NoReturn:
# NOTE: This CLI option normalization applies to all subcommands.
context_settings={"token_normalize_func": lambda x: x.replace("_", "-")},
)
@click.option("--log-level", type=click.Choice(LEVELS.keys()))
@click.option("--log-level", type=click.Choice(tuple(LEVELS)))
@click.option(
"--log-config",
type=str,
Expand Down
2 changes: 1 addition & 1 deletion src/meltano/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def config( # noqa: WPS231
)
@click.option("--extras", is_flag=True)
@click.pass_context
def list_settings(ctx, extras: bool):
def list_settings(ctx: click.Context, extras: bool): # noqa: C901
"""List all settings for the specified plugin with their names, environment variables, and current values.""" # noqa: E501
settings = ctx.obj["settings"]
session = ctx.obj["session"]
Expand Down
2 changes: 1 addition & 1 deletion src/meltano/cli/elt.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ELOptions:
state = click.option("--state", help="Extractor state file.")
dump = click.option(
"--dump",
type=click.Choice(DUMPABLES.keys()),
type=click.Choice(tuple(DUMPABLES)),
help="Dump content of pipeline-specific generated file.",
)
state_id = click.option(
Expand Down
4 changes: 2 additions & 2 deletions src/meltano/cli/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,12 @@ def _validate_tasks(project: Project, task_set: TaskSets, ctx: click.Context) ->
blocks=blocks,
)
try:
block_parser = BlockParser(logger, project, blocks)
block_parser = BlockParser(logger, project, blocks) # type: ignore[arg-type]
parsed_blocks = list(block_parser.find_blocks(0))
tracker.add_contexts(PluginsTrackingContext.from_blocks(parsed_blocks))
except Exception as err:
tracker.track_command_event(CliEvent.aborted)
raise InvalidTasksError(task_set.name, err) from err
raise InvalidTasksError(task_set.name, str(err)) from err
if not validate_block_sets(logger, parsed_blocks):
tracker.track_command_event(CliEvent.aborted)
raise InvalidTasksError(
Expand Down
56 changes: 41 additions & 15 deletions src/meltano/cli/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from meltano.core.utils import coerce_datetime

if t.TYPE_CHECKING:
import datetime

from sqlalchemy.orm import Session

from meltano.core.project import Project
Expand Down Expand Up @@ -54,13 +56,13 @@ def schedule(project, ctx):


def _add_elt(
ctx,
ctx: click.Context,
name: str,
extractor: str,
loader: str,
transform: str,
interval: str,
start_date: str | None,
start_date: datetime.datetime | None,
):
"""Add a new legacy elt schedule."""
project: Project = ctx.obj["project"]
Expand Down Expand Up @@ -122,7 +124,16 @@ def _add_job(ctx, name: str, job: str, interval: str):
)
@click.option("--start-date", type=click.DateTime(), default=None, help="ELT Only")
@click.pass_context
def add(ctx, name, job, extractor, loader, transform, interval, start_date):
def add(
ctx: click.Context,
name: str,
job: str | None,
extractor: str | None,
loader: str | None,
transform: str,
interval: str,
start_date: datetime.datetime | None,
):
"""
Add a new schedule. Schedules can be used to run Meltano jobs or ELT tasks at a specific interval.
Expand Down Expand Up @@ -169,12 +180,13 @@ def _format_job_list_output(entry: Schedule, job: TaskSets) -> dict:

def _format_elt_list_output(entry: Schedule, session: Session) -> dict:
start_date = coerce_datetime(entry.start_date)
if start_date:
start_date = start_date.date().isoformat()
start_date_str = start_date.date().isoformat() if start_date else None

last_successful_run = entry.last_successful_run(session)
last_successful_run_ended_at = (
last_successful_run.ended_at.isoformat() if last_successful_run else None
last_successful_run.ended_at.isoformat()
if last_successful_run.ended_at
else None
)

return {
Expand All @@ -183,7 +195,7 @@ def _format_elt_list_output(entry: Schedule, session: Session) -> dict:
"loader": entry.loader,
"transform": entry.transform,
"interval": entry.interval,
"start_date": start_date,
"start_date": start_date_str,
"env": entry.env,
"cron_interval": entry.cron_interval,
"last_successful_run_ended_at": last_successful_run_ended_at,
Expand All @@ -193,11 +205,17 @@ def _format_elt_list_output(entry: Schedule, session: Session) -> dict:

@schedule.command( # noqa: WPS125
cls=PartialInstrumentedCmd,
name="list",
short_help="List available schedules.",
)
@click.option("--format", type=click.Choice(("json", "text")), default="text")
@click.option(
"--format",
"list_format",
type=click.Choice(("json", "text")),
default="text",
)
@click.pass_context
def list(ctx, format): # noqa: WPS125
def list_schedules(ctx: click.Context, list_format: str) -> None: # noqa: C901
"""List available schedules."""
project = ctx.obj["project"]
schedule_service: ScheduleService = ctx.obj["schedule_service"]
Expand All @@ -208,7 +226,7 @@ def list(ctx, format): # noqa: WPS125
try:
fail_stale_jobs(session)

if format == "text":
if list_format == "text":
transform_elt_markers = {
"run": ("→", "→"),
"only": ("×", "→"),
Expand All @@ -223,14 +241,14 @@ def list(ctx, format): # noqa: WPS125
f"{task_sets_service.get(txt_schedule.job).tasks}",
)
else:
markers = transform_elt_markers[txt_schedule.transform]
markers = transform_elt_markers[txt_schedule.transform] # type: ignore[index]
click.echo(
f"[{txt_schedule.interval}] elt {txt_schedule.name}: "
f"{txt_schedule.extractor} {markers[0]} "
f"{txt_schedule.loader} {markers[1]} transforms",
)

elif format == "json":
elif list_format == "json":
job_schedules = []
elt_schedules = []
for json_schedule in schedule_service.schedules():
Expand Down Expand Up @@ -263,7 +281,7 @@ def list(ctx, format): # noqa: WPS125
@click.argument("name")
@click.argument("elt_options", nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def run(ctx, name, elt_options):
def run(ctx: click.Context, name: str, elt_options: tuple[str]):
"""Run a schedule."""
schedule_service: ScheduleService = ctx.obj["schedule_service"]
process = schedule_service.run(schedule_service.find_schedule(name), *elt_options)
Expand Down Expand Up @@ -357,7 +375,7 @@ def _update_elt_schedule(


class CronParam(click.ParamType):
"""Custom type definition for cron prameter."""
"""Custom type definition for cron parameter."""

name = "cron"

Expand Down Expand Up @@ -390,7 +408,15 @@ def convert(self, value, *_):
help="Update the transform flag for an elt schedule.",
)
@click.pass_context
def set_cmd(ctx, name, interval, job, extractor, loader, transform):
def set_cmd(
ctx: click.Context,
name: str,
interval: str | None,
job: str | None,
extractor: str | None,
loader: str | None,
transform: str | None,
):
"""Update a schedule.
Usage:
Expand Down
2 changes: 1 addition & 1 deletion src/meltano/cli/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def select(
extractor: str,
entities_filter: str,
attributes_filter: str,
**flags: dict[str, bool],
**flags: bool,
):
"""
Manage extractor selection patterns.
Expand Down
12 changes: 8 additions & 4 deletions src/meltano/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def _prompt_plugin_capabilities(plugin_type):
)


def _prompt_plugin_settings(plugin_type):
def _prompt_plugin_settings(plugin_type: PluginType) -> list[dict[str, t.Any]]:
if plugin_type not in {
PluginType.EXTRACTORS,
PluginType.LOADERS,
Expand Down Expand Up @@ -239,7 +239,7 @@ def _prompt_plugin_settings(plugin_type):
"\nDefault: no settings\n",
)

settings: dict | None = None
settings = None
while settings is None: # noqa: WPS426 # allows lambda in loop
settings_input = click.prompt(
click.style("(settings)", fg="blue"),
Expand Down Expand Up @@ -304,11 +304,13 @@ def add_plugin( # noqa: C901
# exclude unspecified properties
plugin_definition.extras.clear()

plugin_attrs = plugin_definition.canonical()
plugin_attrs = plugin_definition.canonical() # type: ignore[assignment]

plugin_name = plugin_attrs.pop("name")
variant = plugin_attrs.pop("variant", variant)

plugin: ProjectContext | PluginRef

try:
plugin = add_service.add(
plugin_type,
Expand Down Expand Up @@ -640,7 +642,9 @@ def invoke(self, ctx: click.Context):
enact_environment_behavior(self.environment_behavior, ctx)
if ctx.obj.get("tracker"):
ctx.obj["tracker"].add_contexts(CliContext.from_click_context(ctx))
super().invoke(ctx)
# Typing these mixin hierarchies is a bit messy, so we'll just ignore it here
# https://mypy.readthedocs.io/en/latest/more_types.html#mixin-classes
super().invoke(ctx) # type: ignore


class InstrumentedDefaultGroup(InstrumentedGroupMixin, DefaultGroup, DYMGroup):
Expand Down
4 changes: 2 additions & 2 deletions src/meltano/cli/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
if t.TYPE_CHECKING:
from collections import abc

from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.orm.session import Session

from meltano.core.project import Project

Expand Down Expand Up @@ -116,7 +116,7 @@ def test(


async def _run_plugin_tests(
session: sessionmaker,
session: Session,
runners: abc.Iterable[ValidationsRunner],
) -> dict[str, dict[str, int]]:
return {runner.plugin_name: await runner.run_all(session) for runner in runners}
Expand Down
6 changes: 6 additions & 0 deletions src/meltano/core/block/blockset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

from __future__ import annotations

import typing as t
from abc import ABCMeta, abstractmethod

if t.TYPE_CHECKING:
from meltano.core.block.ioblock import IOBlock


class BlockSetValidationError(Exception):
"""Base exception when a block in a BlockSet violates the sets requirements."""
Expand All @@ -27,6 +31,8 @@ class BlockSet(metaclass=ABCMeta):
interface.
"""

blocks: tuple[IOBlock, ...]

@abstractmethod
async def run(self) -> None:
"""Do whatever a BlockSet is designed to do."""
Expand Down
2 changes: 1 addition & 1 deletion src/meltano/core/block/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def _expand_jobs(self, blocks: list[str], task_sets: TaskSetsService) -> list[st
def find_blocks(
self,
offset: int = 0,
) -> t.Generator[BlockSet | PluginCommandBlock, None, None]:
) -> t.Generator[BlockSet | PluginCommandBlock | ExtractLoadBlocks, None, None]:
"""Find all blocks in the invocation.
Args:
Expand Down
2 changes: 2 additions & 0 deletions src/meltano/core/block/plugin_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
class PluginCommandBlock(metaclass=ABCMeta):
"""Basic PluginCommand interface specification."""

string_id: str

@property
@abstractmethod
def name(self) -> str:
Expand Down
9 changes: 9 additions & 0 deletions src/meltano/core/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
class Schedule(NameEq, Canonical): # noqa: WPS230
"""A schedule is an elt command or a job configured to run at a certain interval."""

name: str
extractor: str | None
loader: str | None
transform: str | None
interval: str | None
start_date: datetime.datetime | None
job: str | None
env: dict[str, str]

def __init__(
self,
*,
Expand Down

0 comments on commit a396e3b

Please sign in to comment.