Compare commits

..

79 Commits

Author SHA1 Message Date
Florian du Garage Num
61c9b3e23c remove hard-coded uid 1000
Some checks failed
Run tests / tests (3.12) (push) Has been cancelled
Run tests / tests (3.9) (push) Has been cancelled
Sync with private repo / sync (push) Has been cancelled
2025-09-30 21:48:26 +02:00
Abdul Rehman
13da199f04
feat: migrate from pylint/black to ruff (#42) 2025-09-03 21:53:45 +05:00
Ahmed Khalid
05ada48be5
Merge pull request #41 from overhangio/teak 2025-06-27 17:08:45 +05:00
Syed Muhammad Dawoud Sheraz Ali
fdd8651cb1 chore: generate changelog for v20 2025-06-05 17:45:37 +05:00
jfavellar90
7348ff81d3 feat: upgrade to teak 2025-04-04 14:10:01 -05:00
Syed Muhammad Dawoud Sheraz Ali
da8aa31723
v19.0.2 (#40) 2025-03-12 15:35:10 +05:00
Syed Muhammad Dawoud Sheraz Ali
c39f3acc4f
build: Add hatch_build in sdist to fix installation issues (#39)
* build: Add hatch_build in sdist to fix installation issues
2025-03-12 15:25:29 +05:00
jfavellar90
72b2b10821 v19.0.1 2025-03-11 13:22:39 -05:00
Jhony Avella
ef8a30fecd
feat: migrate from setup.py to pyproject.toml (#38)
* feat: migrate from setup.py to pyproject.toml

* chore: addressing PR comments

* chore: addressing more comments
2025-03-11 13:21:02 -05:00
Syed Muhammad Dawoud Sheraz Ali
02a4b1efb1
build: re-add auto-add for PRs with a different target (#37) 2025-01-17 11:31:23 +05:00
Régis Behmo
7de36adacd
feat: upgrade to sumac
---------

Co-authored-by: Régis Behmo <regis@behmo.com>
Co-authored-by: Overhang.IO <ci@overhang.io>
Co-authored-by: Syed Muhammad Dawoud Sheraz Ali <dawoud.sheraz@gmail.com>
2024-12-16 21:58:55 +01:00
Syed Muhammad Dawoud Sheraz Ali
93ad17bb71 chore: update changelog 2024-12-09 18:46:47 +05:00
jfavellar90
8ce407cc40 feat: upgrade to sumac 2024-11-29 22:26:07 +05:00
Régis Behmo
eb47275595 Merge branch 'release' 2024-11-27 18:35:01 +01:00
Syed Muhammad Dawoud Sheraz Ali
a42b22d9ea feat!: Rename branches master->release, nightly->main 2024-11-27 18:34:08 +01:00
Overhang.IO
4136fc6620 Merge remote-tracking branch 'origin/master' into nightly 2024-11-14 16:27:07 +00:00
Syed Muhammad Dawoud Sheraz Ali
a73f745707
chore: remove python 3.8 references and set py39 as minimum version (#35) 2024-11-14 21:19:31 +05:00
Jhony Avella
86abc64404
Ubuntu 24.04 Upgrade (#33)
* chore: xqueue service now uses ubuntu 24.04 as base image

* chore: using 1000 UID. Updating setuptools

* chore: adding changelog entry
2024-10-22 16:08:39 -05:00
Régis Behmo
9f891b2f93 docs: *.local.edly.io -> *.local.openedx.io
The default URL to run a local platform switched from local.edly.io to
local.openedx.io. This changes makes it clearer for everyone that Tutor
is to run Open edX.

See: https://github.com/overhangio/tutor/issues/1120
2024-10-17 08:36:40 +02:00
Régis Behmo
9f6b2ffdcd ci: upgrade vendor actions 2024-10-03 11:16:24 +02:00
Régis Behmo
d478f4de4f docs: fix author domain name 2024-10-01 09:08:48 +02:00
Régis Behmo
f0ac92b85b fix: docker build deprecation warnings
With the latest Docker upgrade, we got the following warnings during
build:

	FromAsCasing: 'as' and 'FROM' keywords' casing do not match
	LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format
2024-06-21 17:12:57 +02:00
Régis Behmo
d68039216d
Merge pull request #31 from overhangio/redwood
Upgrade to Redwood
2024-06-20 03:14:22 +02:00
jfavellar90
a880f44b9f feat: upgrade to redwood 2024-06-20 01:17:00 +02:00
Jhony Avella
5db7addac8
feat: adding Python 3.12 support for Xqueue (#30) 2024-05-03 09:00:16 -05:00
Régis Behmo
fe3742a0f0 ci: don't even try to auto-add PRs to github project
Auto-adding PRs to the Github project is not working because the
github-token is not available there.
2024-03-05 12:03:03 +05:00
Régis Behmo
4bf2a47857 ci: remove now useless OPENEDX_RELEASE variable 2024-02-20 15:15:40 +01:00
Régis Behmo
d1c67f42d9 fix: remove pkg_resources for compatibility with python 3.12
pkg_resources is a package that is unavailable in python 3.12, unless
setuptools is explicitely installed. Turns out, there are replacement
functions coming from importlib_resources, which can be obtained from
the importlib-resources pypi package. This package will be installed
with tutor starting from 17.0.2.
2024-02-12 11:57:51 +01:00
Régis Behmo
02c7719c32 ci: auto-add issues and items to github project 2024-01-12 12:33:35 +01:00
jfavellar90
7f09751e8f chore: addressing comments 2023-12-11 18:12:10 +01:00
jfavellar90
8703186cef feat: upgrade to Quince 2023-12-09 17:43:42 +01:00
Régis Behmo
241283ea95 Merge branch 'master' into nightly 2023-12-09 17:42:22 +01:00
Emad Rad
8c2f78a683 feat: Added 'dev' requirements to 'extras_require' in setup
These include 'tutor' (version 16.x) for development and 'types-requests' (version 2.31.0.0). This addition helps in managing package dependencies during development.
2023-12-09 15:56:42 +01:00
Emad Rad
b0ad257f63 feat: typing added 2023-12-09 15:56:42 +01:00
Emad Rad
e1eea67baf chore: cleanup with black and isort 2023-12-09 15:56:42 +01:00
Emad Rad
add702220c chore: changelog entry added 2023-12-09 15:56:42 +01:00
Emad Rad
7038313d7e ci: test action added 2023-12-09 15:56:42 +01:00
Emad Rad
522fe2a6da feat: Makefile added 2023-12-09 15:56:42 +01:00
Régis Behmo
67c55ffe1d local.overhang.io -> local.edly.io
This is related to https://github.com/overhangio/tutor/issues/945
2023-12-09 15:54:28 +01:00
Régis Behmo
4256df3839 docs: docs.tutor.overhang.io -> docs.tutor.edly.io
See: https://github.com/overhangio/tutor/issues/945
2023-12-05 12:16:40 +01:00
Régis Behmo
be48edeb26 feat: simplify nightly version management
By pulling the version suffix from tutor, we avoid git conflicts when
merging the release branch in nightly.
2023-12-05 12:16:39 +01:00
Régis Behmo
cbe4f5af5c docs: docs.tutor.overhang.io -> docs.tutor.edly.io
See: https://github.com/overhangio/tutor/issues/945
2023-12-05 11:45:48 +01:00
Régis Behmo
18a6d5bc39
fix: add missing pkg-config package (#26)
Build fails in nightly with the following error:

	#28 5.503   × Getting requirements to build wheel did not run
	    successfully.
	#28 5.503   │ exit code: 1
	#28 5.503   ╰─> [24 lines of output]
	#28 5.503       /bin/sh: 1: pkg-config: not found
	#28 5.503       /bin/sh: 1: pkg-config: not found
	#28 5.503       Trying pkg-config --exists mysqlclient
	#28 5.503       Command 'pkg-config --exists mysqlclient' returned
	    non-zero exit status 127.
	#28 5.503       Trying pkg-config --exists mariadb
	#28 5.503       Command 'pkg-config --exists mariadb' returned non-zero
	    exit status 127.
	#28 5.503       Traceback (most recent call last):
	#28 5.503         File
	    "/openedx/venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py",
	line 353, in <module>
	#28 5.503           main()
	#28 5.503         File
	    "/openedx/venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py",
	line 335, in main
	#28 5.503           json_out['return_val'] =
	    hook(**hook_input['kwargs'])
	#28 5.503         File
	    "/openedx/venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py",
	line 118, in get_requires_for_build_wheel
	#28 5.503           return hook(config_settings)
	#28 5.503         File
	    "/tmp/pip-build-env-58chejnv/overlay/lib/python3.8/site-packages/setuptools/build_meta.py",
	line 355, in get_requires_for_build_wheel
	#28 5.503           return self._get_build_requires(config_settings,
	    requirements=['wheel'])
	#28 5.503         File
	    "/tmp/pip-build-env-58chejnv/overlay/lib/python3.8/site-packages/setuptools/build_meta.py",
	line 325, in _get_build_requires
	#28 5.503           self.run_setup()
	#28 5.503         File
	    "/tmp/pip-build-env-58chejnv/overlay/lib/python3.8/site-packages/setuptools/build_meta.py",
	line 341, in run_setup
	#28 5.503           exec(code, locals())
	#28 5.503         File "<string>", line 154, in <module>
	#28 5.503         File "<string>", line 48, in get_config_posix
	#28 5.503         File "<string>", line 27, in find_package_name
	#28 5.503       Exception: Can not find valid pkg-config name.
	#28 5.503       Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env
	    vars manuallY
2023-11-21 06:39:16 -05:00
Régis Behmo
7317758ca9 feat: simplify nightly version management
By pulling the version suffix from tutor, we avoid git conflicts when
merging the release branch in nightly.
2023-11-20 17:39:46 +01:00
Overhang.IO
f81740fa40 Merge remote-tracking branch 'origin/master' into nightly 2023-11-14 23:05:48 +00:00
jfavellar90
84ceed2f92 v16.0.2 2023-11-14 18:03:59 -05:00
ravikhetani
c93d2a3e35
fix: service "xqueue-job" depends on undefined service mysql: invalid compose project (#24)
* fix: docker compose error, dependency sql missing, when running with RUN_MYSQL=false

* fix spelling error

---------

Co-authored-by: Ravi Khetani <r.khetani@institute.global>
2023-11-14 18:01:33 -05:00
Overhang.IO
b25a7e23a4 Merge remote-tracking branch 'origin/master' into nightly 2023-10-03 06:37:34 +00:00
Régis Behmo
c88592235f chore: mark compatibility with python 3.12 2023-10-03 08:23:48 +02:00
Overhang.IO
4d75cd0f84 Merge remote-tracking branch 'origin/master' into nightly 2023-09-07 16:53:29 +00:00
Régis Behmo
5da7bd3278 fix: nightly package version
The package version may not include the "-nightly" suffix. Otherwise,
`pip install .` fails with:

    setuptools.extern.packaging.version.InvalidVersion: Invalid version: '...-nightly'
2023-09-07 18:39:53 +02:00
Overhang.IO
790405a132 Merge remote-tracking branch 'origin/master' into nightly 2023-08-14 14:26:52 +00:00
Emad Rad
9e1296b6bd
docs: typos
* chore: wrong plugin name fixed
* chore: square characters replaced with "→"
2023-08-14 16:24:05 +02:00
Régis Behmo
ee0f3563e2 Merge branch 'master' into nightly 2023-08-09 10:34:31 +02:00
jfavellar90
18f202e593 v16.0.1 2023-08-04 08:16:30 -05:00
Overhang.IO
1ea66816ea Merge remote-tracking branch 'origin/master' into nightly 2023-07-31 13:41:11 +00:00
Jhony Avella
9ab9d85fc5
chore: remove xqueue permissions container
There is now a single "permissions" container that makes all permission changes in Tutor core
2023-07-31 15:35:37 +02:00
Overhang.IO
9787d8d71b Merge remote-tracking branch 'origin/master' into nightly 2023-06-27 15:26:24 +00:00
Régis Behmo
69aff3255f fix: development status is stable 2023-06-27 15:53:57 +02:00
Régis Behmo
b3bc26036a Merge branch 'master' into nightly 2023-06-15 01:12:12 +02:00
Jhony Avella
8448e2ba2f
feat: upgrade to Palm 2023-06-15 01:04:59 +02:00
Régis Behmo
f9cf62c307 fix: nightly package version
The package version number may not include the "-nightly" suffix.
Otherwise, installation fails with:

   setuptools.extern.packaging.version.InvalidVersion: Invalid version: '15.0.7-nightly'
2023-05-26 18:47:48 +02:00
Régis Behmo
06c53909ba feat: label nightly version
This is to address https://github.com/overhangio/tutor-mfe/issues/122
As a consequence of this change, images will be tagged with a "-nightly"
suffix. Next, we'll probably have to build them periodically in CI.
2023-05-26 18:26:28 +02:00
Overhang.IO
70730f1cbf Merge remote-tracking branch 'origin/master' into nightly 2023-05-26 15:00:20 +00:00
Régis Behmo
6cf8776b29 ci: improve compatibility with main & nightly 2023-05-26 16:49:26 +02:00
Overhang.IO
e282c959e0 Merge remote-tracking branch 'origin/master' into nightly 2023-05-26 14:42:14 +00:00
Régis Behmo
ee1ade376a chore: handle nightly version numbers
Here, we make it possible to add a "-nightly" suffix to the package
version. This suffix will find its way to the Docker image tags. Thus,
the nightly branch will have different image tags. This will resolve
some confusion, as image tags are currently identical in nightly and
master.
2023-05-26 16:28:10 +02:00
Overhang.IO
c40a96e8fc Merge remote-tracking branch 'origin/master' into nightly 2023-05-22 07:46:26 +00:00
Régis Behmo
23a15c8f0f docs: fix lingering i char in changelog
Damn you vim!
2023-05-22 09:38:30 +02:00
Overhang.IO
33c4ca9538 Merge remote-tracking branch 'origin/master' into nightly 2023-05-19 14:55:46 +00:00
Régis Behmo
efab209715 docs: add a scriv-managed changelog
This will be useful for tracking changes across releases.

This partly addresses https://github.com/overhangio/tutor/issues/746
2023-05-19 16:19:56 +02:00
Overhang.IO
5595ba7283 Merge remote-tracking branch 'origin/master' into nightly 2023-05-17 08:40:24 +00:00
Régis Behmo
29e53d7fda fix: build-time warning
Installing from source triggers a warning on pip 23.0.1 if
pyproject.toml is not present. Building does not require any special
dependencies, so we just add a simple pyproject.toml file.

See: https://github.com/overhangio/tutor/issues/836
2023-05-17 10:24:21 +02:00
Overhang.IO
09278342d9 Merge remote-tracking branch 'origin/master' into nightly 2023-05-16 08:21:43 +00:00
Régis Behmo
a938fefea7 docs: instruct to install from index 2023-05-16 10:08:58 +02:00
Overhang.IO
8318502e31 Merge remote-tracking branch 'origin/master' into nightly 2023-03-19 14:37:56 +00:00
Overhang.IO
d0a40077f9 Merge remote-tracking branch 'origin/master' into nightly 2023-02-27 11:01:50 +00:00
Overhang.IO
aa3dc2b1fd Merge remote-tracking branch 'origin/master' into nightly 2022-12-22 17:28:37 +00:00
Carlos Muniz
07255c2d41 docs: change quickstart to launch
This change is due to quickstart being renamed to launch in tutor.
2022-12-12 19:08:34 +01:00
20 changed files with 476 additions and 177 deletions

View 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 }}

View File

@ -2,16 +2,16 @@ name: Sync with private repo
on:
push:
branches: [ master ]
branches: [ release, main ]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Add remote
run: git remote add overhangio https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@git.overhang.io/core/tutor-xqueue.git
- name: Push
run: git push overhangio master
run: git push overhangio $GITHUB_REF

25
.github/workflows/test.yml vendored Normal file
View 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

View File

@ -2,7 +2,6 @@ variables:
TUTOR_PLUGIN: xqueue
TUTOR_IMAGES: xqueue
TUTOR_PYPI_PACKAGE: tutor-xqueue
OPENEDX_RELEASE: olive
GITHUB_REPO: overhangio/tutor-xqueue
include:

21
.hatch_build.py Normal file
View 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
View 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)

View File

@ -1,2 +0,0 @@
recursive-include tutorxqueue/patches *
recursive-include tutorxqueue/templates *

42
Makefile Normal file
View 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}'

View File

@ -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
------------
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::
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
-----
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>
@ -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".
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::
@ -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:
.. 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
:align: center
@ -108,9 +108,11 @@ Configuration
- ``XQUEUE_DOCKER_IMAGE`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx-xqueue:{{ TUTOR_VERSION }}"``)
- ``XQUEUE_HOST`` (default: ``"xqueue.{{ LMS_HOST }}"``)
- ``XQUEUE_MYSQL_PASSWORD`` (default: ``"{{ 8|random_string }}"``)
- ``XQUEUE_MYSQL_DATABASE`` (default: ``"xqueue"``
- ``XQUEUE_MYSQL_DATABASE`` (default: ``"xqueue"``)
- ``XQUEUE_MYSQL_USERNAME`` (default: ``"xqueue"``)
- ``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.
@ -126,9 +128,9 @@ Feel free to add breakpoints (``breakpoint()``) anywhere in your source code to
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
---------------
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.

View File

@ -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
View 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
View 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]

View File

@ -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",
],
)

View File

@ -1 +1 @@
__version__ = "15.0.1"
__version__ = "20.0.0"

View File

@ -4,5 +4,4 @@ xqueue-job:
- ../plugins/xqueue/apps/settings/tutor.py:/openedx/xqueue/xqueue/tutor.py:ro
environment:
DJANGO_SETTINGS_MODULE: xqueue.tutor
depends_on:
- mysql
depends_on: {{ [("mysql", RUN_MYSQL)]|list_if }}

View File

@ -0,0 +1 @@
setowner {{ APP_USER_ID }} /mounts/xqueue

View File

@ -0,0 +1 @@
- ../../data/xqueue/media:/mounts/xqueue

View File

@ -9,13 +9,6 @@ xqueue:
restart: unless-stopped
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:
image: {{ XQUEUE_DOCKER_IMAGE }}
volumes:

View File

@ -1,64 +1,85 @@
from glob import glob
from __future__ import annotations
import json
import os
from glob import glob
from typing import Any, Literal, Optional, Union
import click
import pkg_resources
import requests
import importlib_resources
import requests # type: ignore
from tutor import config as tutor_config
from tutor import exceptions
from tutor import hooks as tutor_hooks
from tutor.exceptions import TutorError
from tutor.__about__ import __version_suffix__
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": {
"AUTH_PASSWORD": "{{ 8|random_string }}",
"MYSQL_PASSWORD": "{{ 8|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
tutor_hooks.Filters.COMMANDS_INIT.add_item((
"mysql",
("xqueue", "tasks", "mysql", "init"),
))
# Initialization hooks
# For each service that needs to be initialized, we load the task template
# and add it to the CLI_DO_INIT_TASKS filter, which tells Tutor to
# 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((
"xqueue",
("xqueue", "tasks", "xqueue", "init"),
))
# Image management
tutor_hooks.Filters.IMAGES_BUILD.add_item(
(
"xqueue",
("plugins", "xqueue", "build", "xqueue"),
"{{ XQUEUE_DOCKER_IMAGE }}",
(),
)
)
# Image managment
tutor_hooks.Filters.IMAGES_BUILD.add_item((
"xqueue",
("plugins", "xqueue", "build", "xqueue"),
"{{ XQUEUE_DOCKER_IMAGE }}",
(),
))
tutor_hooks.Filters.IMAGES_PULL.add_item(
(
"xqueue",
"{{ XQUEUE_DOCKER_IMAGE }}",
)
)
tutor_hooks.Filters.IMAGES_PUSH.add_item(
(
"xqueue",
"{{ XQUEUE_DOCKER_IMAGE }}",
)
)
tutor_hooks.Filters.IMAGES_PULL.add_item((
"xqueue",
"{{ XQUEUE_DOCKER_IMAGE }}",
))
tutor_hooks.Filters.IMAGES_PUSH.add_item((
"xqueue",
"{{ XQUEUE_DOCKER_IMAGE }}",
))
@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`,
bind-mount the host repo in the xqueue container.
@ -71,12 +92,13 @@ def _mount_xqueue(volumes, name):
]
return volumes
@click.group(help="Interact with the Xqueue server", name="xqueue")
def command():
def command() -> None:
pass
@click.group(help="List and grade submissions")
@click.group(help="list and grade submissions")
@click.pass_obj
@click.option("-q", "--queue", default="openedx", show_default=True, help="Queue name")
@click.option(
@ -89,21 +111,21 @@ def command():
"from the TUTOR_XQUEUE_URL environment variable."
),
)
def submissions(context, queue, url):
context.queue = queue
context.url = url
def submissions(context: click.Context, queue: str, url: str) -> None:
context.queue = queue # type: ignore
context.url = url # type: ignore
@click.command(name="count", help="Count submissions in queue")
@click.pass_obj
def count_submissions(context):
print_result(context, "count_submissions", context.queue)
def count_submissions(context: click.Context) -> None:
print_result(context, "count_submissions", context.queue) # type: ignore
@click.command(name="show", help="Show last submission")
@click.pass_obj
def show_submission(context):
print_result(context, "show_submission", context.queue)
def show_submission(context: click.Context) -> None:
print_result(context, "show_submission", context.queue) # type: ignore
@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("message")
@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(
context,
"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):
user_config = tutor_config.load(context.root)
client = Client(user_config, url=context.url)
def print_result(
context: click.Context,
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)
result = func(*args, **kwargs)
print(json.dumps(result, indent=2))
class Client:
def __init__(self, user_config, url=None):
self._session = None
def __init__(self, user_config: dict[str, Any], url: str = "") -> None:
self._session: Optional[requests.Session] = None
self.username = user_config["XQUEUE_AUTH_USERNAME"]
self.password = user_config["XQUEUE_AUTH_PASSWORD"]
@ -143,21 +177,21 @@ class Client:
if not self.base_url:
scheme = "https" if user_config["ENABLE_HTTPS"] else "http"
host = user_config["XQUEUE_HOST"]
self.base_url = "{}://{}".format(scheme, host)
self.base_url = f"{scheme}://{host}"
self.login()
@property
def session(self):
def session(self) -> requests.Session:
if self._session is None:
self._session = requests.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
# works...
return self.base_url + endpoint
def login(self):
def login(self) -> None:
response = self.request(
"/xqueue/login/",
method="POST",
@ -165,13 +199,11 @@ class Client:
)
message = response.get("content")
if message != "Logged in":
raise TutorError(
"Could not login to xqueue server at {}. Response: '{}'".format(
self.base_url, message
)
raise exceptions.TutorError(
f"Could not login to xqueue server at {self.base_url}. Response: '{message}'" # noqa: E501
)
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})
if response["return_code"] != 0:
return response
@ -194,10 +226,17 @@ class Client:
"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})
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(
"/xqueue/put_result/",
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())
response = func(self.url(endpoint), data=data, params=params)
# TODO handle errors >= 400 and non-parsable json responses
@ -223,10 +268,9 @@ submissions.add_command(show_submission)
submissions.add_command(grade_submission)
command.add_command(submissions)
####### Boilerplate code
# Add the "templates" folder as a template root
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
tutor_hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
@ -236,29 +280,38 @@ tutor_hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
],
)
# Load patches from files
for path in glob(
os.path.join(
pkg_resources.resource_filename("tutorxqueue", "patches"),
"*",
)
):
for path in glob(str(importlib_resources.files("tutorxqueue") / "patches" / "*")):
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
tutor_hooks.Filters.CLI_COMMANDS.add_item(command)
# Add configuration entries
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(
[
(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

View File

@ -1,31 +1,51 @@
FROM docker.io/ubuntu:20.04
# syntax=docker/dockerfile:1
FROM docker.io/ubuntu:24.04
RUN apt update && \
apt upgrade -y && \
apt install -y language-pack-en git python3 python3-pip python3-venv libmysqlclient-dev
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 install -y \
language-pack-en \
git \
python3 \
python3-pip \
python3-venv \
libmysqlclient-dev \
pkg-config
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
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
###### Install python venv ######
RUN python -m venv /openedx/venv
ENV PATH /openedx/venv/bin:${PATH}
RUN pip install --upgrade pip setuptools
RUN pip install -r requirements.txt
RUN pip install uwsgi==2.0.21
ENV PATH=/openedx/venv/bin:${PATH}
# https://pypi.org/project/setuptools/
# https://pypi.org/project/pip/
# 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
EXPOSE 8000
CMD uwsgi \
--static-map /media=/openedx/data/media/ \
--http 0.0.0.0:8000 \
--thunder-lock \
--single-interpreter \
--enable-threads \
--processes=2 \
--wsgi-file xqueue/wsgi.py
CMD ["uwsgi", \
"--static-map", "/media=/openedx/data/media/", \
"--http", "0.0.0.0:8000", \
"--thunder-lock", \
"--single-interpreter", \
"--enable-threads", \
"--processes=2", \
"--wsgi-file", "xqueue/wsgi.py"]