Browse Source

ref: require stronger types for sentry.runner.commands.* (#68130)

<!-- Describe your PR here. -->
anthony sottile 11 months ago
parent
commit
645e8ae6e3

+ 1 - 1
pyproject.toml

@@ -631,7 +631,7 @@ module = [
     "sentry.nodestore.models",
     "sentry.nodestore.models",
     "sentry.relay.config.metric_extraction",
     "sentry.relay.config.metric_extraction",
     "sentry.reprocessing2",
     "sentry.reprocessing2",
-    "sentry.runner.commands.backup",
+    "sentry.runner.commands.*",
     "sentry.snuba.metrics.extraction",
     "sentry.snuba.metrics.extraction",
     "sentry.tasks.on_demand_metrics",
     "sentry.tasks.on_demand_metrics",
     "sentry.tasks.reprocessing2",
     "sentry.tasks.reprocessing2",

+ 7 - 7
src/sentry/runner/commands/config.py

@@ -4,14 +4,14 @@ from sentry.runner.decorators import configuration
 
 
 
 
 @click.group()
 @click.group()
-def config():
+def config() -> None:
     "Manage runtime config options."
     "Manage runtime config options."
 
 
 
 
 @config.command()
 @config.command()
 @click.argument("pattern", default="*", required=False)
 @click.argument("pattern", default="*", required=False)
 @configuration
 @configuration
-def list(pattern):
+def list(pattern: str) -> None:
     "List configuration options."
     "List configuration options."
     from fnmatch import fnmatch
     from fnmatch import fnmatch
 
 
@@ -26,7 +26,7 @@ def list(pattern):
 @click.option("--silent", "-q", default=False, is_flag=True, help="Suppress extraneous output.")
 @click.option("--silent", "-q", default=False, is_flag=True, help="Suppress extraneous output.")
 @click.argument("option")
 @click.argument("option")
 @configuration
 @configuration
-def get(option, silent):
+def get(option: str, silent: bool) -> None:
     "Get a configuration option."
     "Get a configuration option."
     from django.conf import settings
     from django.conf import settings
 
 
@@ -58,7 +58,7 @@ def get(option, silent):
 @click.argument("key")
 @click.argument("key")
 @click.argument("value", required=False)
 @click.argument("value", required=False)
 @configuration
 @configuration
-def set(key, value, secret):
+def set(key: str, value: str | None, secret: bool) -> None:
     "Set a configuration option to a new value."
     "Set a configuration option to a new value."
     from sentry import options
     from sentry import options
     from sentry.options import UpdateChannel
     from sentry.options import UpdateChannel
@@ -82,7 +82,7 @@ def set(key, value, secret):
 @click.option("--no-input", default=False, is_flag=True, help="Do not show confirmation.")
 @click.option("--no-input", default=False, is_flag=True, help="Do not show confirmation.")
 @click.argument("option")
 @click.argument("option")
 @configuration
 @configuration
-def delete(option, no_input):
+def delete(option: str, no_input: bool) -> None:
     "Delete/unset a configuration option."
     "Delete/unset a configuration option."
     from sentry import options
     from sentry import options
     from sentry.options.manager import UnknownOption
     from sentry.options.manager import UnknownOption
@@ -158,7 +158,7 @@ def dump(flags: int, only_set: bool, pretty_print: bool) -> None:
 
 
 
 
 @config.command(name="generate-secret-key")
 @config.command(name="generate-secret-key")
-def generate_secret_key():
+def generate_secret_key() -> None:
     "Generate a new cryptographically secure secret key value."
     "Generate a new cryptographically secure secret key value."
     from sentry.runner.settings import generate_secret_key
     from sentry.runner.settings import generate_secret_key
 
 
@@ -166,7 +166,7 @@ def generate_secret_key():
 
 
 
 
 @config.command()
 @config.command()
-def discover():
+def discover() -> None:
     "Print paths to config files."
     "Print paths to config files."
     from sentry.runner.settings import discover_configs
     from sentry.runner.settings import discover_configs
 
 

+ 3 - 3
src/sentry/runner/commands/configoptions.py

@@ -80,7 +80,7 @@ def _attempt_update(
 @log_options()
 @log_options()
 @click.pass_context
 @click.pass_context
 @configuration
 @configuration
-def configoptions(ctx, dry_run: bool, file: str | None, hide_drift: bool) -> None:
+def configoptions(ctx: click.Context, dry_run: bool, file: str | None, hide_drift: bool) -> None:
     """
     """
     Makes changes to options in bulk starting from a yaml file.
     Makes changes to options in bulk starting from a yaml file.
     Contrarily to the `config` command, this is meant to perform
     Contrarily to the `config` command, this is meant to perform
@@ -155,7 +155,7 @@ def configoptions(ctx, dry_run: bool, file: str | None, hide_drift: bool) -> Non
 @configoptions.command()
 @configoptions.command()
 @click.pass_context
 @click.pass_context
 @configuration
 @configuration
-def patch(ctx) -> None:
+def patch(ctx: click.Context) -> None:
     """
     """
     Applies to the DB the option values found in the config file.
     Applies to the DB the option values found in the config file.
     Only the options present in the file are updated. No deletions
     Only the options present in the file are updated. No deletions
@@ -217,7 +217,7 @@ def patch(ctx) -> None:
 @configoptions.command()
 @configoptions.command()
 @click.pass_context
 @click.pass_context
 @configuration
 @configuration
-def sync(ctx):
+def sync(ctx: click.Context) -> None:
     """
     """
     Synchronizes the content of the file with the DB. The source of
     Synchronizes the content of the file with the DB. The source of
     truth is the config file, not the DB. If an option is missing in
     truth is the config file, not the DB. If an option is missing in

+ 29 - 7
src/sentry/runner/commands/createuser.py

@@ -1,15 +1,28 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
 import click
 import click
 
 
 from sentry.runner.decorators import configuration
 from sentry.runner.decorators import configuration
 
 
+if TYPE_CHECKING:
+    from django.db.models.fields import Field
+
+    from sentry.models.user import User
+
+
+def _get_field(field_name: str) -> Field[str, str]:
+    from django.db.models.fields import Field
 
 
-def _get_field(field_name):
     from sentry.models.user import User
     from sentry.models.user import User
 
 
-    return User._meta.get_field(field_name)
+    ret = User._meta.get_field(field_name)
+    assert isinstance(ret, Field), ret
+    return ret
 
 
 
 
-def _get_email():
+def _get_email() -> list[str]:
     from django.core.exceptions import ValidationError
     from django.core.exceptions import ValidationError
 
 
     rv = click.prompt("Email")
     rv = click.prompt("Email")
@@ -20,7 +33,7 @@ def _get_email():
         raise click.ClickException("; ".join(e.messages))
         raise click.ClickException("; ".join(e.messages))
 
 
 
 
-def _get_password():
+def _get_password() -> str:
     from django.core.exceptions import ValidationError
     from django.core.exceptions import ValidationError
 
 
     rv = click.prompt("Password", hide_input=True, confirmation_prompt=True)
     rv = click.prompt("Password", hide_input=True, confirmation_prompt=True)
@@ -31,11 +44,11 @@ def _get_password():
         raise click.ClickException("; ".join(e.messages))
         raise click.ClickException("; ".join(e.messages))
 
 
 
 
-def _get_superuser():
+def _get_superuser() -> bool:
     return click.confirm("Should this user be a superuser?", default=False)
     return click.confirm("Should this user be a superuser?", default=False)
 
 
 
 
-def _set_superadmin(user):
+def _set_superadmin(user: User) -> None:
     """
     """
     superadmin role approximates superuser (model attribute) but leveraging
     superadmin role approximates superuser (model attribute) but leveraging
     Sentry's role system.
     Sentry's role system.
@@ -78,7 +91,16 @@ def _set_superadmin(user):
     "--force-update", default=False, is_flag=True, help="If true, will update existing users."
     "--force-update", default=False, is_flag=True, help="If true, will update existing users."
 )
 )
 @configuration
 @configuration
-def createuser(emails, org_id, password, superuser, staff, no_password, no_input, force_update):
+def createuser(
+    emails: list[str] | None,
+    org_id: str | None,
+    password: str | None,
+    superuser: bool | None,
+    staff: bool | None,
+    no_password: bool,
+    no_input: bool,
+    force_update: bool,
+) -> None:
     "Create a new user."
     "Create a new user."
 
 
     from django.conf import settings
     from django.conf import settings

+ 1 - 1
src/sentry/runner/commands/devservices.py

@@ -166,7 +166,7 @@ def ensure_interface(ports: dict[str, int | tuple[str, int]]) -> dict[str, tuple
     return rv
     return rv
 
 
 
 
-def ensure_docker_cli_context(context: str):
+def ensure_docker_cli_context(context: str) -> None:
     # this is faster than running docker context use ...
     # this is faster than running docker context use ...
     config_file = os.path.expanduser("~/.docker/config.json")
     config_file = os.path.expanduser("~/.docker/config.json")
     config = {}
     config = {}

+ 2 - 2
src/sentry/runner/commands/django.py

@@ -7,8 +7,8 @@ from sentry.runner.decorators import configuration
 @click.argument("management_args", nargs=-1, type=click.UNPROCESSED)
 @click.argument("management_args", nargs=-1, type=click.UNPROCESSED)
 @configuration
 @configuration
 @click.pass_context
 @click.pass_context
-def django(ctx, management_args):
+def django(ctx: click.Context, management_args: tuple[str, ...]) -> None:
     "Execute Django subcommands."
     "Execute Django subcommands."
     from django.core.management import execute_from_command_line
     from django.core.management import execute_from_command_line
 
 
-    execute_from_command_line(argv=[ctx.command_path] + list(management_args))
+    execute_from_command_line(argv=[ctx.command_path, *management_args])

+ 1 - 1
src/sentry/runner/commands/exec.py

@@ -20,7 +20,7 @@ except Exception:
 )
 )
 @click.option("-c", default="", help="Read script from string.")
 @click.option("-c", default="", help="Read script from string.")
 @click.argument("file", default=None, required=False)
 @click.argument("file", default=None, required=False)
-def exec_(c, file):
+def exec_(c: str, file: str | None) -> None:
     """
     """
     Execute a script.
     Execute a script.
 
 

+ 3 - 3
src/sentry/runner/commands/execfile.py

@@ -8,7 +8,7 @@ import click
     name="execfile", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True)
     name="execfile", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True)
 )
 )
 @click.argument("filename", required=True)
 @click.argument("filename", required=True)
-def execfile(filename):
+def execfile(filename: str) -> None:
     """Execute a script.
     """Execute a script.
 
 
     This is very similar to `exec`, with the following differences:
     This is very similar to `exec`, with the following differences:
@@ -27,7 +27,7 @@ def execfile(filename):
 
 
     - __file__ is set to the filename of the script.
     - __file__ is set to the filename of the script.
     """
     """
-    filename = pathlib.Path(filename)
+    filename_src = pathlib.Path(filename).read_text()
     preamble = "\n".join(
     preamble = "\n".join(
         [
         [
             "from sentry.runner import configure; configure()",
             "from sentry.runner import configure; configure()",
@@ -39,5 +39,5 @@ def execfile(filename):
     preamble_code = compile(preamble, filename, "exec")
     preamble_code = compile(preamble, filename, "exec")
     exec(preamble_code, script_globals, script_globals)
     exec(preamble_code, script_globals, script_globals)
     sys.argv = sys.argv[1:]
     sys.argv = sys.argv[1:]
-    script_code = compile(filename.read_text(), filename, "exec")
+    script_code = compile(filename_src, filename, "exec")
     exec(script_code, script_globals, script_globals)
     exec(script_code, script_globals, script_globals)

+ 3 - 3
src/sentry/runner/commands/files.py

@@ -6,14 +6,14 @@ from sentry.utils import json
 
 
 
 
 @click.group()
 @click.group()
-def files():
+def files() -> None:
     """Manage files from filestore."""
     """Manage files from filestore."""
 
 
 
 
 @files.command()
 @files.command()
 @click.argument("id", type=click.INT, metavar="FILE_ID")
 @click.argument("id", type=click.INT, metavar="FILE_ID")
 @configuration
 @configuration
-def get(id):
+def get(id: int) -> None:
     """Fetch a file's contents by id."""
     """Fetch a file's contents by id."""
     from sentry.models.files.file import File
     from sentry.models.files.file import File
 
 
@@ -33,7 +33,7 @@ def get(id):
 @click.argument("id", type=click.INT, metavar="FILE_ID")
 @click.argument("id", type=click.INT, metavar="FILE_ID")
 @click.option("--format", default="json", type=click.Choice(("json", "yaml")))
 @click.option("--format", default="json", type=click.Choice(("json", "yaml")))
 @configuration
 @configuration
-def info(id, format):
+def info(id: int, format: str) -> None:
     """Show a file's metadata by id."""
     """Show a file's metadata by id."""
     from sentry.models.files.file import File
     from sentry.models.files.file import File
 
 

+ 2 - 1
src/sentry/runner/commands/help.py

@@ -3,6 +3,7 @@ import click
 
 
 @click.command()
 @click.command()
 @click.pass_context
 @click.pass_context
-def help(ctx):
+def help(ctx: click.Context) -> None:
     "Show this message and exit."
     "Show this message and exit."
+    assert ctx.parent is not None
     click.echo(ctx.parent.get_help())
     click.echo(ctx.parent.get_help())

Some files were not shown because too many files changed in this diff