Skip to content

Commit c7fe9fd

Browse files
committed
feat: add python dependencies at runtime without rebuilding image
Since adding new python packages in LMS/CMS containers through tutor core requires rebuilding the openedx image, we have created this plugin that can add these dependencies at runtime without the need to rebuild images. The plugin implements a live dependencies system with the following components: A tutor do job (build_live_dependencies) that reads package names from LIVE_DEPENDENCIES config variable, installs them in a job runner container, zips the packages, and uploads to Django storage A monitor_livedeps daemon in each container that watches for storage changes and triggers uwsgi restarts when packages are updated An update_livedeps script that runs on uwsgi server start/restart, it downloads and extracts packages to a specified directory Modified uwsgi configuration to run the monitor_livedeps script as a daemon and execute the update_livedeps script on start/restart Dockerfile changes to set PYTHONPATH and copy required scripts into containers The solution works with both Docker Compose and Kubernetes deployments by using persistent storage instead of mounted volumes. Requirements: tutor-minio plugin or compatible S3 storage backend must be installed for persistent package storage
0 parents  commit c7fe9fd

File tree

20 files changed

+1027
-0
lines changed

20 files changed

+1027
-0
lines changed

.github/workflows/test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Run tests
2+
3+
on:
4+
pull_request:
5+
branches: [ release, main ]
6+
push:
7+
branches: [ release, main ]
8+
9+
jobs:
10+
tests:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ['3.9', '3.12']
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- name: Upgrade pip
22+
run: python -m pip install --upgrade pip
23+
- name: Install dependencies
24+
run: |
25+
pip install .[dev]
26+
- name: Test lint, types, and format
27+
run: make test

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.*.swp
2+
!.gitignore
3+
TODO
4+
__pycache__
5+
*.egg-info/
6+
/build/
7+
/dist/

LICENSE.txt

Lines changed: 662 additions & 0 deletions
Large diffs are not rendered by default.

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
recursive-include tutorlivedeps/patches *
2+
recursive-include tutorlivedeps/templates *

Makefile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.DEFAULT_GOAL := help
2+
.PHONY: docs
3+
SRC_DIRS = ./tutorlivedeps
4+
5+
# Warning: These checks are not necessarily run on every PR.
6+
test: test-lint test-types test-format test-pythonpackage # Run some static checks.
7+
8+
test-format: ## Run code formatting tests
9+
ruff format --check --diff ${SRC_DIRS}
10+
11+
test-lint: ## Run code linting tests
12+
ruff check ${SRC_DIRS}
13+
14+
test-types: ## Run type checks.
15+
mypy --exclude=templates --ignore-missing-imports --implicit-reexport --strict ${SRC_DIRS}
16+
17+
build-pythonpackage: ## Build the "tutor-livedeps" python package for upload to pypi
18+
python -m build --sdist
19+
20+
test-pythonpackage: build-pythonpackage ## Test that package can be uploaded to pypi
21+
twine check dist/tutor_livedeps-$(shell make version).tar.gz
22+
23+
format: ## Format code automatically
24+
ruff format ${SRC_DIRS}
25+
26+
fix-lint: ## Fix lint errors automatically
27+
ruff check --fix ${SRC_DIRS}
28+
29+
changelog-entry: ## Create a new changelog entry.
30+
scriv create
31+
32+
changelog: ## Collect changelog entries in the CHANGELOG.md file.
33+
scriv collect
34+
35+
version: ## Print the current tutor-livedeps version
36+
@python -c 'import io, os; about = {}; exec(io.open(os.path.join("tutorlivedeps", "__about__.py"), "rt", encoding="utf-8").read(), about); print(about["__version__"])'
37+
38+
ESCAPE = 
39+
help: ## Print this help
40+
@grep -E '^([a-zA-Z_-]+:.*?## .*|######* .+)$$' Makefile \
41+
| sed 's/######* \(.*\)/@ $(ESCAPE)[1;31m\1$(ESCAPE)[0m/g' | tr '@' '\n' \
42+
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}'

README.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Real time python package installation for Open edX
2+
==================================================
3+
4+
This is a plugin for `Tutor <https://docs.tutor.edly.io>`_ that provides real time python package installation for Open edX platforms. This means you do not need to rebuild the openedx image whenever you add a new package. This is achieved by storing these packages in Django storage and downloading them inside each LMS/CMS container.
5+
6+
Installation
7+
------------
8+
9+
This plugin depends on the `tutor-minio <https://github.com/overhangio/tutor-minio>`_ plugin to store the packages. After installing tutor-minio run the following commands:
10+
11+
``pip install git+https://github.com/overhangio/tutor-livedeps.git``
12+
13+
``tutor plugins enable livedeps``
14+
15+
16+
Configuration
17+
-------------
18+
19+
``LIVE_DEPENDENCIES`` (default: ``"[]"``)
20+
21+
To add a new package to this config run
22+
23+
``tutor config save --append LIVE_DEPENDENCIES=package_name``
24+
25+
To remove an old package from this config run
26+
27+
``tutor config save --remove LIVE_DEPENDENCIES=package_name``.
28+
29+
Then run the following command to install the packages that are present in the ``LIVE_DEPENDENCIES`` config:
30+
31+
``tutor dev/local/k8s do build-live-dependencies``
32+
33+
34+
Troubleshooting
35+
---------------
36+
37+
This Tutor plugin is maintained by Muhammad Labeeb from `Edly <https://edly.io>`__. 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.
38+
39+
License
40+
-------
41+
42+
This work is licensed under the terms of the `GNU Affero General Public License (AGPL) <https://github.com/overhangio/tutor-minio/blob/release/LICENSE.txt>`_.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- [Feature] Add python dependencies at runtime without rebuilding image. (by @mlabeeb03)

changelog.d/scriv.ini

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[scriv]
2+
version = literal: tutorlivedeps/__about__.py: __version__
3+
categories =
4+
format = md
5+
md_header_level = 2
6+
new_fragment_template =
7+
<!--
8+
Create a changelog entry for every new user-facing change. Please respect the following instructions:
9+
- Indicate breaking changes by prepending an explosion 💥 character.
10+
- Prefix your changes with either [Bugfix], [Improvement], [Feature], [Security], [Deprecation].
11+
- You may optionally append "(by @<author>)" at the end of the line, where "<author>" is either one (just one)
12+
of your GitHub username, real name or affiliated organization. These affiliations will be displayed in
13+
the release notes for every release.
14+
-->
15+
16+
<!-- - 💥[Feature] Foobarize the blorginator. This breaks plugins by renaming the `FOO_DO` filter to `BAR_DO`. (by @regisb) -->
17+
<!-- - [Improvement] This is a non-breaking change. Life is good. (by @billgates) -->
18+
entry_title_template = {%% if version %%}v{{ version }} {%% endif %%}({{ date.strftime('%%Y-%%m-%%d') }})

pyproject.toml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# https://packaging.python.org/en/latest/tutorials/packaging-projects/
2+
# https://hatch.pypa.io/latest/config/build/
3+
4+
[project]
5+
name = "tutor-livedeps"
6+
description = "livedeps plugin for Tutor"
7+
authors = [
8+
{ name = "Muhammad Labeeb"},
9+
{ email = "[email protected]" },
10+
]
11+
license = { text = "AGPL-3.0-only" }
12+
13+
readme = {file = "README.rst", content-type = "text/x-rst"}
14+
requires-python = ">= 3.9"
15+
classifiers = [
16+
"Development Status :: 3 - Alpha",
17+
"Intended Audience :: Developers",
18+
"License :: OSI Approved :: GNU Affero General Public License v3",
19+
"Operating System :: OS Independent",
20+
"Programming Language :: Python",
21+
"Programming Language :: Python :: 3.9",
22+
"Programming Language :: Python :: 3.10",
23+
"Programming Language :: Python :: 3.11",
24+
"Programming Language :: Python :: 3.12",
25+
"Programming Language :: Python :: Implementation :: CPython",
26+
"Programming Language :: Python :: Implementation :: PyPy",
27+
28+
]
29+
dependencies = [
30+
"tutor>=20.0.0,<21.0.0",
31+
"tutor-minio>=20.0.0,<21.0.0",
32+
]
33+
optional-dependencies = { dev = ["tutor[dev]>=20.0.0,<21.0.0"] }
34+
35+
# These fields will be set by hatch_build.py
36+
dynamic = ["version"]
37+
38+
[tool.hatch.version]
39+
path = "tutorlivedeps/__about__.py"
40+
41+
# https://packaging.python.org/en/latest/specifications/well-known-project-urls/#well-known-labels
42+
[project.urls]
43+
Documentation = "https://github.com/overhangio/tutor-livedeps#readme"
44+
Issues = "https://github.com/overhangio/tutor-livedeps/issues"
45+
Source = "https://github.com/overhangio/tutor-livedeps"
46+
47+
[build-system]
48+
requires = ["hatchling"]
49+
build-backend = "hatchling.build"
50+
51+
[tool.hatch.build.targets.wheel]
52+
packages = ["tutorlivedeps"]
53+
54+
[tool.hatch.build.targets.sdist]
55+
# Disable strict naming, otherwise twine is not able to detect name/version
56+
strict-naming = false
57+
include = [ "/tutorlivedeps"]
58+
exclude = ["tests*"]
59+
60+
[project.entry-points."tutor.plugin.v1"]
61+
livedeps = "tutorlivedeps.plugin"

tutorlivedeps/__about__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "20.0.0"

0 commit comments

Comments
 (0)