Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c9b3e23c | ||
|
|
13da199f04 | ||
|
|
05ada48be5 | ||
|
|
fdd8651cb1 | ||
|
|
7348ff81d3 | ||
|
|
da8aa31723 | ||
|
|
c39f3acc4f | ||
|
|
72b2b10821 | ||
|
|
ef8a30fecd | ||
|
|
02a4b1efb1 | ||
|
|
7de36adacd | ||
|
|
93ad17bb71 | ||
|
|
8ce407cc40 | ||
|
|
eb47275595 | ||
|
|
a42b22d9ea | ||
|
|
4136fc6620 | ||
|
|
a73f745707 | ||
|
|
86abc64404 | ||
|
|
9f891b2f93 | ||
|
|
9f6b2ffdcd | ||
|
|
d478f4de4f | ||
|
|
f0ac92b85b | ||
|
|
d68039216d | ||
|
|
a880f44b9f | ||
|
|
5db7addac8 | ||
|
|
fe3742a0f0 | ||
|
|
4bf2a47857 | ||
|
|
d1c67f42d9 | ||
|
|
02c7719c32 | ||
|
|
7f09751e8f | ||
|
|
8703186cef | ||
|
|
241283ea95 | ||
|
|
8c2f78a683 | ||
|
|
b0ad257f63 | ||
|
|
e1eea67baf | ||
|
|
add702220c | ||
|
|
7038313d7e | ||
|
|
522fe2a6da | ||
|
|
67c55ffe1d | ||
|
|
4256df3839 | ||
|
|
be48edeb26 | ||
|
|
cbe4f5af5c | ||
|
|
18a6d5bc39 | ||
|
|
7317758ca9 | ||
|
|
f81740fa40 | ||
|
|
84ceed2f92 | ||
|
|
c93d2a3e35 | ||
|
|
b25a7e23a4 | ||
|
|
c88592235f | ||
|
|
4d75cd0f84 | ||
|
|
5da7bd3278 | ||
|
|
790405a132 | ||
|
|
9e1296b6bd | ||
|
|
ee0f3563e2 | ||
|
|
18f202e593 | ||
|
|
1ea66816ea | ||
|
|
9ab9d85fc5 | ||
|
|
9787d8d71b | ||
|
|
69aff3255f | ||
|
|
b3bc26036a | ||
|
|
8448e2ba2f | ||
|
|
f9cf62c307 | ||
|
|
06c53909ba | ||
|
|
70730f1cbf | ||
|
|
6cf8776b29 | ||
|
|
e282c959e0 | ||
|
|
ee1ade376a | ||
|
|
c40a96e8fc | ||
|
|
23a15c8f0f | ||
|
|
33c4ca9538 | ||
|
|
efab209715 | ||
|
|
5595ba7283 | ||
|
|
29e53d7fda | ||
|
|
09278342d9 | ||
|
|
a938fefea7 | ||
|
|
8318502e31 | ||
|
|
d0a40077f9 | ||
|
|
aa3dc2b1fd | ||
|
|
07255c2d41 |
20
.github/workflows/auto-add-to-project.yml
vendored
Normal file
20
.github/workflows/auto-add-to-project.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: Auto Add Issues and Pull Requests to Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# https://github.com/actions/add-to-project
|
||||||
|
add-to-project:
|
||||||
|
name: Add issue and bugs to project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@v1.0.2
|
||||||
|
with:
|
||||||
|
project-url: https://github.com/orgs/overhangio/projects/4
|
||||||
|
github-token: ${{ secrets.GH_PROJECT_PERSONAL_ACCESS_TOKEN }}
|
||||||
6
.github/workflows/sync.yml
vendored
6
.github/workflows/sync.yml
vendored
@ -2,16 +2,16 @@ name: Sync with private repo
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ release, main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add remote
|
- name: Add remote
|
||||||
run: git remote add overhangio https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@git.overhang.io/core/tutor-xqueue.git
|
run: git remote add overhangio https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@git.overhang.io/core/tutor-xqueue.git
|
||||||
- name: Push
|
- name: Push
|
||||||
run: git push overhangio master
|
run: git push overhangio $GITHUB_REF
|
||||||
|
|||||||
25
.github/workflows/test.yml
vendored
Normal file
25
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Run tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ release, main ]
|
||||||
|
push:
|
||||||
|
branches: [ release, main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.9', '3.12']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install .[dev]
|
||||||
|
- name: Test lint, types, and format
|
||||||
|
run: make test
|
||||||
@ -2,7 +2,6 @@ variables:
|
|||||||
TUTOR_PLUGIN: xqueue
|
TUTOR_PLUGIN: xqueue
|
||||||
TUTOR_IMAGES: xqueue
|
TUTOR_IMAGES: xqueue
|
||||||
TUTOR_PYPI_PACKAGE: tutor-xqueue
|
TUTOR_PYPI_PACKAGE: tutor-xqueue
|
||||||
OPENEDX_RELEASE: olive
|
|
||||||
GITHUB_REPO: overhangio/tutor-xqueue
|
GITHUB_REPO: overhangio/tutor-xqueue
|
||||||
|
|
||||||
include:
|
include:
|
||||||
|
|||||||
21
.hatch_build.py
Normal file
21
.hatch_build.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# https://hatch.pypa.io/latest/how-to/config/dynamic-metadata/
|
||||||
|
|
||||||
|
import os
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from hatchling.metadata.plugin.interface import MetadataHookInterface
|
||||||
|
|
||||||
|
HERE = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaDataHook(MetadataHookInterface):
|
||||||
|
def update(self, metadata: dict[str, t.Any]) -> None:
|
||||||
|
about = load_about()
|
||||||
|
metadata["version"] = about["__version__"]
|
||||||
|
|
||||||
|
|
||||||
|
def load_about() -> dict[str, str]:
|
||||||
|
about: dict[str, str] = {}
|
||||||
|
with open(os.path.join(HERE, "tutorxqueue", "__about__.py"), "rt", encoding="utf-8") as f:
|
||||||
|
exec(f.read(), about)
|
||||||
|
return about
|
||||||
69
CHANGELOG.md
Normal file
69
CHANGELOG.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
This file includes a history of past releases. Changes that were not yet added to a release are in the [changelog.d/](./changelog.d) folder.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
⚠️ DO NOT ADD YOUR CHANGES TO THIS FILE! (unless you want to modify existing changelog entries in this file)
|
||||||
|
Changelog entries are managed by scriv. After you have made some changes to this plugin, create a changelog entry with:
|
||||||
|
|
||||||
|
scriv create
|
||||||
|
|
||||||
|
Edit and commit the newly-created file in changelog.d.
|
||||||
|
|
||||||
|
If you need to create a new release, create a separate commit just for that. It is important to respect these
|
||||||
|
instructions, because git commits are used to generate release notes:
|
||||||
|
- Modify the version number in `__about__.py`.
|
||||||
|
- Collect changelog entries with `scriv collect`
|
||||||
|
- The title of the commit should be the same as the new version: "vX.Y.Z".
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- scriv-insert-here -->
|
||||||
|
|
||||||
|
<a id='changelog-20.0.0'></a>
|
||||||
|
## v20.0.0 (2025-06-05)
|
||||||
|
|
||||||
|
- 💥[Feature] Upgrade to Teak. (by @jfavellar90)
|
||||||
|
|
||||||
|
<a id='changelog-19.0.2'></a>
|
||||||
|
## v19.0.2 (2025-03-12)
|
||||||
|
|
||||||
|
- [Improvement] Add hatch_build.py in sdist target to fix the installation issues (by @dawoudsheraz)
|
||||||
|
|
||||||
|
<a id='changelog-19.0.1'></a>
|
||||||
|
## v19.0.1 (2025-03-11)
|
||||||
|
|
||||||
|
- [Improvement] Migrate from `setup.py` (setuptools) to `pyproject.toml` (hatch). (by @jfavellar90)
|
||||||
|
|
||||||
|
<a id='changelog-19.0.0'></a>
|
||||||
|
## v19.0.0 (2024-10-24)
|
||||||
|
|
||||||
|
- 💥 [Deprecation] Drop support for python 3.8 and set Python 3.9 as the minimum supported python version. (by @DawoudSheraz)
|
||||||
|
|
||||||
|
- 💥[Improvement] Rename Tutor's two branches (by @DawoudSheraz):
|
||||||
|
* Rename **master** to **release**, as this branch runs the latest official Open edX release tag.
|
||||||
|
* Rename **nightly** to **main**, as this branch runs the Open edX master branches, which are the basis for the next Open edX release.
|
||||||
|
|
||||||
|
- [Bugfix] Fix legacy warnings during Docker build. (by @regisb)
|
||||||
|
|
||||||
|
- 💥[Feature] Update Xqueue Image to use Ubuntu 24.04 as base OS. (by @jfavellar90)
|
||||||
|
|
||||||
|
- 💥[Feature] Upgrade to Sumac. (by @jfavellar90)
|
||||||
|
|
||||||
|
<a id='changelog-18.0.0'></a>
|
||||||
|
## v18.0.0 (2024-05-09)
|
||||||
|
|
||||||
|
- [Bugfix] Make plugin compatible with Python 3.12 by removing dependency on `pkg_resources`. (by @regisb)
|
||||||
|
|
||||||
|
- 💥[Feature] Upgrade Python version to 3.12.3. (by @jfavellar90)
|
||||||
|
- 💥[Feature] Upgrade to Redwood. (by @jfavellar90)
|
||||||
|
|
||||||
|
<a id='changelog-17.0.0'></a>
|
||||||
|
## v17.0.0 (2023-12-09)
|
||||||
|
|
||||||
|
- 💥 [Feature] Upgrade to Quince (by @jfavellar90).
|
||||||
|
- [Improvement] Add a scriv-compliant changelog. (by @regisb)
|
||||||
|
- [Improvement] Removing the xqueue permissions container in favor of a global single permissions container. (by @jfavellar90)
|
||||||
|
- [Bugfix] Fix "Error: service "xqueue-job" depends on undefined service mysql: invalid compose project" - add conditional statement to check whether the mysql service is enabled or if the user is using an external mysql instance. (by @ravikhetani)
|
||||||
|
- [Improvement] Added Typing to code, Makefile and test action to the repository and formatted code with Black and isort. (by @CodeWithEmad)
|
||||||
|
|
||||||
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
recursive-include tutorxqueue/patches *
|
|
||||||
recursive-include tutorxqueue/templates *
|
|
||||||
42
Makefile
Normal file
42
Makefile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
.DEFAULT_GOAL := help
|
||||||
|
.PHONY: docs
|
||||||
|
SRC_DIRS = ./tutorxqueue
|
||||||
|
|
||||||
|
# Warning: These checks are not necessarily run on every PR.
|
||||||
|
test: test-lint test-format test-types test-pythonpackage # Run some static checks.
|
||||||
|
|
||||||
|
test-format: ## Run code formatting tests
|
||||||
|
ruff format --check --diff $(SRC_DIRS)
|
||||||
|
|
||||||
|
test-lint: ## Run code linting tests
|
||||||
|
ruff check ${SRC_DIRS}
|
||||||
|
|
||||||
|
test-types: ## Run type checks.
|
||||||
|
mypy --exclude=templates --ignore-missing-imports --implicit-reexport --strict ${SRC_DIRS}
|
||||||
|
|
||||||
|
build-pythonpackage: ## Build the "tutor-xqueue" python package for upload to pypi
|
||||||
|
python -m build --sdist
|
||||||
|
|
||||||
|
test-pythonpackage: build-pythonpackage ## Test that package can be uploaded to pypi
|
||||||
|
twine check dist/tutor_xqueue-$(shell make version).tar.gz
|
||||||
|
|
||||||
|
format: ## Format code automatically
|
||||||
|
ruff format ${SRC_DIRS}
|
||||||
|
|
||||||
|
fix-lint: ## Fix lint errors automatically
|
||||||
|
ruff check --fix ${SRC_DIRS}
|
||||||
|
|
||||||
|
changelog-entry: ## Create a new changelog entry.
|
||||||
|
scriv create
|
||||||
|
|
||||||
|
changelog: ## Collect changelog entries in the CHANGELOG.md file.
|
||||||
|
scriv collect
|
||||||
|
|
||||||
|
version: ## Print the current tutor-xqueue version
|
||||||
|
@python -c 'import io, os; about = {}; exec(io.open(os.path.join("tutorxqueue", "__about__.py"), "rt", encoding="utf-8").read(), about); print(about["__version__"])'
|
||||||
|
|
||||||
|
ESCAPE =
|
||||||
|
help: ## Print this help
|
||||||
|
@grep -E '^([a-zA-Z_-]+:.*?## .*|######* .+)$$' Makefile \
|
||||||
|
| sed 's/######* \(.*\)/@ $(ESCAPE)[1;31m\1$(ESCAPE)[0m/g' | tr '@' '\n' \
|
||||||
|
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
22
README.rst
22
README.rst
@ -1,25 +1,25 @@
|
|||||||
Xqueue external grading system plugin for `Tutor <https://docs.tutor.overhang.io>`_
|
Xqueue external grading system plugin for `Tutor <https://docs.tutor.edly.io>`_
|
||||||
===================================================================================
|
===================================================================================
|
||||||
|
|
||||||
This is a plugin for `Tutor <https://docs.tutor.overhang.io>`_ that provides the Xqueue external grading system for Open edX platforms. If you don't know what it is, you probably don't need it.
|
This is a plugin for `Tutor <https://docs.tutor.edly.io>`_ that provides the Xqueue external grading system for Open edX platforms. If you don't know what it is, you probably don't need it.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The plugin is currently bundled with the `binary releases of Tutor <https://github.com/overhangio/tutor/releases>`__. If you have installed Tutor from source, you will have to install this plugin from source, too::
|
The plugin is currently bundled with the `binary releases of Tutor <https://github.com/overhangio/tutor/releases>`__. If you have installed Tutor from source, you will have to install this plugin from source, too::
|
||||||
|
|
||||||
pip install tutor-xqueue
|
tutor plugins install xqueue
|
||||||
|
|
||||||
Then, to enable this plugin, run::
|
Then, to enable this plugin, run::
|
||||||
|
|
||||||
tutor plugins enable xqueue
|
tutor plugins enable xqueue
|
||||||
|
|
||||||
You should then run the initialisation scripts. The easiest way to do this is to run ``tutor local quickstart``.
|
You should then run the initialisation scripts. The easiest way to do this is to run ``tutor local launch``.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
In the Open edX studio, edit a course and add a new "Advanced blank problem" ("Problem" 🠆 "Advanced" 🠆 "Blank Advanced Problem"). Then, click "Edit" and copy-paste the following in the editor::
|
In the Open edX studio, edit a course and add a new "Advanced blank problem" ("Problem" → "Advanced problem types" → "Blank problem"). Then, copy-paste the following in the Blank problem editor::
|
||||||
|
|
||||||
|
|
||||||
<problem>
|
<problem>
|
||||||
@ -56,7 +56,7 @@ For a problem that includes a file submission, write instead::
|
|||||||
|
|
||||||
Note that in all cases, the queue name must be "openedx".
|
Note that in all cases, the queue name must be "openedx".
|
||||||
|
|
||||||
Save and publish the created unit. Then, access the unit from the LMS and attempt to answer the problem. The answer is sent to the Xqueue service. If you know how to use the Xqueue API, you can access it at http(s)://xqueue.LMS_HOST (in production) or http://xqueue.local.overhang.io (in development). However, the Xqueue API is a bit awkward to use. Tutor provides a simple command-line interface to interact with the Xqueue service.
|
Save and publish the created unit. Then, access the unit from the LMS and attempt to answer the problem. The answer is sent to the Xqueue service. If you know how to use the Xqueue API, you can access it at http(s)://xqueue.LMS_HOST (in production) or http://xqueue.local.openedx.io (in development). However, the Xqueue API is a bit awkward to use. Tutor provides a simple command-line interface to interact with the Xqueue service.
|
||||||
|
|
||||||
Count the number of submissions that need to be graded::
|
Count the number of submissions that need to be graded::
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ Grade the submission (in this case, mark it as being correct)::
|
|||||||
|
|
||||||
The submission should then appear as correct with the message that you provided on the command line:
|
The submission should then appear as correct with the message that you provided on the command line:
|
||||||
|
|
||||||
.. image:: https://github.com/overhangio/tutor-xqueue/raw/master/screenshots/correctanswer.png
|
.. image:: https://github.com/overhangio/tutor-xqueue/raw/release/screenshots/correctanswer.png
|
||||||
:alt: Correct answer
|
:alt: Correct answer
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
@ -108,9 +108,11 @@ Configuration
|
|||||||
- ``XQUEUE_DOCKER_IMAGE`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx-xqueue:{{ TUTOR_VERSION }}"``)
|
- ``XQUEUE_DOCKER_IMAGE`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx-xqueue:{{ TUTOR_VERSION }}"``)
|
||||||
- ``XQUEUE_HOST`` (default: ``"xqueue.{{ LMS_HOST }}"``)
|
- ``XQUEUE_HOST`` (default: ``"xqueue.{{ LMS_HOST }}"``)
|
||||||
- ``XQUEUE_MYSQL_PASSWORD`` (default: ``"{{ 8|random_string }}"``)
|
- ``XQUEUE_MYSQL_PASSWORD`` (default: ``"{{ 8|random_string }}"``)
|
||||||
- ``XQUEUE_MYSQL_DATABASE`` (default: ``"xqueue"``
|
- ``XQUEUE_MYSQL_DATABASE`` (default: ``"xqueue"``)
|
||||||
- ``XQUEUE_MYSQL_USERNAME`` (default: ``"xqueue"``)
|
- ``XQUEUE_MYSQL_USERNAME`` (default: ``"xqueue"``)
|
||||||
- ``XQUEUE_SECRET_KEY`` (default: ``"{{ 24|random_string }}"``)
|
- ``XQUEUE_SECRET_KEY`` (default: ``"{{ 24|random_string }}"``)
|
||||||
|
- ``XQUEUE_REPOSITORY`` (default: ``"https://github.com/openedx/xqueue"``)
|
||||||
|
- ``XQUEUE_REPOSITORY_VERSION`` (default: ``"{{ OPENEDX_COMMON_VERSION }}"``)
|
||||||
|
|
||||||
These values can be modified with ``tutor config save --set PARAM_NAME=VALUE`` commands.
|
These values can be modified with ``tutor config save --set PARAM_NAME=VALUE`` commands.
|
||||||
|
|
||||||
@ -126,9 +128,9 @@ Feel free to add breakpoints (``breakpoint()``) anywhere in your source code to
|
|||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
This work is licensed under the terms of the `GNU Affero General Public License (AGPL) <https://github.com/overhangio/tutor-xqueue/blob/master/LICENSE.txt>`_.
|
This work is licensed under the terms of the `GNU Affero General Public License (AGPL) <https://github.com/overhangio/tutor-xqueue/blob/release/LICENSE.txt>`_.
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
This Tutor plugin is maintained by Jhony Avella from `eduNEXT <https://www.edunext.co/>`__. Community support is available from the official `Open edX forum <https://discuss.openedx.org>`__. Do you need help with this plugin? See the `troubleshooting <https://docs.tutor.overhang.io/troubleshooting.html>`__ section from the Tutor documentation.
|
This Tutor plugin is maintained by Jhony Avella from `eduNEXT <https://www.edunext.co/>`__. Community support is available from the official `Open edX forum <https://discuss.openedx.org>`__. Do you need help with this plugin? See the `troubleshooting <https://docs.tutor.edly.io/troubleshooting.html>`__ section from the Tutor documentation.
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
- [Improvement] Migrate from pylint and black to ruff. (by @rehmansheikh222)
|
||||||
|
- [Improvement] Test python package distribution build when running make test. (by @rehmansheikh222)
|
||||||
18
changelog.d/scriv.ini
Normal file
18
changelog.d/scriv.ini
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[scriv]
|
||||||
|
version = literal: tutorxqueue/__about__.py: __version__
|
||||||
|
categories =
|
||||||
|
format = md
|
||||||
|
md_header_level = 2
|
||||||
|
new_fragment_template =
|
||||||
|
<!--
|
||||||
|
Create a changelog entry for every new user-facing change. Please respect the following instructions:
|
||||||
|
- Indicate breaking changes by prepending an explosion 💥 character.
|
||||||
|
- Prefix your changes with either [Bugfix], [Improvement], [Feature], [Security], [Deprecation].
|
||||||
|
- You may optionally append "(by @<author>)" at the end of the line, where "<author>" is either one (just one)
|
||||||
|
of your GitHub username, real name or affiliated organization. These affiliations will be displayed in
|
||||||
|
the release notes for every release.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- - 💥[Feature] Foobarize the blorginator. This breaks plugins by renaming the `FOO_DO` filter to `BAR_DO`. (by @regisb) -->
|
||||||
|
<!-- - [Improvement] This is a non-breaking change. Life is good. (by @billgates) -->
|
||||||
|
entry_title_template = {%% if version %%}v{{ version }} {%% endif %%}({{ date.strftime('%%Y-%%m-%%d') }})
|
||||||
85
pyproject.toml
Normal file
85
pyproject.toml
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# https://packaging.python.org/en/latest/tutorials/packaging-projects/
|
||||||
|
# https://hatch.pypa.io/latest/config/build/
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "tutor-xqueue"
|
||||||
|
description = "A Tutor plugin for Xqueue (external grading system)"
|
||||||
|
authors = [
|
||||||
|
{ name = "Edly" },
|
||||||
|
{ email = "hello@edly.io"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{ name = "Jhony Avella" },
|
||||||
|
{ email = "jhony.avella@edunext.co" },
|
||||||
|
]
|
||||||
|
license = {text = "AGPL-3.0-only"}
|
||||||
|
readme = {file = "README.rst", content-type = "text/x-rst"}
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"tutor>=20.0.0,<21.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
# These fields will be set by hatch_build.py
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"tutor[dev]>=20.0.0,<21.0.0",
|
||||||
|
"ruff"
|
||||||
|
]
|
||||||
|
|
||||||
|
# https://packaging.python.org/en/latest/specifications/well-known-project-urls/#well-known-labels
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://docs.tutor.edly.io/"
|
||||||
|
Documentation = "https://github.com/overhangio/tutor-xqueue#readme"
|
||||||
|
Issues = "https://github.com/overhangio/tutor-xqueue/issues"
|
||||||
|
Source = "https://github.com/overhangio/tutor-xqueue"
|
||||||
|
Changelog = "https://github.com/overhangio/tutor-xqueue/blob/release/CHANGELOG.md"
|
||||||
|
Community = "https://discuss.openedx.org/tag/tutor"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
# hatch-specific configuration
|
||||||
|
[tool.hatch.metadata.hooks.custom]
|
||||||
|
path = ".hatch_build.py"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["tutorxqueue"]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.sdist]
|
||||||
|
# Disable strict naming, otherwise twine is not able to detect name/version
|
||||||
|
strict-naming = false
|
||||||
|
include = [ "/tutorxqueue", ".hatch_build.py"]
|
||||||
|
exclude = ["tests*"]
|
||||||
|
|
||||||
|
[project.entry-points."tutor.plugin.v1"]
|
||||||
|
xqueue = "tutorxqueue.plugin"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = ["templates", "docs/_ext"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# E: pycodestyle errors
|
||||||
|
# I: isort
|
||||||
|
# N: pep8-naming
|
||||||
|
select = ["E", "I", "N"]
|
||||||
|
|
||||||
|
# F401: unused-import
|
||||||
|
# F841: unused-variable
|
||||||
|
# W292: missing-newline-at-end-of-file
|
||||||
|
extend-select = ["F401", "F841", "W292"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
49
setup.py
49
setup.py
@ -1,49 +0,0 @@
|
|||||||
import io
|
|
||||||
import os
|
|
||||||
from setuptools import setup, find_packages
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
with io.open(os.path.join(here, "README.rst"), "rt", encoding="utf8") as f:
|
|
||||||
readme = f.read()
|
|
||||||
|
|
||||||
about = {}
|
|
||||||
with io.open(
|
|
||||||
os.path.join(here, "tutorxqueue", "__about__.py"), "rt", encoding="utf-8"
|
|
||||||
) as f:
|
|
||||||
exec(f.read(), about)
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="tutor-xqueue",
|
|
||||||
version=about["__version__"],
|
|
||||||
url="https://docs.tutor.overhang.io/",
|
|
||||||
project_urls={
|
|
||||||
"Documentation": "https://docs.tutor.overhang.io/",
|
|
||||||
"Code": "https://github.com/overhangio/tutor-xqueue",
|
|
||||||
"Issue tracker": "https://github.com/overhangio/tutor-xqueue/issues",
|
|
||||||
"Community": "https://discuss.openedx.org",
|
|
||||||
},
|
|
||||||
license="AGPLv3",
|
|
||||||
author="Overhang.IO",
|
|
||||||
author_email="contact@overhang.io",
|
|
||||||
maintainer="eduNEXT",
|
|
||||||
description="A Tutor plugin for Xqueue (external grading system)",
|
|
||||||
long_description=readme,
|
|
||||||
long_description_content_type="text/x-rst",
|
|
||||||
packages=find_packages(exclude=["tests*"]),
|
|
||||||
include_package_data=True,
|
|
||||||
python_requires=">=3.7",
|
|
||||||
install_requires=["tutor>=15.0.0,<16.0.0", "requests"],
|
|
||||||
entry_points={"tutor.plugin.v1": ["xqueue = tutorxqueue.plugin"]},
|
|
||||||
classifiers=[
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Intended Audience :: Developers",
|
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@ -1 +1 @@
|
|||||||
__version__ = "15.0.1"
|
__version__ = "20.0.0"
|
||||||
|
|||||||
@ -4,5 +4,4 @@ xqueue-job:
|
|||||||
- ../plugins/xqueue/apps/settings/tutor.py:/openedx/xqueue/xqueue/tutor.py:ro
|
- ../plugins/xqueue/apps/settings/tutor.py:/openedx/xqueue/xqueue/tutor.py:ro
|
||||||
environment:
|
environment:
|
||||||
DJANGO_SETTINGS_MODULE: xqueue.tutor
|
DJANGO_SETTINGS_MODULE: xqueue.tutor
|
||||||
depends_on:
|
depends_on: {{ [("mysql", RUN_MYSQL)]|list_if }}
|
||||||
- mysql
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
setowner {{ APP_USER_ID }} /mounts/xqueue
|
||||||
@ -0,0 +1 @@
|
|||||||
|
- ../../data/xqueue/media:/mounts/xqueue
|
||||||
@ -9,13 +9,6 @@ xqueue:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on: {{ [("mysql", RUN_MYSQL)]|list_if }}
|
depends_on: {{ [("mysql", RUN_MYSQL)]|list_if }}
|
||||||
|
|
||||||
xqueue-permissions:
|
|
||||||
image: {{ DOCKER_IMAGE_PERMISSIONS }}
|
|
||||||
command: ["1000", "/openedx/xqueue"]
|
|
||||||
restart: on-failure
|
|
||||||
volumes:
|
|
||||||
- ../../data/xqueue/media:/openedx/xqueue
|
|
||||||
|
|
||||||
xqueue-consumer:
|
xqueue-consumer:
|
||||||
image: {{ XQUEUE_DOCKER_IMAGE }}
|
image: {{ XQUEUE_DOCKER_IMAGE }}
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@ -1,64 +1,85 @@
|
|||||||
from glob import glob
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from glob import glob
|
||||||
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import pkg_resources
|
import importlib_resources
|
||||||
import requests
|
import requests # type: ignore
|
||||||
|
|
||||||
from tutor import config as tutor_config
|
from tutor import config as tutor_config
|
||||||
|
from tutor import exceptions
|
||||||
from tutor import hooks as tutor_hooks
|
from tutor import hooks as tutor_hooks
|
||||||
from tutor.exceptions import TutorError
|
from tutor.__about__ import __version_suffix__
|
||||||
|
|
||||||
from .__about__ import __version__
|
from .__about__ import __version__
|
||||||
|
|
||||||
|
# Handle version suffix in main mode, just like tutor core
|
||||||
|
if __version_suffix__:
|
||||||
|
__version__ += "-" + __version_suffix__
|
||||||
|
|
||||||
config = {
|
config: dict[str, dict[str, Any]] = {
|
||||||
|
"defaults": {
|
||||||
|
"VERSION": __version__,
|
||||||
|
"AUTH_USERNAME": "lms",
|
||||||
|
"DOCKER_IMAGE": "{{ DOCKER_REGISTRY }}overhangio/openedx-xqueue:{{ XQUEUE_VERSION }}", # noqa: E501
|
||||||
|
"HOST": "xqueue.{{ LMS_HOST }}",
|
||||||
|
"MYSQL_DATABASE": "xqueue",
|
||||||
|
"MYSQL_USERNAME": "xqueue",
|
||||||
|
"REPOSITORY": "https://github.com/openedx/xqueue",
|
||||||
|
"REPOSITORY_VERSION": "{{ OPENEDX_COMMON_VERSION }}",
|
||||||
|
},
|
||||||
"unique": {
|
"unique": {
|
||||||
"AUTH_PASSWORD": "{{ 8|random_string }}",
|
"AUTH_PASSWORD": "{{ 8|random_string }}",
|
||||||
"MYSQL_PASSWORD": "{{ 8|random_string }}",
|
"MYSQL_PASSWORD": "{{ 8|random_string }}",
|
||||||
"SECRET_KEY": "{{ 24|random_string }}",
|
"SECRET_KEY": "{{ 24|random_string }}",
|
||||||
},
|
},
|
||||||
"defaults": {
|
|
||||||
"VERSION": __version__,
|
|
||||||
"AUTH_USERNAME": "lms",
|
|
||||||
"DOCKER_IMAGE": "{{ DOCKER_REGISTRY }}overhangio/openedx-xqueue:{{ XQUEUE_VERSION }}",
|
|
||||||
"HOST": "xqueue.{{ LMS_HOST }}",
|
|
||||||
"MYSQL_DATABASE": "xqueue",
|
|
||||||
"MYSQL_USERNAME": "xqueue",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Inizialization hooks
|
# Initialization hooks
|
||||||
tutor_hooks.Filters.COMMANDS_INIT.add_item((
|
# For each service that needs to be initialized, we load the task template
|
||||||
"mysql",
|
# and add it to the CLI_DO_INIT_TASKS filter, which tells Tutor to
|
||||||
("xqueue", "tasks", "mysql", "init"),
|
# run it as part of the `init` job.
|
||||||
))
|
for service in ["mysql", "xqueue"]:
|
||||||
|
full_path: str = str(
|
||||||
|
importlib_resources.files("tutorxqueue")
|
||||||
|
/ "templates"
|
||||||
|
/ "xqueue"
|
||||||
|
/ "tasks"
|
||||||
|
/ service
|
||||||
|
/ "init"
|
||||||
|
)
|
||||||
|
with open(full_path, encoding="utf-8") as init_task_file:
|
||||||
|
init_task: str = init_task_file.read()
|
||||||
|
tutor_hooks.Filters.CLI_DO_INIT_TASKS.add_item((service, init_task))
|
||||||
|
|
||||||
tutor_hooks.Filters.COMMANDS_INIT.add_item((
|
# Image management
|
||||||
"xqueue",
|
tutor_hooks.Filters.IMAGES_BUILD.add_item(
|
||||||
("xqueue", "tasks", "xqueue", "init"),
|
(
|
||||||
))
|
|
||||||
|
|
||||||
# Image managment
|
|
||||||
tutor_hooks.Filters.IMAGES_BUILD.add_item((
|
|
||||||
"xqueue",
|
"xqueue",
|
||||||
("plugins", "xqueue", "build", "xqueue"),
|
("plugins", "xqueue", "build", "xqueue"),
|
||||||
"{{ XQUEUE_DOCKER_IMAGE }}",
|
"{{ XQUEUE_DOCKER_IMAGE }}",
|
||||||
(),
|
(),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tutor_hooks.Filters.IMAGES_PULL.add_item((
|
tutor_hooks.Filters.IMAGES_PULL.add_item(
|
||||||
|
(
|
||||||
"xqueue",
|
"xqueue",
|
||||||
"{{ XQUEUE_DOCKER_IMAGE }}",
|
"{{ XQUEUE_DOCKER_IMAGE }}",
|
||||||
))
|
)
|
||||||
tutor_hooks.Filters.IMAGES_PUSH.add_item((
|
)
|
||||||
|
tutor_hooks.Filters.IMAGES_PUSH.add_item(
|
||||||
|
(
|
||||||
"xqueue",
|
"xqueue",
|
||||||
"{{ XQUEUE_DOCKER_IMAGE }}",
|
"{{ XQUEUE_DOCKER_IMAGE }}",
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@tutor_hooks.Filters.COMPOSE_MOUNTS.add()
|
@tutor_hooks.Filters.COMPOSE_MOUNTS.add()
|
||||||
def _mount_xqueue(volumes, name):
|
def _mount_xqueue(volumes: list[tuple[str, str]], name: str) -> list[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
When mounting xqueue with `--mount=/path/to/xqueue`,
|
When mounting xqueue with `--mount=/path/to/xqueue`,
|
||||||
bind-mount the host repo in the xqueue container.
|
bind-mount the host repo in the xqueue container.
|
||||||
@ -71,12 +92,13 @@ def _mount_xqueue(volumes, name):
|
|||||||
]
|
]
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
|
|
||||||
@click.group(help="Interact with the Xqueue server", name="xqueue")
|
@click.group(help="Interact with the Xqueue server", name="xqueue")
|
||||||
def command():
|
def command() -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@click.group(help="List and grade submissions")
|
@click.group(help="list and grade submissions")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
@click.option("-q", "--queue", default="openedx", show_default=True, help="Queue name")
|
@click.option("-q", "--queue", default="openedx", show_default=True, help="Queue name")
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -89,21 +111,21 @@ def command():
|
|||||||
"from the TUTOR_XQUEUE_URL environment variable."
|
"from the TUTOR_XQUEUE_URL environment variable."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def submissions(context, queue, url):
|
def submissions(context: click.Context, queue: str, url: str) -> None:
|
||||||
context.queue = queue
|
context.queue = queue # type: ignore
|
||||||
context.url = url
|
context.url = url # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="count", help="Count submissions in queue")
|
@click.command(name="count", help="Count submissions in queue")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def count_submissions(context):
|
def count_submissions(context: click.Context) -> None:
|
||||||
print_result(context, "count_submissions", context.queue)
|
print_result(context, "count_submissions", context.queue) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="show", help="Show last submission")
|
@click.command(name="show", help="Show last submission")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def show_submission(context):
|
def show_submission(context: click.Context) -> None:
|
||||||
print_result(context, "show_submission", context.queue)
|
print_result(context, "show_submission", context.queue) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="grade", help="Grade a specific submission")
|
@click.command(name="grade", help="Grade a specific submission")
|
||||||
@ -113,7 +135,14 @@ def show_submission(context):
|
|||||||
@click.argument("correct", type=click.BOOL)
|
@click.argument("correct", type=click.BOOL)
|
||||||
@click.argument("message")
|
@click.argument("message")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def grade_submission(context, submission_id, submission_key, grade, correct, message):
|
def grade_submission(
|
||||||
|
context: click.Context,
|
||||||
|
submission_id: str,
|
||||||
|
submission_key: str,
|
||||||
|
grade: str,
|
||||||
|
correct: str,
|
||||||
|
message: str,
|
||||||
|
) -> None:
|
||||||
print_result(
|
print_result(
|
||||||
context,
|
context,
|
||||||
"grade_submission",
|
"grade_submission",
|
||||||
@ -125,17 +154,22 @@ def grade_submission(context, submission_id, submission_key, grade, correct, mes
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def print_result(context, client_func_name, *args, **kwargs):
|
def print_result(
|
||||||
user_config = tutor_config.load(context.root)
|
context: click.Context,
|
||||||
client = Client(user_config, url=context.url)
|
client_func_name: str,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
user_config = tutor_config.load(context.root) # type: ignore
|
||||||
|
client = Client(user_config, url=context.url) # type: ignore
|
||||||
func = getattr(client, client_func_name)
|
func = getattr(client, client_func_name)
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
print(json.dumps(result, indent=2))
|
print(json.dumps(result, indent=2))
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
def __init__(self, user_config, url=None):
|
def __init__(self, user_config: dict[str, Any], url: str = "") -> None:
|
||||||
self._session = None
|
self._session: Optional[requests.Session] = None
|
||||||
self.username = user_config["XQUEUE_AUTH_USERNAME"]
|
self.username = user_config["XQUEUE_AUTH_USERNAME"]
|
||||||
self.password = user_config["XQUEUE_AUTH_PASSWORD"]
|
self.password = user_config["XQUEUE_AUTH_PASSWORD"]
|
||||||
|
|
||||||
@ -143,21 +177,21 @@ class Client:
|
|||||||
if not self.base_url:
|
if not self.base_url:
|
||||||
scheme = "https" if user_config["ENABLE_HTTPS"] else "http"
|
scheme = "https" if user_config["ENABLE_HTTPS"] else "http"
|
||||||
host = user_config["XQUEUE_HOST"]
|
host = user_config["XQUEUE_HOST"]
|
||||||
self.base_url = "{}://{}".format(scheme, host)
|
self.base_url = f"{scheme}://{host}"
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self):
|
def session(self) -> requests.Session:
|
||||||
if self._session is None:
|
if self._session is None:
|
||||||
self._session = requests.Session()
|
self._session = requests.Session()
|
||||||
return self._session
|
return self._session
|
||||||
|
|
||||||
def url(self, endpoint):
|
def url(self, endpoint: str) -> str:
|
||||||
# Don't forget to add a trailing slash to all endpoints: this is how xqueue
|
# Don't forget to add a trailing slash to all endpoints: this is how xqueue
|
||||||
# works...
|
# works...
|
||||||
return self.base_url + endpoint
|
return self.base_url + endpoint
|
||||||
|
|
||||||
def login(self):
|
def login(self) -> None:
|
||||||
response = self.request(
|
response = self.request(
|
||||||
"/xqueue/login/",
|
"/xqueue/login/",
|
||||||
method="POST",
|
method="POST",
|
||||||
@ -165,13 +199,11 @@ class Client:
|
|||||||
)
|
)
|
||||||
message = response.get("content")
|
message = response.get("content")
|
||||||
if message != "Logged in":
|
if message != "Logged in":
|
||||||
raise TutorError(
|
raise exceptions.TutorError(
|
||||||
"Could not login to xqueue server at {}. Response: '{}'".format(
|
f"Could not login to xqueue server at {self.base_url}. Response: '{message}'" # noqa: E501
|
||||||
self.base_url, message
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_submission(self, queue):
|
def show_submission(self, queue: str) -> Union[dict[str, Any], Any]:
|
||||||
response = self.request("/xqueue/get_submission/", params={"queue_name": queue})
|
response = self.request("/xqueue/get_submission/", params={"queue_name": queue})
|
||||||
if response["return_code"] != 0:
|
if response["return_code"] != 0:
|
||||||
return response
|
return response
|
||||||
@ -194,10 +226,17 @@ class Client:
|
|||||||
"return_code": response["return_code"],
|
"return_code": response["return_code"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def count_submissions(self, queue):
|
def count_submissions(self, queue: str) -> Any:
|
||||||
return self.request("/xqueue/get_queuelen/", params={"queue_name": queue})
|
return self.request("/xqueue/get_queuelen/", params={"queue_name": queue})
|
||||||
|
|
||||||
def grade_submission(self, submission_id, submission_key, grade, correct, msg):
|
def grade_submission(
|
||||||
|
self,
|
||||||
|
submission_id: str,
|
||||||
|
submission_key: str,
|
||||||
|
grade: str,
|
||||||
|
correct: bool,
|
||||||
|
msg: str,
|
||||||
|
) -> Any:
|
||||||
return self.request(
|
return self.request(
|
||||||
"/xqueue/put_result/",
|
"/xqueue/put_result/",
|
||||||
method="POST",
|
method="POST",
|
||||||
@ -211,7 +250,13 @@ class Client:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def request(self, endpoint, method="GET", data=None, params=None):
|
def request(
|
||||||
|
self,
|
||||||
|
endpoint: str,
|
||||||
|
method: str = "GET",
|
||||||
|
data: Optional[dict[str, Any]] = None,
|
||||||
|
params: Optional[dict[str, Any]] = None,
|
||||||
|
) -> Any:
|
||||||
func = getattr(self.session, method.lower())
|
func = getattr(self.session, method.lower())
|
||||||
response = func(self.url(endpoint), data=data, params=params)
|
response = func(self.url(endpoint), data=data, params=params)
|
||||||
# TODO handle errors >= 400 and non-parsable json responses
|
# TODO handle errors >= 400 and non-parsable json responses
|
||||||
@ -223,10 +268,9 @@ submissions.add_command(show_submission)
|
|||||||
submissions.add_command(grade_submission)
|
submissions.add_command(grade_submission)
|
||||||
command.add_command(submissions)
|
command.add_command(submissions)
|
||||||
|
|
||||||
####### Boilerplate code
|
|
||||||
# Add the "templates" folder as a template root
|
# Add the "templates" folder as a template root
|
||||||
tutor_hooks.Filters.ENV_TEMPLATE_ROOTS.add_item(
|
tutor_hooks.Filters.ENV_TEMPLATE_ROOTS.add_item(
|
||||||
pkg_resources.resource_filename("tutorxqueue", "templates")
|
str(importlib_resources.files("tutorxqueue") / "templates")
|
||||||
)
|
)
|
||||||
# Render the "build" and "apps" folders
|
# Render the "build" and "apps" folders
|
||||||
tutor_hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
|
tutor_hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
|
||||||
@ -236,29 +280,38 @@ tutor_hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
# Load patches from files
|
# Load patches from files
|
||||||
for path in glob(
|
for path in glob(str(importlib_resources.files("tutorxqueue") / "patches" / "*")):
|
||||||
os.path.join(
|
|
||||||
pkg_resources.resource_filename("tutorxqueue", "patches"),
|
|
||||||
"*",
|
|
||||||
)
|
|
||||||
):
|
|
||||||
with open(path, encoding="utf-8") as patch_file:
|
with open(path, encoding="utf-8") as patch_file:
|
||||||
tutor_hooks.Filters.ENV_PATCHES.add_item((os.path.basename(path), patch_file.read()))
|
tutor_hooks.Filters.ENV_PATCHES.add_item(
|
||||||
|
(os.path.basename(path), patch_file.read())
|
||||||
|
)
|
||||||
|
|
||||||
# Add cli commands filter
|
# Add cli commands filter
|
||||||
tutor_hooks.Filters.CLI_COMMANDS.add_item(command)
|
tutor_hooks.Filters.CLI_COMMANDS.add_item(command)
|
||||||
|
|
||||||
# Add configuration entries
|
# Add configuration entries
|
||||||
tutor_hooks.Filters.CONFIG_DEFAULTS.add_items(
|
tutor_hooks.Filters.CONFIG_DEFAULTS.add_items(
|
||||||
[
|
[(f"XQUEUE_{key}", value) for key, value in config.get("defaults", {}).items()]
|
||||||
(f"XQUEUE_{key}", value)
|
|
||||||
for key, value in config.get("defaults", {}).items()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
tutor_hooks.Filters.CONFIG_UNIQUE.add_items(
|
tutor_hooks.Filters.CONFIG_UNIQUE.add_items(
|
||||||
[
|
[(f"XQUEUE_{key}", value) for key, value in config.get("unique", {}).items()]
|
||||||
(f"XQUEUE_{key}", value)
|
|
||||||
for key, value in config.get("unique", {}).items()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
tutor_hooks.Filters.CONFIG_OVERRIDES.add_items(list(config.get("overrides", {}).items()))
|
tutor_hooks.Filters.CONFIG_OVERRIDES.add_items(
|
||||||
|
list(config.get("overrides", {}).items())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# Xqueue Public Host
|
||||||
|
########################################
|
||||||
|
|
||||||
|
|
||||||
|
@tutor_hooks.Filters.APP_PUBLIC_HOSTS.add()
|
||||||
|
def _xqueue_public_hosts(
|
||||||
|
hosts: list[str], context_name: Literal["local", "dev"]
|
||||||
|
) -> list[str]:
|
||||||
|
if context_name == "dev":
|
||||||
|
hosts += ["{{ XQUEUE_HOST }}:8000"]
|
||||||
|
else:
|
||||||
|
hosts += ["{{ XQUEUE_HOST }}"]
|
||||||
|
return hosts
|
||||||
|
|||||||
@ -1,31 +1,51 @@
|
|||||||
FROM docker.io/ubuntu:20.04
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM docker.io/ubuntu:24.04
|
||||||
|
|
||||||
RUN apt update && \
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Delete default UID=1000 `ubuntu` user to ensure we can use id 1000 for app user
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||||
|
apt update && \
|
||||||
apt upgrade -y && \
|
apt upgrade -y && \
|
||||||
apt install -y language-pack-en git python3 python3-pip python3-venv libmysqlclient-dev
|
apt install -y \
|
||||||
|
language-pack-en \
|
||||||
|
git \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
libmysqlclient-dev \
|
||||||
|
pkg-config
|
||||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||||
|
|
||||||
ARG APP_USER_ID=1000
|
###### Git-clone xqueue repo ######
|
||||||
|
ARG APP_USER_ID={{ HOST_USER_ID }}
|
||||||
RUN useradd --home-dir /openedx --create-home --shell /bin/bash --uid ${APP_USER_ID} app
|
RUN useradd --home-dir /openedx --create-home --shell /bin/bash --uid ${APP_USER_ID} app
|
||||||
USER ${APP_USER_ID}
|
USER ${APP_USER_ID}
|
||||||
|
|
||||||
RUN git clone https://github.com/edx/xqueue --branch {{ OPENEDX_COMMON_VERSION }} --depth 1 /openedx/xqueue
|
RUN git clone {{ XQUEUE_REPOSITORY }} --branch {{ XQUEUE_REPOSITORY_VERSION }} --depth 1 /openedx/xqueue
|
||||||
WORKDIR /openedx/xqueue
|
WORKDIR /openedx/xqueue
|
||||||
|
|
||||||
|
###### Install python venv ######
|
||||||
RUN python -m venv /openedx/venv
|
RUN python -m venv /openedx/venv
|
||||||
ENV PATH /openedx/venv/bin:${PATH}
|
ENV PATH=/openedx/venv/bin:${PATH}
|
||||||
RUN pip install --upgrade pip setuptools
|
# https://pypi.org/project/setuptools/
|
||||||
RUN pip install -r requirements.txt
|
# https://pypi.org/project/pip/
|
||||||
RUN pip install uwsgi==2.0.21
|
# https://pypi.org/project/wheel/
|
||||||
|
RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared pip install setuptools==78.1.0 pip==25.0.1 wheel==0.46.0
|
||||||
|
RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared pip install -r requirements.txt
|
||||||
|
RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared pip install uwsgi==2.0.28
|
||||||
|
|
||||||
RUN mkdir /openedx/data /openedx/data/media
|
RUN mkdir /openedx/data /openedx/data/media
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
CMD uwsgi \
|
CMD ["uwsgi", \
|
||||||
--static-map /media=/openedx/data/media/ \
|
"--static-map", "/media=/openedx/data/media/", \
|
||||||
--http 0.0.0.0:8000 \
|
"--http", "0.0.0.0:8000", \
|
||||||
--thunder-lock \
|
"--thunder-lock", \
|
||||||
--single-interpreter \
|
"--single-interpreter", \
|
||||||
--enable-threads \
|
"--enable-threads", \
|
||||||
--processes=2 \
|
"--processes=2", \
|
||||||
--wsgi-file xqueue/wsgi.py
|
"--wsgi-file", "xqueue/wsgi.py"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user