mirror of
https://github.com/ChristianLight/tutor.git
synced 2025-01-22 13:18:24 +00:00
refactor: annotation with __future__.annotations
Adds `from __future__ import annotations` to the top of every module, right below the module's docstring. Replaces any usages of t.List, t.Dict, t.Set, t.Tuple, and t.Type with their built-in equivalents: list, dict, set, tuple, and type. Ensures that make test still passes under Python 3.7, 3.8 and 3.9.
This commit is contained in:
parent
d629ca932c
commit
ac1a875f42
@ -0,0 +1 @@
|
|||||||
|
- [Improvement] Changes annotations from `typing` to use built-in generic types from `__future__.annotations` (by @Carlos-Muniz)
|
@ -38,6 +38,12 @@ nitpick_ignore = [
|
|||||||
("py:class", "tutor.hooks.filters.P"),
|
("py:class", "tutor.hooks.filters.P"),
|
||||||
("py:class", "tutor.hooks.filters.T"),
|
("py:class", "tutor.hooks.filters.T"),
|
||||||
("py:class", "tutor.hooks.actions.P"),
|
("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
|
# -- Sphinx-Click configuration
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import typing as t
|
from __future__ import annotations
|
||||||
|
|
||||||
import click.testing
|
import click.testing
|
||||||
|
|
||||||
@ -12,13 +12,13 @@ class TestCommandMixin:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def invoke(args: t.List[str]) -> click.testing.Result:
|
def invoke(args: list[str]) -> click.testing.Result:
|
||||||
with temporary_root() as root:
|
with temporary_root() as root:
|
||||||
return TestCommandMixin.invoke_in_root(root, args)
|
return TestCommandMixin.invoke_in_root(root, args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def invoke_in_root(
|
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:
|
) -> click.testing.Result:
|
||||||
"""
|
"""
|
||||||
Use this method for commands that all need to run in the same root:
|
Use this method for commands that all need to run in the same root:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
import unittest
|
import unittest
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
@ -62,9 +63,9 @@ class ComposeTests(unittest.TestCase):
|
|||||||
# Mount volumes
|
# Mount volumes
|
||||||
compose.mount_tmp_volumes(mount_args, LocalContext(""))
|
compose.mount_tmp_volumes(mount_args, LocalContext(""))
|
||||||
|
|
||||||
compose_file: t.Dict[str, t.Any] = hooks.Filters.COMPOSE_LOCAL_TMP.apply({})
|
compose_file: dict[str, t.Any] = hooks.Filters.COMPOSE_LOCAL_TMP.apply({})
|
||||||
actual_services: t.Dict[str, t.Any] = compose_file["services"]
|
actual_services: dict[str, t.Any] = compose_file["services"]
|
||||||
expected_services: t.Dict[str, t.Any] = {
|
expected_services: dict[str, t.Any] = {
|
||||||
"cms": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
"cms": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
||||||
"cms-worker": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
"cms-worker": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
||||||
"lms": {
|
"lms": {
|
||||||
@ -78,11 +79,9 @@ class ComposeTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.assertEqual(actual_services, expected_services)
|
self.assertEqual(actual_services, expected_services)
|
||||||
|
|
||||||
compose_jobs_file: t.Dict[
|
compose_jobs_file = hooks.Filters.COMPOSE_LOCAL_JOBS_TMP.apply({})
|
||||||
str, t.Any
|
actual_jobs_services = compose_jobs_file["services"]
|
||||||
] = hooks.Filters.COMPOSE_LOCAL_JOBS_TMP.apply({})
|
expected_jobs_services: dict[str, t.Any] = {
|
||||||
actual_jobs_services: t.Dict[str, t.Any] = compose_jobs_file["services"]
|
|
||||||
expected_jobs_services: t.Dict[str, t.Any] = {
|
|
||||||
"cms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
"cms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
||||||
"lms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
"lms-job": {"volumes": ["/path/to/edx-platform:/openedx/edx-platform"]},
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@ -23,14 +24,14 @@ class PluginFiltersTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_add_items(self) -> None:
|
def test_add_items(self) -> None:
|
||||||
@hooks.filters.add("tests:add-sheeps")
|
@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]
|
return sheeps + [0]
|
||||||
|
|
||||||
hooks.filters.add_item("tests:add-sheeps", 1)
|
hooks.filters.add_item("tests:add-sheeps", 1)
|
||||||
hooks.filters.add_item("tests:add-sheeps", 2)
|
hooks.filters.add_item("tests:add-sheeps", 2)
|
||||||
hooks.filters.add_items("tests:add-sheeps", [3, 4])
|
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)
|
self.assertEqual([0, 1, 2, 3, 4], sheeps)
|
||||||
|
|
||||||
def test_filter_callbacks(self) -> None:
|
def test_filter_callbacks(self) -> None:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -197,9 +198,7 @@ class PluginsTests(PluginsTestCase):
|
|||||||
{"name": "myplugin", "config": {"set": {"KEY": "value"}}, "version": "0.1"}
|
{"name": "myplugin", "config": {"set": {"KEY": "value"}}, "version": "0.1"}
|
||||||
)
|
)
|
||||||
plugins.load("myplugin")
|
plugins.load("myplugin")
|
||||||
overriden_items: t.List[
|
overriden_items = hooks.Filters.CONFIG_OVERRIDES.apply([])
|
||||||
t.Tuple[str, t.Any]
|
|
||||||
] = hooks.Filters.CONFIG_OVERRIDES.apply([])
|
|
||||||
versions = list(plugins.iter_info())
|
versions = list(plugins.iter_info())
|
||||||
self.assertEqual("myplugin", plugin.name)
|
self.assertEqual("myplugin", plugin.name)
|
||||||
self.assertEqual([("myplugin", "0.1")], versions)
|
self.assertEqual([("myplugin", "0.1")], versions)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ class TutorCli(click.MultiCommand):
|
|||||||
hooks.Actions.PROJECT_ROOT_READY.do(ctx.params["root"])
|
hooks.Actions.PROJECT_ROOT_READY.do(ctx.params["root"])
|
||||||
cls.IS_ROOT_READY = True
|
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:
|
This is run in the following cases:
|
||||||
- shell autocompletion: tutor <tab>
|
- shell autocompletion: tutor <tab>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import typing as t
|
import typing as t
|
||||||
@ -16,15 +17,15 @@ from tutor.exceptions import TutorError
|
|||||||
from tutor.tasks import BaseComposeTaskRunner
|
from tutor.tasks import BaseComposeTaskRunner
|
||||||
from tutor.types import Config
|
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):
|
class ComposeTaskRunner(BaseComposeTaskRunner):
|
||||||
def __init__(self, root: str, config: Config):
|
def __init__(self, root: str, config: Config):
|
||||||
super().__init__(root, config)
|
super().__init__(root, config)
|
||||||
self.project_name = ""
|
self.project_name = ""
|
||||||
self.docker_compose_files: t.List[str] = []
|
self.docker_compose_files: list[str] = []
|
||||||
self.docker_compose_job_files: t.List[str] = []
|
self.docker_compose_job_files: list[str] = []
|
||||||
|
|
||||||
def docker_compose(self, *command: str) -> int:
|
def docker_compose(self, *command: str) -> int:
|
||||||
"""
|
"""
|
||||||
@ -55,7 +56,7 @@ class ComposeTaskRunner(BaseComposeTaskRunner):
|
|||||||
Update the contents of the docker-compose.tmp.yml and
|
Update the contents of the docker-compose.tmp.yml and
|
||||||
docker-compose.jobs.tmp.yml files, which are generated at runtime.
|
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 }}",
|
"version": "{{ DOCKER_COMPOSE_VERSION }}",
|
||||||
"services": {},
|
"services": {},
|
||||||
}
|
}
|
||||||
@ -134,11 +135,11 @@ class MountParam(click.ParamType):
|
|||||||
value: str,
|
value: str,
|
||||||
param: t.Optional["click.Parameter"],
|
param: t.Optional["click.Parameter"],
|
||||||
ctx: t.Optional[click.Context],
|
ctx: t.Optional[click.Context],
|
||||||
) -> t.List["MountType"]:
|
) -> list["MountType"]:
|
||||||
mounts = self.convert_explicit_form(value) or self.convert_implicit_form(value)
|
mounts = self.convert_explicit_form(value) or self.convert_implicit_form(value)
|
||||||
return mounts
|
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".
|
Argument is of the form "containers:/host/path:/container/path".
|
||||||
"""
|
"""
|
||||||
@ -146,8 +147,8 @@ class MountParam(click.ParamType):
|
|||||||
if not match:
|
if not match:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
mounts: t.List["MountParam.MountType"] = []
|
mounts: list["MountParam.MountType"] = []
|
||||||
services: t.List[str] = [
|
services: list[str] = [
|
||||||
service.strip() for service in match["services"].split(",")
|
service.strip() for service in match["services"].split(",")
|
||||||
]
|
]
|
||||||
host_path = os.path.abspath(os.path.expanduser(match["host_path"]))
|
host_path = os.path.abspath(os.path.expanduser(match["host_path"]))
|
||||||
@ -159,11 +160,11 @@ class MountParam(click.ParamType):
|
|||||||
mounts.append((service, host_path, container_path))
|
mounts.append((service, host_path, container_path))
|
||||||
return mounts
|
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"
|
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))
|
host_path = os.path.abspath(os.path.expanduser(value))
|
||||||
for service, container_path in hooks.Filters.COMPOSE_MOUNTS.iterate(
|
for service, container_path in hooks.Filters.COMPOSE_MOUNTS.iterate(
|
||||||
os.path.basename(host_path)
|
os.path.basename(host_path)
|
||||||
@ -175,7 +176,7 @@ class MountParam(click.ParamType):
|
|||||||
|
|
||||||
def shell_complete(
|
def shell_complete(
|
||||||
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
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
|
Mount argument completion works only for the single path (implicit) form. The
|
||||||
reason is that colons break words in bash completion:
|
reason is that colons break words in bash completion:
|
||||||
@ -197,7 +198,7 @@ mount_option = click.option(
|
|||||||
|
|
||||||
|
|
||||||
def mount_tmp_volumes(
|
def mount_tmp_volumes(
|
||||||
all_mounts: t.Tuple[t.List[MountParam.MountType], ...],
|
all_mounts: tuple[list[MountParam.MountType], ...],
|
||||||
context: BaseComposeContext,
|
context: BaseComposeContext,
|
||||||
) -> None:
|
) -> None:
|
||||||
for mounts in all_mounts:
|
for mounts in all_mounts:
|
||||||
@ -230,8 +231,8 @@ def mount_tmp_volume(
|
|||||||
|
|
||||||
@compose_tmp_filter.add()
|
@compose_tmp_filter.add()
|
||||||
def _add_mounts_to_docker_compose_tmp(
|
def _add_mounts_to_docker_compose_tmp(
|
||||||
docker_compose: t.Dict[str, t.Any],
|
docker_compose: dict[str, t.Any],
|
||||||
) -> t.Dict[str, t.Any]:
|
) -> dict[str, t.Any]:
|
||||||
services = docker_compose.setdefault("services", {})
|
services = docker_compose.setdefault("services", {})
|
||||||
services.setdefault(service, {"volumes": []})
|
services.setdefault(service, {"volumes": []})
|
||||||
services[service]["volumes"].append(f"{host_path}:{container_path}")
|
services[service]["volumes"].append(f"{host_path}:{container_path}")
|
||||||
@ -251,8 +252,8 @@ def start(
|
|||||||
context: BaseComposeContext,
|
context: BaseComposeContext,
|
||||||
skip_build: bool,
|
skip_build: bool,
|
||||||
detach: bool,
|
detach: bool,
|
||||||
mounts: t.Tuple[t.List[MountParam.MountType]],
|
mounts: tuple[list[MountParam.MountType]],
|
||||||
services: t.List[str],
|
services: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
command = ["up", "--remove-orphans"]
|
command = ["up", "--remove-orphans"]
|
||||||
if not skip_build:
|
if not skip_build:
|
||||||
@ -269,7 +270,7 @@ def start(
|
|||||||
@click.command(help="Stop a running platform")
|
@click.command(help="Stop a running platform")
|
||||||
@click.argument("services", metavar="service", nargs=-1)
|
@click.argument("services", metavar="service", nargs=-1)
|
||||||
@click.pass_obj
|
@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)
|
config = tutor_config.load(context.root)
|
||||||
context.job_runner(config).docker_compose("stop", *services)
|
context.job_runner(config).docker_compose("stop", *services)
|
||||||
|
|
||||||
@ -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.option("-d", "--detach", is_flag=True, help="Start in daemon mode")
|
||||||
@click.argument("services", metavar="service", nargs=-1)
|
@click.argument("services", metavar="service", nargs=-1)
|
||||||
@click.pass_context
|
@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(stop, services=services)
|
||||||
context.invoke(start, detach=detach, services=services)
|
context.invoke(start, detach=detach, services=services)
|
||||||
|
|
||||||
@ -295,7 +296,7 @@ fully stop the platform, use the 'reboot' command.""",
|
|||||||
)
|
)
|
||||||
@click.argument("services", metavar="service", nargs=-1)
|
@click.argument("services", metavar="service", nargs=-1)
|
||||||
@click.pass_obj
|
@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)
|
config = tutor_config.load(context.root)
|
||||||
command = ["restart"]
|
command = ["restart"]
|
||||||
if "all" in services:
|
if "all" in services:
|
||||||
@ -315,9 +316,7 @@ def restart(context: BaseComposeContext, services: t.List[str]) -> None:
|
|||||||
@jobs.do_group
|
@jobs.do_group
|
||||||
@mount_option
|
@mount_option
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def do(
|
def do(context: BaseComposeContext, mounts: tuple[list[MountParam.MountType]]) -> None:
|
||||||
context: BaseComposeContext, mounts: t.Tuple[t.List[MountParam.MountType]]
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Run a custom job in the right container(s).
|
Run a custom job in the right container(s).
|
||||||
"""
|
"""
|
||||||
@ -345,8 +344,8 @@ def do(
|
|||||||
@click.pass_context
|
@click.pass_context
|
||||||
def run(
|
def run(
|
||||||
context: click.Context,
|
context: click.Context,
|
||||||
mounts: t.Tuple[t.List[MountParam.MountType]],
|
mounts: tuple[list[MountParam.MountType]],
|
||||||
args: t.List[str],
|
args: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
extra_args = ["--rm"]
|
extra_args = ["--rm"]
|
||||||
if not utils.is_a_tty():
|
if not utils.is_a_tty():
|
||||||
@ -411,7 +410,7 @@ def copyfrom(
|
|||||||
)
|
)
|
||||||
@click.argument("args", nargs=-1, required=True)
|
@click.argument("args", nargs=-1, required=True)
|
||||||
@click.pass_context
|
@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)
|
context.invoke(dc_command, command="exec", args=args)
|
||||||
|
|
||||||
|
|
||||||
@ -454,9 +453,9 @@ def status(context: click.Context) -> None:
|
|||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def dc_command(
|
def dc_command(
|
||||||
context: BaseComposeContext,
|
context: BaseComposeContext,
|
||||||
mounts: t.Tuple[t.List[MountParam.MountType]],
|
mounts: tuple[list[MountParam.MountType]],
|
||||||
command: str,
|
command: str,
|
||||||
args: t.List[str],
|
args: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
mount_tmp_volumes(mounts, context)
|
mount_tmp_volumes(mounts, context)
|
||||||
config = tutor_config.load(context.root)
|
config = tutor_config.load(context.root)
|
||||||
@ -465,8 +464,8 @@ def dc_command(
|
|||||||
|
|
||||||
@hooks.Filters.COMPOSE_MOUNTS.add()
|
@hooks.Filters.COMPOSE_MOUNTS.add()
|
||||||
def _mount_edx_platform(
|
def _mount_edx_platform(
|
||||||
volumes: t.List[t.Tuple[str, str]], name: str
|
volumes: list[tuple[str, str]], name: str
|
||||||
) -> t.List[t.Tuple[str, str]]:
|
) -> list[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
When mounting edx-platform with `--mount=/path/to/edx-platform`, bind-mount the host
|
When mounting edx-platform with `--mount=/path/to/edx-platform`, bind-mount the host
|
||||||
repo in the lms/cms containers.
|
repo in the lms/cms containers.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ class ConfigKeyParamType(click.ParamType):
|
|||||||
|
|
||||||
def shell_complete(
|
def shell_complete(
|
||||||
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
||||||
) -> t.List[click.shell_completion.CompletionItem]:
|
) -> list[click.shell_completion.CompletionItem]:
|
||||||
return [
|
return [
|
||||||
click.shell_completion.CompletionItem(key)
|
click.shell_completion.CompletionItem(key)
|
||||||
for key, _value in self._shell_complete_config_items(ctx, incomplete)
|
for key, _value in self._shell_complete_config_items(ctx, incomplete)
|
||||||
@ -36,7 +37,7 @@ class ConfigKeyParamType(click.ParamType):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _shell_complete_config_items(
|
def _shell_complete_config_items(
|
||||||
ctx: click.Context, incomplete: str
|
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
|
# 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.
|
# 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
|
# The project root would ordinarily be stored in ctx.obj.root, but during
|
||||||
@ -58,7 +59,7 @@ class ConfigKeyValParamType(ConfigKeyParamType):
|
|||||||
|
|
||||||
name = "configkeyval"
|
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)
|
result = serialize.parse_key_value(value)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.fail(f"'{value}' is not of the form 'key=value'.", param, ctx)
|
self.fail(f"'{value}' is not of the form 'key=value'.", param, ctx)
|
||||||
@ -66,7 +67,7 @@ class ConfigKeyValParamType(ConfigKeyParamType):
|
|||||||
|
|
||||||
def shell_complete(
|
def shell_complete(
|
||||||
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
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.
|
Nice and friendly <KEY>=<VAL> auto-completion.
|
||||||
"""
|
"""
|
||||||
@ -117,7 +118,7 @@ def save(
|
|||||||
context: Context,
|
context: Context,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
set_vars: Config,
|
set_vars: Config,
|
||||||
unset_vars: t.List[str],
|
unset_vars: list[str],
|
||||||
env_only: bool,
|
env_only: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = tutor_config.load_minimal(context.root)
|
config = tutor_config.load_minimal(context.root)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import typing as t
|
from __future__ import annotations
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ def launch(
|
|||||||
context: click.Context,
|
context: click.Context,
|
||||||
non_interactive: bool,
|
non_interactive: bool,
|
||||||
pullimages: bool,
|
pullimages: bool,
|
||||||
mounts: t.Tuple[t.List[compose.MountParam.MountType]],
|
mounts: tuple[list[compose.MountParam.MountType]],
|
||||||
) -> None:
|
) -> None:
|
||||||
compose.mount_tmp_volumes(mounts, context.obj)
|
compose.mount_tmp_volumes(mounts, context.obj)
|
||||||
try:
|
try:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@ -21,9 +22,9 @@ VENDOR_IMAGES = [
|
|||||||
|
|
||||||
@hooks.Filters.IMAGES_BUILD.add()
|
@hooks.Filters.IMAGES_BUILD.add()
|
||||||
def _add_core_images_to_build(
|
def _add_core_images_to_build(
|
||||||
build_images: t.List[t.Tuple[str, t.Tuple[str, ...], str, t.Tuple[str, ...]]],
|
build_images: list[tuple[str, tuple[str, ...], str, tuple[str, ...]]],
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> t.List[t.Tuple[str, t.Tuple[str, ...], str, t.Tuple[str, ...]]]:
|
) -> list[tuple[str, tuple[str, ...], str, tuple[str, ...]]]:
|
||||||
"""
|
"""
|
||||||
Add base images to the list of Docker images to build on `tutor build all`.
|
Add base images to the list of Docker images to build on `tutor build all`.
|
||||||
"""
|
"""
|
||||||
@ -35,8 +36,8 @@ def _add_core_images_to_build(
|
|||||||
|
|
||||||
@hooks.Filters.IMAGES_PULL.add()
|
@hooks.Filters.IMAGES_PULL.add()
|
||||||
def _add_images_to_pull(
|
def _add_images_to_pull(
|
||||||
remote_images: t.List[t.Tuple[str, str]], config: Config
|
remote_images: list[tuple[str, str]], config: Config
|
||||||
) -> t.List[t.Tuple[str, str]]:
|
) -> list[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Add base and vendor images to the list of Docker images to pull on `tutor pull all`.
|
Add base and vendor images to the list of Docker images to pull on `tutor pull all`.
|
||||||
"""
|
"""
|
||||||
@ -50,8 +51,8 @@ def _add_images_to_pull(
|
|||||||
|
|
||||||
@hooks.Filters.IMAGES_PUSH.add()
|
@hooks.Filters.IMAGES_PUSH.add()
|
||||||
def _add_core_images_to_push(
|
def _add_core_images_to_push(
|
||||||
remote_images: t.List[t.Tuple[str, str]], config: Config
|
remote_images: list[tuple[str, str]], config: Config
|
||||||
) -> t.List[t.Tuple[str, str]]:
|
) -> list[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Add base images to the list of Docker images to push on `tutor push all`.
|
Add base images to the list of Docker images to push on `tutor push all`.
|
||||||
"""
|
"""
|
||||||
@ -100,12 +101,12 @@ def images_command() -> None:
|
|||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def build(
|
def build(
|
||||||
context: Context,
|
context: Context,
|
||||||
image_names: t.List[str],
|
image_names: list[str],
|
||||||
no_cache: bool,
|
no_cache: bool,
|
||||||
build_args: t.List[str],
|
build_args: list[str],
|
||||||
add_hosts: t.List[str],
|
add_hosts: list[str],
|
||||||
target: str,
|
target: str,
|
||||||
docker_args: t.List[str],
|
docker_args: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
config = tutor_config.load(context.root)
|
config = tutor_config.load(context.root)
|
||||||
command_args = []
|
command_args = []
|
||||||
@ -132,7 +133,7 @@ def build(
|
|||||||
@click.command(short_help="Pull images from the Docker registry")
|
@click.command(short_help="Pull images from the Docker registry")
|
||||||
@click.argument("image_names", metavar="image", nargs=-1)
|
@click.argument("image_names", metavar="image", nargs=-1)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def pull(context: Context, image_names: t.List[str]) -> None:
|
def pull(context: Context, image_names: list[str]) -> None:
|
||||||
config = tutor_config.load_full(context.root)
|
config = tutor_config.load_full(context.root)
|
||||||
for image in image_names:
|
for image in image_names:
|
||||||
for tag in find_remote_image_tags(config, hooks.Filters.IMAGES_PULL, image):
|
for tag in find_remote_image_tags(config, hooks.Filters.IMAGES_PULL, image):
|
||||||
@ -142,7 +143,7 @@ def pull(context: Context, image_names: t.List[str]) -> None:
|
|||||||
@click.command(short_help="Push images to the Docker registry")
|
@click.command(short_help="Push images to the Docker registry")
|
||||||
@click.argument("image_names", metavar="image", nargs=-1)
|
@click.argument("image_names", metavar="image", nargs=-1)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def push(context: Context, image_names: t.List[str]) -> None:
|
def push(context: Context, image_names: list[str]) -> None:
|
||||||
config = tutor_config.load_full(context.root)
|
config = tutor_config.load_full(context.root)
|
||||||
for image in image_names:
|
for image in image_names:
|
||||||
for tag in find_remote_image_tags(config, hooks.Filters.IMAGES_PUSH, image):
|
for tag in find_remote_image_tags(config, hooks.Filters.IMAGES_PUSH, image):
|
||||||
@ -152,7 +153,7 @@ def push(context: Context, image_names: t.List[str]) -> None:
|
|||||||
@click.command(short_help="Print tag associated to a Docker image")
|
@click.command(short_help="Print tag associated to a Docker image")
|
||||||
@click.argument("image_names", metavar="image", nargs=-1)
|
@click.argument("image_names", metavar="image", nargs=-1)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def printtag(context: Context, image_names: t.List[str]) -> None:
|
def printtag(context: Context, image_names: list[str]) -> None:
|
||||||
config = tutor_config.load_full(context.root)
|
config = tutor_config.load_full(context.root)
|
||||||
for image in image_names:
|
for image in image_names:
|
||||||
for _name, _path, tag, _args in find_images_to_build(config, image):
|
for _name, _path, tag, _args in find_images_to_build(config, image):
|
||||||
@ -161,7 +162,7 @@ def printtag(context: Context, image_names: t.List[str]) -> None:
|
|||||||
|
|
||||||
def find_images_to_build(
|
def find_images_to_build(
|
||||||
config: Config, image: str
|
config: Config, image: str
|
||||||
) -> t.Iterator[t.Tuple[str, t.Tuple[str, ...], str, t.Tuple[str, ...]]]:
|
) -> t.Iterator[tuple[str, tuple[str, ...], str, tuple[str, ...]]]:
|
||||||
"""
|
"""
|
||||||
Iterate over all images to build.
|
Iterate over all images to build.
|
||||||
|
|
||||||
@ -182,7 +183,7 @@ def find_images_to_build(
|
|||||||
|
|
||||||
def find_remote_image_tags(
|
def find_remote_image_tags(
|
||||||
config: Config,
|
config: Config,
|
||||||
filtre: "hooks.filters.Filter[t.List[t.Tuple[str, str]], [Config]]",
|
filtre: "hooks.filters.Filter[list[tuple[str, str]], [Config]]",
|
||||||
image: str,
|
image: str,
|
||||||
) -> t.Iterator[str]:
|
) -> t.Iterator[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Common jobs that must be added both to local, dev and k8s commands.
|
Common jobs that must be added both to local, dev and k8s commands.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
import functools
|
import functools
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ def _add_core_init_tasks() -> None:
|
|||||||
|
|
||||||
@click.command("init", help="Initialise all applications")
|
@click.command("init", help="Initialise all applications")
|
||||||
@click.option("-l", "--limit", help="Limit initialisation to this service or plugin")
|
@click.option("-l", "--limit", help="Limit initialisation to this service or plugin")
|
||||||
def initialise(limit: t.Optional[str]) -> t.Iterator[t.Tuple[str, str]]:
|
def initialise(limit: t.Optional[str]) -> t.Iterator[tuple[str, str]]:
|
||||||
fmt.echo_info("Initialising all services...")
|
fmt.echo_info("Initialising all services...")
|
||||||
filter_context = hooks.Contexts.APP(limit).name if limit else None
|
filter_context = hooks.Contexts.APP(limit).name if limit else None
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ def createuser(
|
|||||||
password: str,
|
password: str,
|
||||||
name: str,
|
name: str,
|
||||||
email: str,
|
email: str,
|
||||||
) -> t.Iterable[t.Tuple[str, str]]:
|
) -> t.Iterable[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Create an Open edX user
|
Create an Open edX user
|
||||||
|
|
||||||
@ -127,7 +128,7 @@ u.save()"
|
|||||||
|
|
||||||
|
|
||||||
@click.command(help="Import the demo course")
|
@click.command(help="Import the demo course")
|
||||||
def importdemocourse() -> t.Iterable[t.Tuple[str, str]]:
|
def importdemocourse() -> t.Iterable[tuple[str, str]]:
|
||||||
template = """
|
template = """
|
||||||
# Import demo course
|
# Import demo course
|
||||||
git clone https://github.com/openedx/edx-demo-course --branch {{ OPENEDX_COMMON_VERSION }} --depth 1 ../edx-demo-course
|
git clone https://github.com/openedx/edx-demo-course --branch {{ OPENEDX_COMMON_VERSION }} --depth 1 ../edx-demo-course
|
||||||
@ -150,7 +151,7 @@ python ./manage.py cms import ../data ../edx-demo-course
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
@click.argument("theme_name")
|
@click.argument("theme_name")
|
||||||
def settheme(domains: t.List[str], theme_name: str) -> t.Iterable[t.Tuple[str, str]]:
|
def settheme(domains: list[str], theme_name: str) -> t.Iterable[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Assign a theme to the LMS and the CMS.
|
Assign a theme to the LMS and the CMS.
|
||||||
|
|
||||||
@ -159,7 +160,7 @@ def settheme(domains: t.List[str], theme_name: str) -> t.Iterable[t.Tuple[str, s
|
|||||||
yield ("lms", set_theme_template(theme_name, domains))
|
yield ("lms", set_theme_template(theme_name, domains))
|
||||||
|
|
||||||
|
|
||||||
def set_theme_template(theme_name: str, domain_names: t.List[str]) -> str:
|
def set_theme_template(theme_name: str, domain_names: list[str]) -> str:
|
||||||
"""
|
"""
|
||||||
For each domain, get or create a Site object and assign the selected theme.
|
For each domain, get or create a Site object and assign the selected theme.
|
||||||
"""
|
"""
|
||||||
@ -231,7 +232,7 @@ P = ParamSpec("P")
|
|||||||
|
|
||||||
|
|
||||||
def _patch_callback(
|
def _patch_callback(
|
||||||
job_name: str, func: t.Callable[P, t.Iterable[t.Tuple[str, str]]]
|
job_name: str, func: t.Callable[P, t.Iterable[tuple[str, str]]]
|
||||||
) -> t.Callable[P, None]:
|
) -> t.Callable[P, None]:
|
||||||
"""
|
"""
|
||||||
Modify a subcommand callback function such that its results are processed by `do_callback`.
|
Modify a subcommand callback function such that its results are processed by `do_callback`.
|
||||||
@ -247,7 +248,7 @@ def _patch_callback(
|
|||||||
return new_callback
|
return new_callback
|
||||||
|
|
||||||
|
|
||||||
def do_callback(service_commands: t.Iterable[t.Tuple[str, str]]) -> None:
|
def do_callback(service_commands: t.Iterable[tuple[str, str]]) -> None:
|
||||||
"""
|
"""
|
||||||
This function must be added as a callback to all `do` subcommands.
|
This function must be added as a callback to all `do` subcommands.
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@ -70,7 +71,7 @@ def local(context: click.Context) -> None:
|
|||||||
@click.pass_context
|
@click.pass_context
|
||||||
def launch(
|
def launch(
|
||||||
context: click.Context,
|
context: click.Context,
|
||||||
mounts: t.Tuple[t.List[compose.MountParam.MountType]],
|
mounts: tuple[list[compose.MountParam.MountType]],
|
||||||
non_interactive: bool,
|
non_interactive: bool,
|
||||||
pullimages: bool,
|
pullimages: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@ -22,13 +23,13 @@ class PluginName(click.ParamType):
|
|||||||
|
|
||||||
def shell_complete(
|
def shell_complete(
|
||||||
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
self, ctx: click.Context, param: click.Parameter, incomplete: str
|
||||||
) -> t.List[click.shell_completion.CompletionItem]:
|
) -> list[click.shell_completion.CompletionItem]:
|
||||||
return [
|
return [
|
||||||
click.shell_completion.CompletionItem(name)
|
click.shell_completion.CompletionItem(name)
|
||||||
for name in self.get_names(incomplete)
|
for name in self.get_names(incomplete)
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_names(self, incomplete: str) -> t.List[str]:
|
def get_names(self, incomplete: str) -> list[str]:
|
||||||
candidates = []
|
candidates = []
|
||||||
if self.allow_all:
|
if self.allow_all:
|
||||||
candidates.append("all")
|
candidates.append("all")
|
||||||
@ -67,7 +68,7 @@ def list_command() -> None:
|
|||||||
@click.command(help="Enable a plugin")
|
@click.command(help="Enable a plugin")
|
||||||
@click.argument("plugin_names", metavar="plugin", nargs=-1, type=PluginName())
|
@click.argument("plugin_names", metavar="plugin", nargs=-1, type=PluginName())
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def enable(context: Context, plugin_names: t.List[str]) -> None:
|
def enable(context: Context, plugin_names: list[str]) -> None:
|
||||||
config = tutor_config.load_minimal(context.root)
|
config = tutor_config.load_minimal(context.root)
|
||||||
for plugin in plugin_names:
|
for plugin in plugin_names:
|
||||||
plugins.load(plugin)
|
plugins.load(plugin)
|
||||||
@ -87,10 +88,10 @@ def enable(context: Context, plugin_names: t.List[str]) -> None:
|
|||||||
"plugin_names", metavar="plugin", nargs=-1, type=PluginName(allow_all=True)
|
"plugin_names", metavar="plugin", nargs=-1, type=PluginName(allow_all=True)
|
||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def disable(context: Context, plugin_names: t.List[str]) -> None:
|
def disable(context: Context, plugin_names: list[str]) -> None:
|
||||||
config = tutor_config.load_minimal(context.root)
|
config = tutor_config.load_minimal(context.root)
|
||||||
disable_all = "all" in plugin_names
|
disable_all = "all" in plugin_names
|
||||||
disabled: t.List[str] = []
|
disabled: list[str] = []
|
||||||
for plugin in tutor_config.get_enabled_plugins(config):
|
for plugin in tutor_config.get_enabled_plugins(config):
|
||||||
if disable_all or plugin in plugin_names:
|
if disable_all or plugin in plugin_names:
|
||||||
fmt.echo_info(f"Disabling plugin {plugin}...")
|
fmt.echo_info(f"Disabling plugin {plugin}...")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import typing as t
|
|
||||||
|
|
||||||
from tutor import env, exceptions, fmt, hooks, plugins, serialize, utils
|
from tutor import env, exceptions, fmt, hooks, plugins, serialize, utils
|
||||||
from tutor.types import Config, ConfigValue, cast_config, get_typed
|
from tutor.types import Config, ConfigValue, cast_config, get_typed
|
||||||
@ -108,7 +108,7 @@ def get_base() -> Config:
|
|||||||
Entries in this configuration are unrendered.
|
Entries in this configuration are unrendered.
|
||||||
"""
|
"""
|
||||||
base = get_template("base.yml")
|
base = get_template("base.yml")
|
||||||
extra_config: t.List[t.Tuple[str, ConfigValue]] = []
|
extra_config: list[tuple[str, ConfigValue]] = []
|
||||||
extra_config = hooks.Filters.CONFIG_UNIQUE.apply(extra_config)
|
extra_config = hooks.Filters.CONFIG_UNIQUE.apply(extra_config)
|
||||||
extra_config = hooks.Filters.CONFIG_OVERRIDES.apply(extra_config)
|
extra_config = hooks.Filters.CONFIG_OVERRIDES.apply(extra_config)
|
||||||
for name, value in extra_config:
|
for name, value in extra_config:
|
||||||
@ -269,7 +269,7 @@ def enable_plugins(config: Config) -> None:
|
|||||||
plugins.load_all(get_enabled_plugins(config))
|
plugins.load_all(get_enabled_plugins(config))
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_plugins(config: Config) -> t.List[str]:
|
def get_enabled_plugins(config: Config) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Return the list of plugins that are enabled, as per the configuration. Note that
|
Return the list of plugins that are enabled, as per the configuration. Note that
|
||||||
this may differ from the list of loaded plugins. For instance when a plugin is
|
this may differ from the list of loaded plugins. For instance when a plugin is
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@ -111,7 +112,7 @@ class Renderer:
|
|||||||
The elements of `prefix` must contain only "/", and not os.sep.
|
The elements of `prefix` must contain only "/", and not os.sep.
|
||||||
"""
|
"""
|
||||||
full_prefix = "/".join(prefix)
|
full_prefix = "/".join(prefix)
|
||||||
env_templates: t.List[str] = self.environment.loader.list_templates()
|
env_templates: list[str] = self.environment.loader.list_templates()
|
||||||
for template in env_templates:
|
for template in env_templates:
|
||||||
if template.startswith(full_prefix):
|
if template.startswith(full_prefix):
|
||||||
# Exclude templates that match certain patterns
|
# Exclude templates that match certain patterns
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
||||||
__license__ = "Apache 2.0"
|
__license__ = "Apache 2.0"
|
||||||
|
|
||||||
@ -53,11 +55,11 @@ class Action(t.Generic[P]):
|
|||||||
This strong typing makes it easier for plugin developers to quickly check whether they are adding and calling action callbacks correctly.
|
This strong typing makes it easier for plugin developers to quickly check whether they are adding and calling action callbacks correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INDEX: t.Dict[str, "Action[t.Any]"] = {}
|
INDEX: dict[str, "Action[t.Any]"] = {}
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
def __init__(self, name: str) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.callbacks: t.List[ActionCallback[P]] = []
|
self.callbacks: list[ActionCallback[P]] = []
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.__class__.__name__}('{self.name}')"
|
return f"{self.__class__.__name__}('{self.name}')"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
||||||
__license__ = "Apache 2.0"
|
__license__ = "Apache 2.0"
|
||||||
|
|
||||||
@ -6,7 +8,7 @@ from contextlib import contextmanager
|
|||||||
|
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
CURRENT: t.List[str] = []
|
CURRENT: list[str] = []
|
||||||
|
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
||||||
__license__ = "Apache 2.0"
|
__license__ = "Apache 2.0"
|
||||||
|
|
||||||
@ -58,11 +60,11 @@ class Filter(t.Generic[T, P]):
|
|||||||
they are adding and calling filter callbacks correctly.
|
they are adding and calling filter callbacks correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INDEX: t.Dict[str, "Filter[t.Any, t.Any]"] = {}
|
INDEX: dict[str, "Filter[t.Any, t.Any]"] = {}
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
def __init__(self, name: str) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.callbacks: t.List[FilterCallback[T, P]] = []
|
self.callbacks: list[FilterCallback[T, P]] = []
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.__class__.__name__}('{self.name}')"
|
return f"{self.__class__.__name__}('{self.name}')"
|
||||||
@ -143,12 +145,12 @@ class Filter(t.Generic[T, P]):
|
|||||||
|
|
||||||
# The methods below are specific to filters which take lists as first arguments
|
# The methods below are specific to filters which take lists as first arguments
|
||||||
def add_item(
|
def add_item(
|
||||||
self: "Filter[t.List[E], P]", item: E, priority: t.Optional[int] = None
|
self: "Filter[list[E], P]", item: E, priority: t.Optional[int] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.add_items([item], priority=priority)
|
self.add_items([item], priority=priority)
|
||||||
|
|
||||||
def add_items(
|
def add_items(
|
||||||
self: "Filter[t.List[E], P]", items: t.List[E], priority: t.Optional[int] = None
|
self: "Filter[list[E], P]", items: list[E], priority: t.Optional[int] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
# Unfortunately we have to type-ignore this line. If not, mypy complains with:
|
# Unfortunately we have to type-ignore this line. If not, mypy complains with:
|
||||||
#
|
#
|
||||||
@ -158,18 +160,16 @@ class Filter(t.Generic[T, P]):
|
|||||||
# But we are unable to mark arguments positional-only (by adding / after values arg) in Python 3.7.
|
# But we are unable to mark arguments positional-only (by adding / after values arg) in Python 3.7.
|
||||||
# Get rid of this statement after Python 3.7 EOL.
|
# Get rid of this statement after Python 3.7 EOL.
|
||||||
@self.add(priority=priority) # type: ignore
|
@self.add(priority=priority) # type: ignore
|
||||||
def callback(
|
def callback(values: list[E], *_args: P.args, **_kwargs: P.kwargs) -> list[E]:
|
||||||
values: t.List[E], *_args: P.args, **_kwargs: P.kwargs
|
|
||||||
) -> t.List[E]:
|
|
||||||
return values + items
|
return values + items
|
||||||
|
|
||||||
def iterate(
|
def iterate(
|
||||||
self: "Filter[t.List[E], P]", *args: P.args, **kwargs: P.kwargs
|
self: "Filter[list[E], P]", *args: P.args, **kwargs: P.kwargs
|
||||||
) -> t.Iterator[E]:
|
) -> t.Iterator[E]:
|
||||||
yield from self.iterate_from_context(None, *args, **kwargs)
|
yield from self.iterate_from_context(None, *args, **kwargs)
|
||||||
|
|
||||||
def iterate_from_context(
|
def iterate_from_context(
|
||||||
self: "Filter[t.List[E], P]",
|
self: "Filter[list[E], P]",
|
||||||
context: t.Optional[str],
|
context: t.Optional[str],
|
||||||
*args: P.args,
|
*args: P.args,
|
||||||
**kwargs: P.kwargs,
|
**kwargs: P.kwargs,
|
||||||
@ -268,7 +268,7 @@ def add_item(name: str, item: T, priority: t.Optional[int] = None) -> None:
|
|||||||
get(name).add_item(item, priority=priority)
|
get(name).add_item(item, priority=priority)
|
||||||
|
|
||||||
|
|
||||||
def add_items(name: str, items: t.List[T], priority: t.Optional[int] = None) -> None:
|
def add_items(name: str, items: list[T], priority: t.Optional[int] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Convenience function to add multiple item to a filter that returns a list of items.
|
Convenience function to add multiple item to a filter that returns a list of items.
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from typing_extensions import Protocol
|
from typing_extensions import Protocol
|
||||||
@ -14,7 +15,7 @@ class PrioritizedCallback(Protocol):
|
|||||||
TPrioritized = t.TypeVar("TPrioritized", bound=PrioritizedCallback)
|
TPrioritized = t.TypeVar("TPrioritized", bound=PrioritizedCallback)
|
||||||
|
|
||||||
|
|
||||||
def insert_callback(callback: TPrioritized, callbacks: t.List[TPrioritized]) -> None:
|
def insert_callback(callback: TPrioritized, callbacks: list[TPrioritized]) -> None:
|
||||||
# I wish we could use bisect.insort_right here but the `key=` parameter
|
# I wish we could use bisect.insort_right here but the `key=` parameter
|
||||||
# is unsupported in Python 3.9
|
# is unsupported in Python 3.9
|
||||||
position = 0
|
position = 0
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Provide API for plugin features.
|
Provide API for plugin features.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
import typing as t
|
import typing as t
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ def _convert_plugin_patches() -> None:
|
|||||||
|
|
||||||
This action is run after plugins have been loaded.
|
This action is run after plugins have been loaded.
|
||||||
"""
|
"""
|
||||||
patches: t.Iterable[t.Tuple[str, str]] = hooks.Filters.ENV_PATCHES.iterate()
|
patches: t.Iterable[tuple[str, str]] = hooks.Filters.ENV_PATCHES.iterate()
|
||||||
for name, content in patches:
|
for name, content in patches:
|
||||||
hooks.Filters.ENV_PATCH(name).add_item(content)
|
hooks.Filters.ENV_PATCH(name).add_item(content)
|
||||||
|
|
||||||
@ -44,14 +45,14 @@ def iter_installed() -> t.Iterator[str]:
|
|||||||
yield from sorted(hooks.Filters.PLUGINS_INSTALLED.iterate())
|
yield from sorted(hooks.Filters.PLUGINS_INSTALLED.iterate())
|
||||||
|
|
||||||
|
|
||||||
def iter_info() -> t.Iterator[t.Tuple[str, t.Optional[str]]]:
|
def iter_info() -> t.Iterator[tuple[str, t.Optional[str]]]:
|
||||||
"""
|
"""
|
||||||
Iterate on the information of all installed plugins.
|
Iterate on the information of all installed plugins.
|
||||||
|
|
||||||
Yields (<plugin name>, <info>) tuples.
|
Yields (<plugin name>, <info>) tuples.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def plugin_info_name(info: t.Tuple[str, t.Optional[str]]) -> str:
|
def plugin_info_name(info: tuple[str, t.Optional[str]]) -> str:
|
||||||
return info[0]
|
return info[0]
|
||||||
|
|
||||||
yield from sorted(hooks.Filters.PLUGINS_INFO.iterate(), key=plugin_info_name)
|
yield from sorted(hooks.Filters.PLUGINS_INFO.iterate(), key=plugin_info_name)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import re
|
import re
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ def parse(v: t.Union[str, t.IO[str]]) -> t.Any:
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def parse_key_value(text: str) -> t.Optional[t.Tuple[str, t.Any]]:
|
def parse_key_value(text: str) -> t.Optional[tuple[str, t.Any]]:
|
||||||
"""
|
"""
|
||||||
Parse <KEY>=<YAML VALUE> command line arguments.
|
Parse <KEY>=<YAML VALUE> command line arguments.
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
# The Tutor plugin system is licensed under the terms of the Apache 2.0 license.
|
||||||
__license__ = "Apache 2.0"
|
__license__ = "Apache 2.0"
|
||||||
|
|
||||||
@ -38,9 +40,9 @@ T = t.TypeVar("T")
|
|||||||
|
|
||||||
|
|
||||||
def get_typed(
|
def get_typed(
|
||||||
config: t.Dict[str, t.Any],
|
config: dict[str, t.Any],
|
||||||
key: str,
|
key: str,
|
||||||
expected_type: t.Type[T],
|
expected_type: type[T],
|
||||||
default: t.Optional[T] = None,
|
default: t.Optional[T] = None,
|
||||||
) -> T:
|
) -> T:
|
||||||
value = config.get(key, default)
|
value = config.get(key, default)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user