tutor 23.0.0.dev3201__tar.gz → 23.0.0.dev3205__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/PKG-INFO +4 -1
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/requirements/base.in +1 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/requirements/dev.txt +6 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/compose.py +3 -2
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/config.py +15 -2
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/k8s.py +3 -2
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/plugins.py +12 -4
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/config.py +95 -2
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/hooks/catalog.py +5 -1
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/interactive.py +11 -2
- tutor-23.0.0.dev3205/tutor/serialize.py +193 -0
- tutor-23.0.0.dev3201/tutor/serialize.py +0 -80
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/.gitignore +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/.hatch_build.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/LICENSE.txt +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/README.rst +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/pyproject.toml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/requirements/plugins.txt +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/__about__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/bindmount.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/cli.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/context.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/dev.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/images.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/jobs.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/jobs_utils.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/local.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/mounts.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/params.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/upgrade/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/upgrade/common.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/upgrade/compose.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/commands/upgrade/k8s.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/hooks/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/hooks/actions.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/hooks/contexts.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/hooks/filters.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/core/hooks/priorities.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/env.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/exceptions.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/fmt.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/hooks/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/images.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/base.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/indexes.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/openedx.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/v0.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/plugins/v1.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/py.typed +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/tasks.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/caddy/Caddyfile +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/cms.env.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/lms.env.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/partials/auth.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/granian-config +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/development.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/production.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/test.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/development.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/production.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/test.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/partials/common_all.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/partials/common_cms.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/partials/common_lms.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/partials/common_test.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/permissions/setowners.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/redis/redis.conf +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/Dockerfile +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/bin/reload-granian +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/bin/run-granian +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/revisions.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/assets.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/i18n.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/granian-config +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/__init__.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/assets.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/i18n.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/partials/assets.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/partials/i18n.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/themes/README +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/permissions/Dockerfile +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/permissions/setowner.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/config/base.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/config/defaults.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/dev/docker-compose.jobs.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/dev/docker-compose.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/cms.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/lms.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/meilisearch.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/mounted-directories.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/mysql.sh +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/deployments.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/jobs.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/namespace.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/override.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/services.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/k8s/volumes.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/kustomization.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/local/docker-compose.jobs.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/local/docker-compose.prod.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/local/docker-compose.yml +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/version +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/types.py +0 -0
- {tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tutor
|
|
3
|
-
Version: 23.0.0.
|
|
3
|
+
Version: 23.0.0.dev3205
|
|
4
4
|
Summary: The Docker-based Open edX distribution designed for peace of mind
|
|
5
5
|
Project-URL: Homepage, https://docs.tutor.edly.io/
|
|
6
6
|
Project-URL: Documentation, https://docs.tutor.edly.io/
|
|
@@ -33,6 +33,7 @@ Requires-Dist: mypy
|
|
|
33
33
|
Requires-Dist: packaging
|
|
34
34
|
Requires-Dist: pycryptodome>=3.17.0
|
|
35
35
|
Requires-Dist: pyyaml>=6.0
|
|
36
|
+
Requires-Dist: ruamel-yaml>=0.18.11
|
|
36
37
|
Requires-Dist: typing-extensions>=4.4.0
|
|
37
38
|
Provides-Extra: dev
|
|
38
39
|
Requires-Dist: altgraph==0.17.5; extra == 'dev'
|
|
@@ -92,6 +93,8 @@ Requires-Dist: requests==2.32.5; extra == 'dev'
|
|
|
92
93
|
Requires-Dist: rfc3986==2.0.0; extra == 'dev'
|
|
93
94
|
Requires-Dist: rich==15.0.0; extra == 'dev'
|
|
94
95
|
Requires-Dist: rsa==4.9.1; extra == 'dev'
|
|
96
|
+
Requires-Dist: ruamel-yaml-clib==0.2.15; extra == 'dev'
|
|
97
|
+
Requires-Dist: ruamel-yaml==0.19.1; extra == 'dev'
|
|
95
98
|
Requires-Dist: ruff==0.15.14; extra == 'dev'
|
|
96
99
|
Requires-Dist: scriv==1.7.0; extra == 'dev'
|
|
97
100
|
Requires-Dist: six==1.17.0; extra == 'dev'
|
|
@@ -190,6 +190,12 @@ rsa==4.9.1
|
|
|
190
190
|
# via
|
|
191
191
|
# -r requirements/base.txt
|
|
192
192
|
# google-auth
|
|
193
|
+
ruamel-yaml==0.19.1
|
|
194
|
+
# via -r requirements/base.txt
|
|
195
|
+
ruamel-yaml-clib==0.2.15
|
|
196
|
+
# via
|
|
197
|
+
# -r requirements/base.txt
|
|
198
|
+
# ruamel-yaml
|
|
193
199
|
ruff==0.15.14
|
|
194
200
|
# via -r requirements/dev.in
|
|
195
201
|
scriv==1.7.0
|
|
@@ -222,13 +222,14 @@ def interactive_configuration(
|
|
|
222
222
|
run_for_prod: t.Optional[bool] = None,
|
|
223
223
|
) -> None:
|
|
224
224
|
config = tutor_config.load_minimal(context.obj.root)
|
|
225
|
+
answered: list[str] = []
|
|
225
226
|
if interactive:
|
|
226
227
|
click.echo(fmt.title("Interactive platform configuration"))
|
|
227
|
-
interactive_config.ask_questions(
|
|
228
|
+
answered = interactive_config.ask_questions(
|
|
228
229
|
config,
|
|
229
230
|
run_for_prod=run_for_prod,
|
|
230
231
|
)
|
|
231
|
-
tutor_config.save_config_file(context.obj.root, config)
|
|
232
|
+
tutor_config.save_config_file(context.obj.root, config, explicit=answered)
|
|
232
233
|
config = tutor_config.load_full(context.obj.root)
|
|
233
234
|
tutor_env.save(context.obj.root, config)
|
|
234
235
|
|
|
@@ -170,8 +170,9 @@ def save(
|
|
|
170
170
|
if run_clean:
|
|
171
171
|
env.delete_env_dir(context.root)
|
|
172
172
|
|
|
173
|
+
answered: list[str] = []
|
|
173
174
|
if interactive:
|
|
174
|
-
interactive_config.ask_questions(config)
|
|
175
|
+
answered = interactive_config.ask_questions(config)
|
|
175
176
|
elif clean_env:
|
|
176
177
|
env.delete_env_dir(context.root)
|
|
177
178
|
if set_vars:
|
|
@@ -207,7 +208,19 @@ def save(
|
|
|
207
208
|
for key in unset_vars:
|
|
208
209
|
config.pop(key, None)
|
|
209
210
|
if not env_only:
|
|
210
|
-
|
|
211
|
+
# Entries that the user named on the command line are saved as literal
|
|
212
|
+
# values, even when config.yml holds a Jinja expression that renders to
|
|
213
|
+
# the same result. So are the interactive answers that differ from the
|
|
214
|
+
# current value, but not the accepted defaults: accepting a default is
|
|
215
|
+
# not a request to replace an expression by its result.
|
|
216
|
+
explicit = (
|
|
217
|
+
answered
|
|
218
|
+
+ [key for key, _value in set_vars]
|
|
219
|
+
+ [key for key, _value in append_vars]
|
|
220
|
+
+ [key for key, _value in remove_vars]
|
|
221
|
+
+ list(unset_vars)
|
|
222
|
+
)
|
|
223
|
+
tutor_config.save_config_file(context.root, config, explicit=explicit)
|
|
211
224
|
|
|
212
225
|
# Reload configuration, without version checking
|
|
213
226
|
config = tutor_config.load_full(context.root)
|
|
@@ -241,10 +241,11 @@ def launch(context: click.Context, non_interactive: bool) -> None:
|
|
|
241
241
|
)
|
|
242
242
|
|
|
243
243
|
config = tutor_config.load_minimal(context.obj.root)
|
|
244
|
+
answered: list[str] = []
|
|
244
245
|
if not non_interactive:
|
|
245
246
|
click.echo(fmt.title("Interactive platform configuration"))
|
|
246
|
-
interactive_config.ask_questions(config, run_for_prod=True)
|
|
247
|
-
tutor_config.save_config_file(context.obj.root, config)
|
|
247
|
+
answered = interactive_config.ask_questions(config, run_for_prod=True)
|
|
248
|
+
tutor_config.save_config_file(context.obj.root, config, explicit=answered)
|
|
248
249
|
config = tutor_config.load_full(context.obj.root)
|
|
249
250
|
tutor_env.save(context.obj.root, config)
|
|
250
251
|
|
|
@@ -143,7 +143,9 @@ def enable(context: click.Context, plugin_names: list[str]) -> None:
|
|
|
143
143
|
plugins.load(plugin)
|
|
144
144
|
fmt.echo_info(f"Plugin {plugin} enabled")
|
|
145
145
|
tutor_config.save_enabled_plugins(config)
|
|
146
|
-
tutor_config.save_config_file(
|
|
146
|
+
tutor_config.save_config_file(
|
|
147
|
+
context.obj.root, config, explicit=[tutor_config.PLUGINS_CONFIG_KEY]
|
|
148
|
+
)
|
|
147
149
|
context.invoke(config_save_command, env_only=True)
|
|
148
150
|
|
|
149
151
|
|
|
@@ -166,7 +168,9 @@ def disable(context: click.Context, plugin_names: list[str]) -> None:
|
|
|
166
168
|
disabled.append(plugin)
|
|
167
169
|
fmt.echo_info(f"Plugin {plugin} disabled")
|
|
168
170
|
if disabled:
|
|
169
|
-
tutor_config.save_config_file(
|
|
171
|
+
tutor_config.save_config_file(
|
|
172
|
+
context.obj.root, config, explicit=[tutor_config.PLUGINS_CONFIG_KEY]
|
|
173
|
+
)
|
|
170
174
|
context.invoke(config_save_command, env_only=True)
|
|
171
175
|
|
|
172
176
|
|
|
@@ -458,7 +462,9 @@ def index_add(context: Context, url: str) -> None:
|
|
|
458
462
|
"""
|
|
459
463
|
config = tutor_config.load_minimal(context.root)
|
|
460
464
|
if indexes.add(url, config):
|
|
461
|
-
tutor_config.save_config_file(
|
|
465
|
+
tutor_config.save_config_file(
|
|
466
|
+
context.root, config, explicit=[indexes.PLUGIN_INDEXES_KEY]
|
|
467
|
+
)
|
|
462
468
|
update_indexes(config)
|
|
463
469
|
else:
|
|
464
470
|
fmt.echo_alert("Plugin index was already added")
|
|
@@ -473,7 +479,9 @@ def index_remove(context: Context, url: str) -> None:
|
|
|
473
479
|
"""
|
|
474
480
|
config = tutor_config.load_minimal(context.root)
|
|
475
481
|
if indexes.remove(url, config):
|
|
476
|
-
tutor_config.save_config_file(
|
|
482
|
+
tutor_config.save_config_file(
|
|
483
|
+
context.root, config, explicit=[indexes.PLUGIN_INDEXES_KEY]
|
|
484
|
+
)
|
|
477
485
|
update_indexes(config)
|
|
478
486
|
else:
|
|
479
487
|
fmt.echo_alert("Plugin index not present")
|
|
@@ -278,14 +278,107 @@ def convert_json2yml(root: str) -> None:
|
|
|
278
278
|
)
|
|
279
279
|
|
|
280
280
|
|
|
281
|
-
def save_config_file(
|
|
281
|
+
def save_config_file(
|
|
282
|
+
root: str, config: Config, explicit: t.Optional[t.Iterable[str]] = None
|
|
283
|
+
) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Write the configuration to config.yml.
|
|
286
|
+
|
|
287
|
+
The file is not re-generated from scratch: instead, the document that is
|
|
288
|
+
already on disk is modified in place, such that comments, key order and
|
|
289
|
+
hand-written Jinja expressions are all preserved. Only the entries that
|
|
290
|
+
actually changed are written, and the file is left untouched when there is
|
|
291
|
+
nothing to change.
|
|
292
|
+
|
|
293
|
+
`explicit` lists the entries that the user set explicitly, for instance with
|
|
294
|
+
`tutor config save --set KEY=VALUE`. Those are always written as literal
|
|
295
|
+
values, even when the file happens to contain a Jinja expression that
|
|
296
|
+
renders to the same result.
|
|
297
|
+
"""
|
|
282
298
|
path = config_path(root)
|
|
299
|
+
document, layout = load_config_document(path)
|
|
300
|
+
if not update_config_document(document, config, set(explicit or [])):
|
|
301
|
+
# Nothing changed: leave the file alone, mtime included.
|
|
302
|
+
return
|
|
283
303
|
utils.ensure_file_directory_exists(path)
|
|
284
304
|
with open(path, "w", encoding="utf-8") as of:
|
|
285
|
-
serialize.
|
|
305
|
+
serialize.dump_round_trip(document, of, layout)
|
|
286
306
|
fmt.echo_info(f"Configuration saved to {path}")
|
|
287
307
|
|
|
288
308
|
|
|
309
|
+
def load_config_document(path: str) -> tuple[t.Any, serialize.YamlLayout]:
|
|
310
|
+
"""
|
|
311
|
+
Load config.yml as an editable document that remembers its own formatting.
|
|
312
|
+
|
|
313
|
+
An empty document is returned when the file does not exist yet, or when it
|
|
314
|
+
does not hold a mapping.
|
|
315
|
+
"""
|
|
316
|
+
if not os.path.exists(path):
|
|
317
|
+
return serialize.empty_document(), serialize.YamlLayout()
|
|
318
|
+
with open(path, encoding="utf-8") as f:
|
|
319
|
+
document, layout = serialize.load_round_trip(f.read())
|
|
320
|
+
if not isinstance(document, dict):
|
|
321
|
+
return serialize.empty_document(), layout
|
|
322
|
+
return document, layout
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def update_config_document(document: t.Any, config: Config, explicit: set[str]) -> bool:
|
|
326
|
+
"""
|
|
327
|
+
Apply `config` to an existing config.yml document, in place.
|
|
328
|
+
|
|
329
|
+
Return True if the document was modified, and thus needs to be written back
|
|
330
|
+
to disk.
|
|
331
|
+
"""
|
|
332
|
+
changed = False
|
|
333
|
+
for key, value in config.items():
|
|
334
|
+
if key not in document:
|
|
335
|
+
# New entry: append it at the end of the document.
|
|
336
|
+
document[key] = value
|
|
337
|
+
changed = True
|
|
338
|
+
continue
|
|
339
|
+
if document[key] == value:
|
|
340
|
+
continue
|
|
341
|
+
if key not in explicit and is_stable_expression(config, document[key]):
|
|
342
|
+
# The file holds a Jinja expression that is re-evaluated on every
|
|
343
|
+
# load, so the value we have in memory is derived data: saving it
|
|
344
|
+
# would replace the expression by one of its results. Leave it be.
|
|
345
|
+
continue
|
|
346
|
+
document[key] = value
|
|
347
|
+
changed = True
|
|
348
|
+
for key in [key for key in document if key not in config]:
|
|
349
|
+
del document[key]
|
|
350
|
+
changed = True
|
|
351
|
+
return changed
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def is_stable_expression(config: Config, raw_value: t.Any) -> bool:
|
|
355
|
+
"""
|
|
356
|
+
Check whether `raw_value`, as read from config.yml, is a Jinja expression
|
|
357
|
+
that always renders to the same result.
|
|
358
|
+
|
|
359
|
+
This is what allows a hand-written entry such as:
|
|
360
|
+
|
|
361
|
+
CMS_HOST: "studio.{{ LMS_HOST }}"
|
|
362
|
+
|
|
363
|
+
to survive a save, instead of being flattened to its result. Expressions
|
|
364
|
+
that render to something different every time, such as
|
|
365
|
+
"{{ 24|random_string }}", are not stable: those are frozen to a literal,
|
|
366
|
+
which is how generated secrets have always behaved.
|
|
367
|
+
|
|
368
|
+
Rendering happens against the final configuration, so an expression that
|
|
369
|
+
points at another entry keeps working even when that other entry just
|
|
370
|
+
changed.
|
|
371
|
+
"""
|
|
372
|
+
try:
|
|
373
|
+
rendered = env.render_unknown(config, raw_value)
|
|
374
|
+
if rendered == raw_value:
|
|
375
|
+
# Not an expression at all, just a plain value.
|
|
376
|
+
return False
|
|
377
|
+
return bool(rendered == env.render_unknown(config, raw_value))
|
|
378
|
+
except exceptions.TutorError:
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
|
|
289
382
|
def config_path(root: str) -> str:
|
|
290
383
|
return os.path.join(root, CONFIG_FILENAME)
|
|
291
384
|
|
|
@@ -287,6 +287,10 @@ class Filters:
|
|
|
287
287
|
DOCKER_BUILD_COMMAND: Filter[list[str], []] = Filter()
|
|
288
288
|
|
|
289
289
|
#: List of patches that should be inserted in a given location of the templates.
|
|
290
|
+
#: Each ``{{ patch("name") }}`` statement in the templates is replaced by the
|
|
291
|
+
#: rendered content of all the patches with the same name. See :ref:`template_patches`
|
|
292
|
+
#: for a detailed explanation and the :ref:`template patch catalog <patches>` for
|
|
293
|
+
#: the list of all available patches.
|
|
290
294
|
#:
|
|
291
295
|
#: :parameter list[tuple[str, str]] patches: pairs of (name, content) tuples. Use this
|
|
292
296
|
#: filter to modify the Tutor templates.
|
|
@@ -325,7 +329,7 @@ class Filters:
|
|
|
325
329
|
#: - ``list_if``: In a list of ``(value, condition)`` tuples, return the list of ``value`` for which the ``condition`` is true.
|
|
326
330
|
#: - ``long_to_base64``: Base-64 encode a long integer.
|
|
327
331
|
#: - ``iter_values_named``: Yield the values of the configuration settings that match a certain pattern. Example: ``{% for value in iter_values_named(prefix="KEY", suffix="SUFFIX")%}...{% endfor %}``. By default, only non-empty values are yielded. To iterate also on empty values, pass the ``allow_empty=True`` argument.
|
|
328
|
-
#: - ``patch``: See :ref:`
|
|
332
|
+
#: - ``patch``: See :ref:`template_patches`.
|
|
329
333
|
#: - ``random_string``: Return a random string of the given length composed of ASCII letters and digits. Example: ``{{ 8|random_string }}``.
|
|
330
334
|
#: - ``reverse_host``: Reverse a domain name (see `reference <https://en.wikipedia.org/wiki/Reverse_domain_name_notation>`__). Example: ``{{ "demo.myopenedx.com"|reverse_host }}`` is equal to "com.myopenedx.demo".
|
|
331
335
|
#: - ``rsa_import_key``: Import a PEM-formatted RSA key and return the corresponding object.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
1
2
|
from typing import List, Optional
|
|
2
3
|
|
|
3
4
|
import click
|
|
@@ -7,7 +8,7 @@ from . import env, exceptions, fmt, hooks
|
|
|
7
8
|
from .types import Config, get_typed
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def ask_questions(config: Config, run_for_prod: Optional[bool] = None) ->
|
|
11
|
+
def ask_questions(config: Config, run_for_prod: Optional[bool] = None) -> List[str]:
|
|
11
12
|
"""
|
|
12
13
|
Interactively ask questions to collect configuration values from the user.
|
|
13
14
|
|
|
@@ -16,8 +17,10 @@ def ask_questions(config: Config, run_for_prod: Optional[bool] = None) -> None:
|
|
|
16
17
|
run_for_prod: Whether platform should be configured for production.
|
|
17
18
|
If None, then ask the user.
|
|
18
19
|
Returns:
|
|
19
|
-
|
|
20
|
+
The names of the entries whose value was changed by the answers. Entries
|
|
21
|
+
whose default value was accepted are not part of this list.
|
|
20
22
|
"""
|
|
23
|
+
before = deepcopy(config)
|
|
21
24
|
defaults = tutor_config.get_defaults()
|
|
22
25
|
if run_for_prod is None:
|
|
23
26
|
run_for_prod = config.get("LMS_HOST") not in [
|
|
@@ -154,6 +157,12 @@ def ask_questions(config: Config, run_for_prod: Optional[bool] = None) -> None:
|
|
|
154
157
|
|
|
155
158
|
hooks.Actions.CONFIG_INTERACTIVE.do(config)
|
|
156
159
|
|
|
160
|
+
return [
|
|
161
|
+
key
|
|
162
|
+
for key in set(before) | set(config)
|
|
163
|
+
if key not in before or key not in config or before[key] != config[key]
|
|
164
|
+
]
|
|
165
|
+
|
|
157
166
|
|
|
158
167
|
def ask(question: str, key: str, config: Config, defaults: Config) -> None:
|
|
159
168
|
default = get_typed(defaults, key, str)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import typing as t
|
|
5
|
+
from _io import TextIOWrapper
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from ruamel.yaml import YAML
|
|
9
|
+
from ruamel.yaml.comments import CommentedMap
|
|
10
|
+
from ruamel.yaml.resolver import VersionedResolver
|
|
11
|
+
from ruamel.yaml.util import load_yaml_guess_indent
|
|
12
|
+
from yaml.parser import ParserError
|
|
13
|
+
from yaml.scanner import ScannerError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load(stream: t.Union[str, t.IO[str]]) -> t.Any:
|
|
17
|
+
return yaml.load(stream, Loader=yaml.SafeLoader)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_all(stream: str) -> t.Iterator[t.Any]:
|
|
21
|
+
return yaml.load_all(stream, Loader=yaml.SafeLoader)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def dump_all(documents: t.Sequence[t.Any], fileobj: TextIOWrapper) -> None:
|
|
25
|
+
yaml.safe_dump_all(
|
|
26
|
+
documents, stream=fileobj, default_flow_style=False, allow_unicode=True
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def dump(content: t.Any, fileobj: TextIOWrapper) -> None:
|
|
31
|
+
yaml.dump(content, stream=fileobj, default_flow_style=False, allow_unicode=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def dumps(content: t.Any) -> str:
|
|
35
|
+
result = yaml.dump(content, default_flow_style=False, allow_unicode=True)
|
|
36
|
+
assert isinstance(result, str)
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class YamlLayout(t.NamedTuple):
|
|
41
|
+
"""
|
|
42
|
+
Layout of a yaml document that the round-trip loader does not carry inside
|
|
43
|
+
the document itself, and that must therefore be handed back to the dumper.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
sequence_indent: int = 2
|
|
47
|
+
sequence_dash_offset: int = 0
|
|
48
|
+
explicit_start: bool = False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Yaml11Resolver(VersionedResolver):
|
|
52
|
+
"""
|
|
53
|
+
Resolve plain scalars with the YAML 1.1 rules, which are the ones used by
|
|
54
|
+
PyYAML, instead of the YAML 1.2 rules that ruamel.yaml applies by default.
|
|
55
|
+
|
|
56
|
+
Files that are written by the round-trip dumper are read back with PyYAML, so
|
|
57
|
+
both must agree on the type of every value. Under YAML 1.2, `on`, `yes` and
|
|
58
|
+
`0777` are a string, a string and the integer 777, while PyYAML loads them as
|
|
59
|
+
True, True and 511. With this resolver, the round-trip loader returns the same
|
|
60
|
+
values as PyYAML, and the dumper quotes any string that PyYAML would load as
|
|
61
|
+
something else.
|
|
62
|
+
|
|
63
|
+
Note that ruamel.yaml would also switch to YAML 1.1 if we set `YAML.version`,
|
|
64
|
+
but then every document would be dumped with a "%YAML 1.1" directive.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def processing_version(self) -> t.Any:
|
|
69
|
+
return (1, 1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def round_trip_yaml(layout: YamlLayout) -> YAML:
|
|
73
|
+
"""
|
|
74
|
+
YAML parser/dumper that preserves comments, key order and quoting style.
|
|
75
|
+
|
|
76
|
+
This is considerably slower than the SafeLoader used everywhere else, so it
|
|
77
|
+
should only be used for the few files that users are expected to edit by
|
|
78
|
+
hand, such as config.yml.
|
|
79
|
+
"""
|
|
80
|
+
yaml_rt = YAML(typ="rt")
|
|
81
|
+
yaml_rt.Resolver = Yaml11Resolver
|
|
82
|
+
# PyYAML keeps the last of duplicate keys, while ruamel.yaml refuses to load
|
|
83
|
+
# them. Accept them too: ruamel.yaml keeps the first value, but the save then
|
|
84
|
+
# sees that it differs from the one we loaded with PyYAML, and writes the
|
|
85
|
+
# latter back, once.
|
|
86
|
+
yaml_rt.allow_duplicate_keys = True
|
|
87
|
+
yaml_rt.preserve_quotes = True
|
|
88
|
+
yaml_rt.default_flow_style = False
|
|
89
|
+
yaml_rt.allow_unicode = True
|
|
90
|
+
yaml_rt.explicit_start = layout.explicit_start
|
|
91
|
+
yaml_rt.indent(
|
|
92
|
+
mapping=2,
|
|
93
|
+
sequence=layout.sequence_indent,
|
|
94
|
+
offset=layout.sequence_dash_offset,
|
|
95
|
+
)
|
|
96
|
+
# Never re-wrap long values, such as RSA private keys, onto several lines.
|
|
97
|
+
yaml_rt.width = 2**16
|
|
98
|
+
return yaml_rt
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def load_round_trip(text: str) -> tuple[t.Any, YamlLayout]:
|
|
102
|
+
"""
|
|
103
|
+
Load a yaml document, preserving comments, key order and quoting style.
|
|
104
|
+
|
|
105
|
+
Return the document, along with the layout that must be passed back to
|
|
106
|
+
`dump_round_trip` for the document to be written out as the user wrote it.
|
|
107
|
+
The layout does not affect parsing, so the default one is good enough to load
|
|
108
|
+
the document with.
|
|
109
|
+
"""
|
|
110
|
+
document, sequence_indent, dash_offset = load_yaml_guess_indent(
|
|
111
|
+
text, yaml=round_trip_yaml(YamlLayout())
|
|
112
|
+
)
|
|
113
|
+
layout = YamlLayout(
|
|
114
|
+
sequence_indent=2 if sequence_indent is None else int(sequence_indent),
|
|
115
|
+
sequence_dash_offset=0 if dash_offset is None else int(dash_offset),
|
|
116
|
+
explicit_start=has_explicit_start(text),
|
|
117
|
+
)
|
|
118
|
+
return document, layout
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def has_explicit_start(text: str) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Check whether a yaml document opens with an explicit "---" marker.
|
|
124
|
+
"""
|
|
125
|
+
for line in text.splitlines():
|
|
126
|
+
stripped = line.strip()
|
|
127
|
+
if not stripped or stripped.startswith("#"):
|
|
128
|
+
continue
|
|
129
|
+
return stripped == "---"
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def dump_round_trip(
|
|
134
|
+
content: t.Any, fileobj: t.IO[str], layout: YamlLayout = YamlLayout()
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Dump a document loaded with `load_round_trip`, preserving its formatting.
|
|
138
|
+
"""
|
|
139
|
+
round_trip_yaml(layout).dump(content, fileobj)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def empty_document() -> CommentedMap:
|
|
143
|
+
"""
|
|
144
|
+
An empty document suitable for `dump_round_trip`.
|
|
145
|
+
"""
|
|
146
|
+
return CommentedMap()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def str_format(content: t.Any) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Convert a value to str.
|
|
152
|
+
|
|
153
|
+
This is almost like json, but more convenient for printing to the standard output.
|
|
154
|
+
"""
|
|
155
|
+
if content is True:
|
|
156
|
+
return "true"
|
|
157
|
+
if content is False:
|
|
158
|
+
return "false"
|
|
159
|
+
if content is None:
|
|
160
|
+
return "null"
|
|
161
|
+
return str(content)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def parse(v: t.Union[str, t.IO[str]]) -> t.Any:
|
|
165
|
+
"""
|
|
166
|
+
Parse a yaml-formatted string.
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
return load(v)
|
|
170
|
+
except (ParserError, ScannerError):
|
|
171
|
+
pass
|
|
172
|
+
return v
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def parse_key_value(text: str) -> t.Optional[tuple[str, t.Any]]:
|
|
176
|
+
"""
|
|
177
|
+
Parse <KEY>=<YAML VALUE> command line arguments.
|
|
178
|
+
|
|
179
|
+
Return None if text could not be parsed.
|
|
180
|
+
"""
|
|
181
|
+
match = re.match(r"(?P<key>[a-zA-Z0-9_-]+)=(?P<value>(.|\n|\r)*)", text)
|
|
182
|
+
if not match:
|
|
183
|
+
return None
|
|
184
|
+
key = match.groupdict()["key"]
|
|
185
|
+
value = match.groupdict()["value"]
|
|
186
|
+
if not value:
|
|
187
|
+
# Empty strings are interpreted as null values, which is incorrect.
|
|
188
|
+
value = "''"
|
|
189
|
+
elif "\n" not in value and value.startswith("#"):
|
|
190
|
+
# Single-line string that starts with a pound # key
|
|
191
|
+
# We need to escape the string, otherwise pound will be interpreted as a comment.
|
|
192
|
+
value = f'"{value}"'
|
|
193
|
+
return key, parse(value)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
import typing as t
|
|
5
|
-
from _io import TextIOWrapper
|
|
6
|
-
|
|
7
|
-
import yaml
|
|
8
|
-
from yaml.parser import ParserError
|
|
9
|
-
from yaml.scanner import ScannerError
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def load(stream: t.Union[str, t.IO[str]]) -> t.Any:
|
|
13
|
-
return yaml.load(stream, Loader=yaml.SafeLoader)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def load_all(stream: str) -> t.Iterator[t.Any]:
|
|
17
|
-
return yaml.load_all(stream, Loader=yaml.SafeLoader)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def dump_all(documents: t.Sequence[t.Any], fileobj: TextIOWrapper) -> None:
|
|
21
|
-
yaml.safe_dump_all(
|
|
22
|
-
documents, stream=fileobj, default_flow_style=False, allow_unicode=True
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def dump(content: t.Any, fileobj: TextIOWrapper) -> None:
|
|
27
|
-
yaml.dump(content, stream=fileobj, default_flow_style=False, allow_unicode=True)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def dumps(content: t.Any) -> str:
|
|
31
|
-
result = yaml.dump(content, default_flow_style=False, allow_unicode=True)
|
|
32
|
-
assert isinstance(result, str)
|
|
33
|
-
return result
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def str_format(content: t.Any) -> str:
|
|
37
|
-
"""
|
|
38
|
-
Convert a value to str.
|
|
39
|
-
|
|
40
|
-
This is almost like json, but more convenient for printing to the standard output.
|
|
41
|
-
"""
|
|
42
|
-
if content is True:
|
|
43
|
-
return "true"
|
|
44
|
-
if content is False:
|
|
45
|
-
return "false"
|
|
46
|
-
if content is None:
|
|
47
|
-
return "null"
|
|
48
|
-
return str(content)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def parse(v: t.Union[str, t.IO[str]]) -> t.Any:
|
|
52
|
-
"""
|
|
53
|
-
Parse a yaml-formatted string.
|
|
54
|
-
"""
|
|
55
|
-
try:
|
|
56
|
-
return load(v)
|
|
57
|
-
except (ParserError, ScannerError):
|
|
58
|
-
pass
|
|
59
|
-
return v
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def parse_key_value(text: str) -> t.Optional[tuple[str, t.Any]]:
|
|
63
|
-
"""
|
|
64
|
-
Parse <KEY>=<YAML VALUE> command line arguments.
|
|
65
|
-
|
|
66
|
-
Return None if text could not be parsed.
|
|
67
|
-
"""
|
|
68
|
-
match = re.match(r"(?P<key>[a-zA-Z0-9_-]+)=(?P<value>(.|\n|\r)*)", text)
|
|
69
|
-
if not match:
|
|
70
|
-
return None
|
|
71
|
-
key = match.groupdict()["key"]
|
|
72
|
-
value = match.groupdict()["value"]
|
|
73
|
-
if not value:
|
|
74
|
-
# Empty strings are interpreted as null values, which is incorrect.
|
|
75
|
-
value = "''"
|
|
76
|
-
elif "\n" not in value and value.startswith("#"):
|
|
77
|
-
# Single-line string that starts with a pound # key
|
|
78
|
-
# We need to escape the string, otherwise pound will be interpreted as a comment.
|
|
79
|
-
value = f'"{value}"'
|
|
80
|
-
return key, parse(value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/cms.env.yml
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/lms.env.yml
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/config/partials/auth.yml
RENAMED
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/cms/test.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/apps/openedx/settings/lms/test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/bin/reload-granian
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/__init__.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/assets.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/cms/i18n.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/granian-config
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/__init__.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/assets.py
RENAMED
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/build/openedx/settings/lms/i18n.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tutor-23.0.0.dev3201 → tutor-23.0.0.dev3205}/tutor/templates/jobs/init/mounted-directories.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|