working prototype
Now there is a lot of TODO items remaining...
This commit is contained in:
parent
35554c2cc2
commit
34028c0e7c
@ -1,19 +1,22 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from contextlib import contextmanager
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
|
||||||
# import sys
|
|
||||||
# import time
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
||||||
# from contextlib import contextmanager
|
|
||||||
|
|
||||||
import tutor.env
|
import tutor.env
|
||||||
from quart import Quart, render_template, request, websocket, redirect, url_for
|
from quart import Quart, render_template, request, websocket, redirect, url_for
|
||||||
from tutor import hooks
|
from tutor.exceptions import TutorError
|
||||||
|
from tutor import fmt, hooks
|
||||||
from tutor.types import Config
|
from tutor.types import Config
|
||||||
|
import tutor.utils
|
||||||
|
from tutor.commands.cli import cli
|
||||||
|
|
||||||
|
|
||||||
class TutorProject:
|
class TutorProject:
|
||||||
@ -55,13 +58,6 @@ app = Quart(
|
|||||||
|
|
||||||
|
|
||||||
def run(root: str, **app_kwargs: t.Any) -> None:
|
def run(root: str, **app_kwargs: t.Any) -> None:
|
||||||
# import module to trigger all the right imports and hooks
|
|
||||||
# TODO how do we handle this now that we call the tutor binary directly? Should we
|
|
||||||
# even deal with hooks at all? We have to to figure out plugin configuration,
|
|
||||||
# information, etc.
|
|
||||||
# pylint: disable=unused-import,import-outside-toplevel
|
|
||||||
from tutor.commands.cli import cli
|
|
||||||
|
|
||||||
hooks.Actions.CORE_READY.do() # discover plugins
|
hooks.Actions.CORE_READY.do() # discover plugins
|
||||||
hooks.Actions.PROJECT_ROOT_READY.do(root)
|
hooks.Actions.PROJECT_ROOT_READY.do(root)
|
||||||
app.logger.info("Dash tutor logs location: %s", TutorProject.tutor_stdout_path())
|
app.logger.info("Dash tutor logs location: %s", TutorProject.tutor_stdout_path())
|
||||||
@ -75,6 +71,7 @@ async def home():
|
|||||||
|
|
||||||
@app.get("/sidebar/plugins")
|
@app.get("/sidebar/plugins")
|
||||||
async def sidebar_plugins():
|
async def sidebar_plugins():
|
||||||
|
# TODO get rid of this view and render from home()
|
||||||
return await render_template(
|
return await render_template(
|
||||||
"sidebar/_plugins.html",
|
"sidebar/_plugins.html",
|
||||||
installed_plugins=sorted(set(hooks.Filters.PLUGINS_INSTALLED.iterate())),
|
installed_plugins=sorted(set(hooks.Filters.PLUGINS_INSTALLED.iterate())),
|
||||||
@ -104,35 +101,97 @@ async def toggle_plugin(name: str):
|
|||||||
@app.post("/tutor/cli")
|
@app.post("/tutor/cli")
|
||||||
async def tutor_cli():
|
async def tutor_cli():
|
||||||
# Run command asynchronously
|
# Run command asynchronously
|
||||||
|
# TODO return 400 if thread is active
|
||||||
# TODO parse command from JSON request body
|
# TODO parse command from JSON request body
|
||||||
# app.add_background_task(subprocess_exec, ["tutor", "config", "printvalue", "DOCKER_IMAGE_OPENEDX"])
|
thread = threading.Thread(
|
||||||
# app.add_background_task(subprocess_exec, ["tutor" "local", "launch"])
|
target=run_tutor_cli,
|
||||||
# await subprocess_exec(["tutor", "config", "printvalue", "pouac"])
|
# args=[["dev", "dc", "run", "pouac"]],
|
||||||
await subprocess_exec(["tutor", "dev", "dc", "run", "pouac"])
|
# args=[["config", "printvalue", "DOCKER_IMAGE_OPENEDX"]],
|
||||||
|
# args=[["config", "printvalue", "POUAC"]],
|
||||||
|
args=[["local", "launch", "--non-interactive"]],
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
return redirect(url_for("tutor_logs"))
|
return redirect(url_for("tutor_logs"))
|
||||||
|
|
||||||
|
|
||||||
async def subprocess_exec(command: list[str]):
|
def run_tutor_cli(args: list[str]) -> None:
|
||||||
path = TutorProject.tutor_stdout_path()
|
"""
|
||||||
# if os.path.exists(path):
|
Execute some arbitrary tutor command. Capture the output in a dedicated file.
|
||||||
# # TODO return 400? We can't run two commands at the same time
|
|
||||||
# return {}
|
TODO Return the exit code?
|
||||||
with open(path, "w", encoding="utf8") as stdout:
|
TODO Refactor this
|
||||||
# Print command
|
"""
|
||||||
# TODO this doesn't seem to work. For some reason, the command is added at the
|
with open(TutorProject.tutor_stdout_path(), "w", encoding="utf8") as stdout:
|
||||||
# bottom of the file!!!
|
# useless because overwritten by Popen
|
||||||
stdout.write(f"$ {shlex.join(command)}\n")
|
stdout.write(f"$ tutor {shlex.join(args)}\n")
|
||||||
# Run command
|
|
||||||
proc = await asyncio.create_subprocess_exec(
|
# Override execute function
|
||||||
*command,
|
with patch_objects(
|
||||||
stdout=stdout,
|
[
|
||||||
stderr=stdout,
|
(tutor.utils, "execute", execute),
|
||||||
stdin=asyncio.subprocess.DEVNULL,
|
(fmt.click, "echo", click_echo),
|
||||||
)
|
(fmt.click, "style", click_style),
|
||||||
while proc.returncode is None:
|
]
|
||||||
await proc.communicate()
|
):
|
||||||
await asyncio.sleep(0.1)
|
try:
|
||||||
return {}
|
# Call tutor command
|
||||||
|
cli(args)
|
||||||
|
except TutorError as e:
|
||||||
|
with open(TutorProject.tutor_stdout_path(), "a", encoding="utf8") as stdout:
|
||||||
|
stdout.write(e.args[0])
|
||||||
|
except SystemExit as e:
|
||||||
|
# TODO what to do with e.code?
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_objects(refs):
|
||||||
|
old_objects = []
|
||||||
|
for module, object_name, new_object in refs:
|
||||||
|
# backup old object
|
||||||
|
old_objects.append((module, object_name, getattr(module, object_name)))
|
||||||
|
# override object
|
||||||
|
setattr(module, object_name, new_object)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# restore old objects
|
||||||
|
for module, object_name, old_object in old_objects:
|
||||||
|
setattr(module, object_name, old_object)
|
||||||
|
|
||||||
|
|
||||||
|
def click_echo(text, **kwargs):
|
||||||
|
with open(TutorProject.tutor_stdout_path(), "a", encoding="utf8") as stdout:
|
||||||
|
stdout.write(text)
|
||||||
|
stdout.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def click_style(text, **kwargs):
|
||||||
|
"""
|
||||||
|
Strip ANSI colors
|
||||||
|
|
||||||
|
TODO convert to HTML color codes?
|
||||||
|
"""
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def execute(*command: str) -> int:
|
||||||
|
"""
|
||||||
|
TODO refactor this
|
||||||
|
"""
|
||||||
|
with open(TutorProject.tutor_stdout_path(), "ab") as stdout:
|
||||||
|
with subprocess.Popen(command, stdout=stdout, stderr=stdout) as p:
|
||||||
|
try:
|
||||||
|
result = p.wait(timeout=None)
|
||||||
|
except Exception as e:
|
||||||
|
p.kill()
|
||||||
|
p.wait()
|
||||||
|
raise TutorError(f"Command failed: {' '.join(command)}") from e
|
||||||
|
if result > 0:
|
||||||
|
raise TutorError(
|
||||||
|
f"Command failed with status {result}: {' '.join(command)}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@app.get("/tutor/logs")
|
@app.get("/tutor/logs")
|
||||||
|
|||||||
@ -55,6 +55,7 @@ body {
|
|||||||
.workspace {
|
.workspace {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user