Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- [Improvement] Changes annotations from `typing` to use built-in generic types from `__future__.annotations` (by @Carlos-Muniz)
6 changes: 6 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
("py:class", "tutor.hooks.filters.P"),
("py:class", "tutor.hooks.filters.T"),
("py:class", "tutor.hooks.actions.P"),
("py:class", "P"),
("py:class", "P.args"),
("py:class", "P.kwargs"),
("py:class", "T"),
("py:class", "t.Any"),
("py:class", "t.Optional"),
]

# -- Sphinx-Click configuration
Expand Down
6 changes: 3 additions & 3 deletions tests/commands/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import typing as t
from __future__ import annotations

import click.testing

Expand All @@ -12,13 +12,13 @@ class TestCommandMixin:
"""

@staticmethod
def invoke(args: t.List[str]) -> click.testing.Result:
def invoke(args: list[str]) -> click.testing.Result:
with temporary_root() as root:
return TestCommandMixin.invoke_in_root(root, args)

@staticmethod
def invoke_in_root(
root: str, args: t.List[str], catch_exceptions: bool = True
root: str, args: list[str], catch_exceptions: bool = True
) -> click.testing.Result:
"""
Use this method for commands that all need to run in the same root:
Expand Down
15 changes: 7 additions & 8 deletions tests/commands/test_compose.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import typing as t
import unittest
from io import StringIO
Expand Down Expand Up @@ -62,9 +63,9 @@ def test_compose_local_tmp_generation(self, _mock_stdout: StringIO) -> None:
# Mount volumes
compose.mount_tmp_volumes(mount_args, LocalContext(""))

compose_file: t.Dict[str, t.Any] = hooks.Filters.COMPOSE_LOCAL_TMP.apply({})
actual_services: t.Dict[str, t.Any] = compose_file["services"]
expected_services: t.Dict[str, t.Any] = {
compose_file: dict[str, t.Any] = hooks.Filters.COMPOSE_LOCAL_TMP.apply({})
actual_services: dict[str, t.Any] = compose_file["services"]
expected_services: dict[str, t.Any] = {
"cms": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
"cms-worker": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
"lms": {
Expand All @@ -78,11 +79,9 @@ def test_compose_local_tmp_generation(self, _mock_stdout: StringIO) -> None:
}
self.assertEqual(actual_services, expected_services)

compose_jobs_file: t.Dict[
str, t.Any
] = hooks.Filters.COMPOSE_LOCAL_JOBS_TMP.apply({})
actual_jobs_services: t.Dict[str, t.Any] = compose_jobs_file["services"]
expected_jobs_services: t.Dict[str, t.Any] = {
compose_jobs_file = hooks.Filters.COMPOSE_LOCAL_JOBS_TMP.apply({})
actual_jobs_services = compose_jobs_file["services"]
expected_jobs_services: dict[str, t.Any] = {
"cms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
"lms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
}
Expand Down
5 changes: 3 additions & 2 deletions tests/hooks/test_filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import typing as t
import unittest

Expand All @@ -23,14 +24,14 @@ def filter1(value: int) -> int:

def test_add_items(self) -> None:
@hooks.filters.add("tests:add-sheeps")
def filter1(sheeps: t.List[int]) -> t.List[int]:
def filter1(sheeps: list[int]) -> list[int]:
return sheeps + [0]

hooks.filters.add_item("tests:add-sheeps", 1)
hooks.filters.add_item("tests:add-sheeps", 2)
hooks.filters.add_items("tests:add-sheeps", [3, 4])

sheeps: t.List[int] = hooks.filters.apply("tests:add-sheeps", [])
sheeps: list[int] = hooks.filters.apply("tests:add-sheeps", [])
self.assertEqual([0, 1, 2, 3, 4], sheeps)

def test_filter_callbacks(self) -> None:
Expand Down
5 changes: 2 additions & 3 deletions tests/test_plugins_v0.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import typing as t
from unittest.mock import patch

Expand Down Expand Up @@ -197,9 +198,7 @@ def test_dict_plugin(self) -> None:
{"name": "myplugin", "config": {"set": {"KEY": "value"}}, "version": "0.1"}
)
plugins.load("myplugin")
overriden_items: t.List[
t.Tuple[str, t.Any]
] = hooks.Filters.CONFIG_OVERRIDES.apply([])
overriden_items = hooks.Filters.CONFIG_OVERRIDES.apply([])
versions = list(plugins.iter_info())
self.assertEqual("myplugin", plugin.name)
self.assertEqual([("myplugin", "0.1")], versions)
Expand Down
3 changes: 2 additions & 1 deletion tutor/commands/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import sys
import typing as t

Expand Down Expand Up @@ -61,7 +62,7 @@ def ensure_plugins_enabled(cls, ctx: click.Context) -> None:
hooks.Actions.PROJECT_ROOT_READY.do(ctx.params["root"])
cls.IS_ROOT_READY = True

def list_commands(self, ctx: click.Context) -> t.List[str]:
def list_commands(self, ctx: click.Context) -> list[str]:
"""
This is run in the following cases:
- shell autocompletion: tutor <tab>
Expand Down
57 changes: 28 additions & 29 deletions tutor/commands/compose.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import os
import re
import typing as t
Expand All @@ -16,15 +17,15 @@
from tutor.tasks import BaseComposeTaskRunner
from tutor.types import Config

COMPOSE_FILTER_TYPE: TypeAlias = "hooks.filters.Filter[t.Dict[str, t.Any], []]"
COMPOSE_FILTER_TYPE: TypeAlias = "hooks.filters.Filter[dict[str, t.Any], []]"


class ComposeTaskRunner(BaseComposeTaskRunner):
def __init__(self, root: str, config: Config):
super().__init__(root, config)
self.project_name = ""
self.docker_compose_files: t.List[str] = []
self.docker_compose_job_files: t.List[str] = []
self.docker_compose_files: list[str] = []
self.docker_compose_job_files: list[str] = []

def docker_compose(self, *command: str) -> int:
"""
Expand Down Expand Up @@ -55,7 +56,7 @@ def update_docker_compose_tmp(
Update the contents of the docker-compose.tmp.yml and
docker-compose.jobs.tmp.yml files, which are generated at runtime.
"""
compose_base: t.Dict[str, t.Any] = {
compose_base: dict[str, t.Any] = {
"version": "{{ DOCKER_COMPOSE_VERSION }}",
"services": {},
}
Expand Down Expand Up @@ -134,20 +135,20 @@ def convert(
value: str,
param: t.Optional["click.Parameter"],
ctx: t.Optional[click.Context],
) -> t.List["MountType"]:
) -> list["MountType"]:
mounts = self.convert_explicit_form(value) or self.convert_implicit_form(value)
return mounts

def convert_explicit_form(self, value: str) -> t.List["MountParam.MountType"]:
def convert_explicit_form(self, value: str) -> list["MountParam.MountType"]:
"""
Argument is of the form "containers:/host/path:/container/path".
"""
match = re.match(self.PARAM_REGEXP, value)
if not match:
return []

mounts: t.List["MountParam.MountType"] = []
services: t.List[str] = [
mounts: list["MountParam.MountType"] = []
services: list[str] = [
service.strip() for service in match["services"].split(",")
]
host_path = os.path.abspath(os.path.expanduser(match["host_path"]))
Expand All @@ -159,11 +160,11 @@ def convert_explicit_form(self, value: str) -> t.List["MountParam.MountType"]:
mounts.append((service, host_path, container_path))
return mounts

def convert_implicit_form(self, value: str) -> t.List["MountParam.MountType"]:
def convert_implicit_form(self, value: str) -> list["MountParam.MountType"]:
"""
Argument is of the form "/host/path"
"""
mounts: t.List["MountParam.MountType"] = []
mounts: list["MountParam.MountType"] = []
host_path = os.path.abspath(os.path.expanduser(value))
for service, container_path in hooks.Filters.COMPOSE_MOUNTS.iterate(
os.path.basename(host_path)
Expand All @@ -175,7 +176,7 @@ def convert_implicit_form(self, value: str) -> t.List["MountParam.MountType"]:

def shell_complete(
self, ctx: click.Context, param: click.Parameter, incomplete: str
) -> t.List[CompletionItem]:
) -> list[CompletionItem]:
"""
Mount argument completion works only for the single path (implicit) form. The
reason is that colons break words in bash completion:
Expand All @@ -197,7 +198,7 @@ def shell_complete(


def mount_tmp_volumes(
all_mounts: t.Tuple[t.List[MountParam.MountType], ...],
all_mounts: tuple[list[MountParam.MountType], ...],
context: BaseComposeContext,
) -> None:
for mounts in all_mounts:
Expand Down Expand Up @@ -230,8 +231,8 @@ def mount_tmp_volume(

@compose_tmp_filter.add()
def _add_mounts_to_docker_compose_tmp(
docker_compose: t.Dict[str, t.Any],
) -> t.Dict[str, t.Any]:
docker_compose: dict[str, t.Any],
) -> dict[str, t.Any]:
services = docker_compose.setdefault("services", {})
services.setdefault(service, {"volumes": []})
services[service]["volumes"].append(f"{host_path}:{container_path}")
Expand All @@ -251,8 +252,8 @@ def start(
context: BaseComposeContext,
skip_build: bool,
detach: bool,
mounts: t.Tuple[t.List[MountParam.MountType]],
services: t.List[str],
mounts: tuple[list[MountParam.MountType]],
services: list[str],
) -> None:
command = ["up", "--remove-orphans"]
if not skip_build:
Expand All @@ -269,7 +270,7 @@ def start(
@click.command(help="Stop a running platform")
@click.argument("services", metavar="service", nargs=-1)
@click.pass_obj
def stop(context: BaseComposeContext, services: t.List[str]) -> None:
def stop(context: BaseComposeContext, services: list[str]) -> None:
config = tutor_config.load(context.root)
context.job_runner(config).docker_compose("stop", *services)

Expand All @@ -281,7 +282,7 @@ def stop(context: BaseComposeContext, services: t.List[str]) -> None:
@click.option("-d", "--detach", is_flag=True, help="Start in daemon mode")
@click.argument("services", metavar="service", nargs=-1)
@click.pass_context
def reboot(context: click.Context, detach: bool, services: t.List[str]) -> None:
def reboot(context: click.Context, detach: bool, services: list[str]) -> None:
context.invoke(stop, services=services)
context.invoke(start, detach=detach, services=services)

Expand All @@ -295,7 +296,7 @@ def reboot(context: click.Context, detach: bool, services: t.List[str]) -> None:
)
@click.argument("services", metavar="service", nargs=-1)
@click.pass_obj
def restart(context: BaseComposeContext, services: t.List[str]) -> None:
def restart(context: BaseComposeContext, services: list[str]) -> None:
config = tutor_config.load(context.root)
command = ["restart"]
if "all" in services:
Expand All @@ -315,9 +316,7 @@ def restart(context: BaseComposeContext, services: t.List[str]) -> None:
@jobs.do_group
@mount_option
@click.pass_obj
def do(
context: BaseComposeContext, mounts: t.Tuple[t.List[MountParam.MountType]]
) -> None:
def do(context: BaseComposeContext, mounts: tuple[list[MountParam.MountType]]) -> None:
"""
Run a custom job in the right container(s).
"""
Expand Down Expand Up @@ -345,8 +344,8 @@ def _mount_tmp_volumes(_job_name: str, *_args: t.Any, **_kwargs: t.Any) -> None:
@click.pass_context
def run(
context: click.Context,
mounts: t.Tuple[t.List[MountParam.MountType]],
args: t.List[str],
mounts: tuple[list[MountParam.MountType]],
args: list[str],
) -> None:
extra_args = ["--rm"]
if not utils.is_a_tty():
Expand Down Expand Up @@ -411,7 +410,7 @@ def copyfrom(
)
@click.argument("args", nargs=-1, required=True)
@click.pass_context
def execute(context: click.Context, args: t.List[str]) -> None:
def execute(context: click.Context, args: list[str]) -> None:
context.invoke(dc_command, command="exec", args=args)


Expand Down Expand Up @@ -454,9 +453,9 @@ def status(context: click.Context) -> None:
@click.pass_obj
def dc_command(
context: BaseComposeContext,
mounts: t.Tuple[t.List[MountParam.MountType]],
mounts: tuple[list[MountParam.MountType]],
command: str,
args: t.List[str],
args: list[str],
) -> None:
mount_tmp_volumes(mounts, context)
config = tutor_config.load(context.root)
Expand All @@ -465,8 +464,8 @@ def dc_command(

@hooks.Filters.COMPOSE_MOUNTS.add()
def _mount_edx_platform(
volumes: t.List[t.Tuple[str, str]], name: str
) -> t.List[t.Tuple[str, str]]:
volumes: list[tuple[str, str]], name: str
) -> list[tuple[str, str]]:
"""
When mounting edx-platform with `--mount=/path/to/edx-platform`, bind-mount the host
repo in the lms/cms containers.
Expand Down
11 changes: 6 additions & 5 deletions tutor/commands/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import json
import typing as t

Expand Down Expand Up @@ -27,7 +28,7 @@ class ConfigKeyParamType(click.ParamType):

def shell_complete(
self, ctx: click.Context, param: click.Parameter, incomplete: str
) -> t.List[click.shell_completion.CompletionItem]:
) -> list[click.shell_completion.CompletionItem]:
return [
click.shell_completion.CompletionItem(key)
for key, _value in self._shell_complete_config_items(ctx, incomplete)
Expand All @@ -36,7 +37,7 @@ def shell_complete(
@staticmethod
def _shell_complete_config_items(
ctx: click.Context, incomplete: str
) -> t.List[t.Tuple[str, ConfigValue]]:
) -> list[tuple[str, ConfigValue]]:
# Here we want to auto-complete the name of the config key. For that we need to
# figure out the list of enabled plugins, and for that we need the project root.
# The project root would ordinarily be stored in ctx.obj.root, but during
Expand All @@ -58,15 +59,15 @@ class ConfigKeyValParamType(ConfigKeyParamType):

name = "configkeyval"

def convert(self, value: str, param: t.Any, ctx: t.Any) -> t.Tuple[str, t.Any]:
def convert(self, value: str, param: t.Any, ctx: t.Any) -> tuple[str, t.Any]:
result = serialize.parse_key_value(value)
if result is None:
self.fail(f"'{value}' is not of the form 'key=value'.", param, ctx)
return result

def shell_complete(
self, ctx: click.Context, param: click.Parameter, incomplete: str
) -> t.List[click.shell_completion.CompletionItem]:
) -> list[click.shell_completion.CompletionItem]:
"""
Nice and friendly <KEY>=<VAL> auto-completion.
"""
Expand Down Expand Up @@ -117,7 +118,7 @@ def save(
context: Context,
interactive: bool,
set_vars: Config,
unset_vars: t.List[str],
unset_vars: list[str],
env_only: bool,
) -> None:
config = tutor_config.load_minimal(context.root)
Expand Down
4 changes: 2 additions & 2 deletions tutor/commands/dev.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import typing as t
from __future__ import annotations

import click

Expand Down Expand Up @@ -70,7 +70,7 @@ def launch(
context: click.Context,
non_interactive: bool,
pullimages: bool,
mounts: t.Tuple[t.List[compose.MountParam.MountType]],
mounts: tuple[list[compose.MountParam.MountType]],
) -> None:
compose.mount_tmp_volumes(mounts, context.obj)
try:
Expand Down
Loading