diff --git a/cms/envs/common.py b/cms/envs/common.py index 591247388a9d..bde0ef6d6de2 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -144,7 +144,7 @@ get_theme_base_dirs_from_settings ) from openedx.core.lib.license import LicenseMixin -from openedx.core.lib.derived import derived, derived_collection_entry +from openedx.core.lib.derived import Derived from openedx.core.release import doc_version # pylint: enable=useless-suppression @@ -740,7 +740,7 @@ # Don't look for template source files inside installed applications. 'APP_DIRS': False, # Instead, look for template source files in these dirs. - 'DIRS': _make_mako_template_dirs, + 'DIRS': Derived(_make_mako_template_dirs), # Options specific to this backend. 'OPTIONS': { 'loaders': ( @@ -759,7 +759,7 @@ 'NAME': 'mako', 'BACKEND': 'common.djangoapps.edxmako.backend.Mako', 'APP_DIRS': False, - 'DIRS': _make_mako_template_dirs, + 'DIRS': Derived(_make_mako_template_dirs), 'OPTIONS': { 'context_processors': CONTEXT_PROCESSORS, 'debug': False, @@ -778,8 +778,6 @@ } }, ] -derived_collection_entry('TEMPLATES', 0, 'DIRS') -derived_collection_entry('TEMPLATES', 1, 'DIRS') DEFAULT_TEMPLATE_ENGINE = TEMPLATES[0] #################################### AWS ####################################### @@ -825,8 +823,7 @@ # Warning: Must have trailing slash to activate correct logout view # (auth_backends, not LMS user_authn) FRONTEND_LOGOUT_URL = '/logout/' -FRONTEND_REGISTER_URL = lambda settings: settings.LMS_ROOT_URL + '/register' -derived('FRONTEND_REGISTER_URL') +FRONTEND_REGISTER_URL = Derived(lambda settings: settings.LMS_ROOT_URL + '/register') LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/" ENTERPRISE_API_URL = LMS_INTERNAL_ROOT_URL + '/enterprise/api/v1/' @@ -1315,8 +1312,7 @@ STATICI18N_FILENAME_FUNCTION = 'statici18n.utils.legacy_filename' STATICI18N_ROOT = PROJECT_ROOT / "static" -LOCALE_PATHS = _make_locale_paths -derived('LOCALE_PATHS') +LOCALE_PATHS = Derived(_make_locale_paths) # Messages MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' @@ -2086,10 +2082,9 @@ # See annotations in lms/envs/common.py for details. RETIRED_EMAIL_DOMAIN = 'retired.invalid' # See annotations in lms/envs/common.py for details. -RETIRED_USERNAME_FMT = lambda settings: settings.RETIRED_USERNAME_PREFIX + '{}' +RETIRED_USERNAME_FMT = Derived(lambda settings: settings.RETIRED_USERNAME_PREFIX + '{}') # See annotations in lms/envs/common.py for details. -RETIRED_EMAIL_FMT = lambda settings: settings.RETIRED_EMAIL_PREFIX + '{}@' + settings.RETIRED_EMAIL_DOMAIN -derived('RETIRED_USERNAME_FMT', 'RETIRED_EMAIL_FMT') +RETIRED_EMAIL_FMT = Derived(lambda settings: settings.RETIRED_EMAIL_PREFIX + '{}@' + settings.RETIRED_EMAIL_DOMAIN) # See annotations in lms/envs/common.py for details. RETIRED_USER_SALTS = ['abc', '123'] # See annotations in lms/envs/common.py for details. @@ -2366,13 +2361,12 @@ ############## Settings for Studio Context Sensitive Help ############## HELP_TOKENS_INI_FILE = REPO_ROOT / "cms" / "envs" / "help_tokens.ini" -HELP_TOKENS_LANGUAGE_CODE = lambda settings: settings.LANGUAGE_CODE -HELP_TOKENS_VERSION = lambda settings: doc_version() +HELP_TOKENS_LANGUAGE_CODE = Derived(lambda settings: settings.LANGUAGE_CODE) +HELP_TOKENS_VERSION = Derived(lambda settings: doc_version()) HELP_TOKENS_BOOKS = { 'learner': 'https://edx.readthedocs.io/projects/open-edx-learner-guide', 'course_author': 'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course', } -derived('HELP_TOKENS_LANGUAGE_CODE', 'HELP_TOKENS_VERSION') # Used with Email sending RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS = 5 @@ -2873,15 +2867,15 @@ def _should_send_learning_badge_events(settings): }, 'org.openedx.content_authoring.xblock.published.v1': { 'course-authoring-xblock-lifecycle': - {'event_key_field': 'xblock_info.usage_key', 'enabled': _should_send_xblock_events}, + {'event_key_field': 'xblock_info.usage_key', 'enabled': Derived(_should_send_xblock_events)}, }, 'org.openedx.content_authoring.xblock.deleted.v1': { 'course-authoring-xblock-lifecycle': - {'event_key_field': 'xblock_info.usage_key', 'enabled': _should_send_xblock_events}, + {'event_key_field': 'xblock_info.usage_key', 'enabled': Derived(_should_send_xblock_events)}, }, 'org.openedx.content_authoring.xblock.duplicated.v1': { 'course-authoring-xblock-lifecycle': - {'event_key_field': 'xblock_info.usage_key', 'enabled': _should_send_xblock_events}, + {'event_key_field': 'xblock_info.usage_key', 'enabled': Derived(_should_send_xblock_events)}, }, # LMS events. These have to be copied over here because lms.common adds some derived entries as well, # and the derivation fails if the keys are missing. If we ever remove the import of lms.common, we can remove these. @@ -2896,38 +2890,17 @@ def _should_send_learning_badge_events(settings): "org.openedx.learning.course.passing.status.updated.v1": { "learning-badges-lifecycle": { "event_key_field": "course_passing_status.course.course_key", - "enabled": _should_send_learning_badge_events, + "enabled": Derived(_should_send_learning_badge_events), }, }, "org.openedx.learning.ccx.course.passing.status.updated.v1": { "learning-badges-lifecycle": { "event_key_field": "course_passing_status.course.ccx_course_key", - "enabled": _should_send_learning_badge_events, + "enabled": Derived(_should_send_learning_badge_events), }, }, } - -derived_collection_entry('EVENT_BUS_PRODUCER_CONFIG', 'org.openedx.content_authoring.xblock.published.v1', - 'course-authoring-xblock-lifecycle', 'enabled') -derived_collection_entry('EVENT_BUS_PRODUCER_CONFIG', 'org.openedx.content_authoring.xblock.duplicated.v1', - 'course-authoring-xblock-lifecycle', 'enabled') -derived_collection_entry('EVENT_BUS_PRODUCER_CONFIG', 'org.openedx.content_authoring.xblock.deleted.v1', - 'course-authoring-xblock-lifecycle', 'enabled') - -derived_collection_entry( - "EVENT_BUS_PRODUCER_CONFIG", - "org.openedx.learning.course.passing.status.updated.v1", - "learning-badges-lifecycle", - "enabled", -) -derived_collection_entry( - "EVENT_BUS_PRODUCER_CONFIG", - "org.openedx.learning.ccx.course.passing.status.updated.v1", - "learning-badges-lifecycle", - "enabled", -) - ################### Authoring API ###################### # This affects the Authoring API swagger docs but not the legacy swagger docs under /api-docs/. diff --git a/lms/envs/common.py b/lms/envs/common.py index 23e0d12b3dde..ec2e76622220 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -69,7 +69,7 @@ get_themes_unchecked, get_theme_base_dirs_from_settings ) -from openedx.core.lib.derived import derived, derived_collection_entry +from openedx.core.lib.derived import Derived from openedx.core.release import doc_version from lms.djangoapps.lms_xblock.mixin import LmsBlockMixin @@ -1395,7 +1395,7 @@ def _make_mako_template_dirs(settings): # Don't look for template source files inside installed applications. 'APP_DIRS': False, # Instead, look for template source files in these dirs. - 'DIRS': _make_mako_template_dirs, + 'DIRS': Derived(_make_mako_template_dirs), # Options specific to this backend. 'OPTIONS': { 'context_processors': CONTEXT_PROCESSORS, @@ -1404,7 +1404,6 @@ def _make_mako_template_dirs(settings): } }, ] -derived_collection_entry('TEMPLATES', 1, 'DIRS') DEFAULT_TEMPLATE_ENGINE = TEMPLATES[0] DEFAULT_TEMPLATE_ENGINE_DIRS = DEFAULT_TEMPLATE_ENGINE['DIRS'][:] @@ -1734,7 +1733,7 @@ def _make_mako_template_dirs(settings): 'DOC_STORE_CONFIG': DOC_STORE_CONFIG, 'OPTIONS': { 'default_class': 'xmodule.hidden_block.HiddenBlock', - 'fs_root': lambda settings: settings.DATA_DIR, + 'fs_root': Derived(lambda settings: settings.DATA_DIR), 'render_template': 'common.djangoapps.edxmako.shortcuts.render_to_string', } }, @@ -1744,7 +1743,7 @@ def _make_mako_template_dirs(settings): 'DOC_STORE_CONFIG': DOC_STORE_CONFIG, 'OPTIONS': { 'default_class': 'xmodule.hidden_block.HiddenBlock', - 'fs_root': lambda settings: settings.DATA_DIR, + 'fs_root': Derived(lambda settings: settings.DATA_DIR), 'render_template': 'common.djangoapps.edxmako.shortcuts.render_to_string', } } @@ -2054,8 +2053,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring for locale_path in settings.COMPREHENSIVE_THEME_LOCALE_PATHS: locale_paths += (path(locale_path), ) return locale_paths -LOCALE_PATHS = _make_locale_paths -derived('LOCALE_PATHS') +LOCALE_PATHS = Derived(_make_locale_paths) # Messages MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' @@ -4651,13 +4649,12 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring ############## Settings for LMS Context Sensitive Help ############## HELP_TOKENS_INI_FILE = REPO_ROOT / "lms" / "envs" / "help_tokens.ini" -HELP_TOKENS_LANGUAGE_CODE = lambda settings: settings.LANGUAGE_CODE -HELP_TOKENS_VERSION = lambda settings: doc_version() +HELP_TOKENS_LANGUAGE_CODE = Derived(lambda settings: settings.LANGUAGE_CODE) +HELP_TOKENS_VERSION = Derived(lambda settings: doc_version()) HELP_TOKENS_BOOKS = { 'learner': 'https://edx.readthedocs.io/projects/open-edx-learner-guide', 'course_author': 'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course', } -derived('HELP_TOKENS_LANGUAGE_CODE', 'HELP_TOKENS_VERSION') ############## OPEN EDX ENTERPRISE SERVICE CONFIGURATION ###################### # The Open edX Enterprise service is currently hosted via the LMS container/process. @@ -4945,14 +4942,13 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. setting_description: Set the format a retired user username field gets transformed into, where {} # is replaced with the hash of the original username. This is a derived setting that depends on # RETIRED_USERNAME_PREFIX value. -RETIRED_USERNAME_FMT = lambda settings: settings.RETIRED_USERNAME_PREFIX + '{}' +RETIRED_USERNAME_FMT = Derived(lambda settings: settings.RETIRED_USERNAME_PREFIX + '{}'), # .. setting_name: RETIRED_EMAIL_FMT # .. setting_default: retired__user_{}@retired.invalid # .. setting_description: Set the format a retired user email field gets transformed into, where {} is # replaced with the hash of the original email. This is a derived setting that depends on # RETIRED_EMAIL_PREFIX and RETIRED_EMAIL_DOMAIN values. -RETIRED_EMAIL_FMT = lambda settings: settings.RETIRED_EMAIL_PREFIX + '{}@' + settings.RETIRED_EMAIL_DOMAIN -derived('RETIRED_USERNAME_FMT', 'RETIRED_EMAIL_FMT') +RETIRED_EMAIL_FMT = Derived(lambda settings: settings.RETIRED_EMAIL_PREFIX + '{}@' + settings.RETIRED_EMAIL_DOMAIN), # .. setting_name: RETIRED_USER_SALTS # .. setting_default: ['abc', '123'] # .. setting_description: Set a list of salts used for hashing usernames and emails on users retirement. @@ -5440,11 +5436,11 @@ def _should_send_learning_badge_events(settings): EVENT_BUS_PRODUCER_CONFIG = { 'org.openedx.learning.certificate.created.v1': { 'learning-certificate-lifecycle': - {'event_key_field': 'certificate.course.course_key', 'enabled': _should_send_certificate_events}, + {'event_key_field': 'certificate.course.course_key', 'enabled': Derived(_should_send_certificate_events)}, }, 'org.openedx.learning.certificate.revoked.v1': { 'learning-certificate-lifecycle': - {'event_key_field': 'certificate.course.course_key', 'enabled': _should_send_certificate_events}, + {'event_key_field': 'certificate.course.course_key', 'enabled': Derived(_should_send_certificate_events)}, }, 'org.openedx.learning.course.unenrollment.completed.v1': { 'course-unenrollment-lifecycle': @@ -5506,33 +5502,16 @@ def _should_send_learning_badge_events(settings): "org.openedx.learning.course.passing.status.updated.v1": { "learning-badges-lifecycle": { "event_key_field": "course_passing_status.course.course_key", - "enabled": _should_send_learning_badge_events, + "enabled": Derived(_should_send_learning_badge_events), }, }, "org.openedx.learning.ccx.course.passing.status.updated.v1": { "learning-badges-lifecycle": { "event_key_field": "course_passing_status.course.ccx_course_key", - "enabled": _should_send_learning_badge_events, + "enabled": Derived(_should_send_learning_badge_events), }, }, } -derived_collection_entry('EVENT_BUS_PRODUCER_CONFIG', 'org.openedx.learning.certificate.created.v1', - 'learning-certificate-lifecycle', 'enabled') -derived_collection_entry('EVENT_BUS_PRODUCER_CONFIG', 'org.openedx.learning.certificate.revoked.v1', - 'learning-certificate-lifecycle', 'enabled') - -derived_collection_entry( - "EVENT_BUS_PRODUCER_CONFIG", - "org.openedx.learning.course.passing.status.updated.v1", - "learning-badges-lifecycle", - "enabled", -) -derived_collection_entry( - "EVENT_BUS_PRODUCER_CONFIG", - "org.openedx.learning.ccx.course.passing.status.updated.v1", - "learning-badges-lifecycle", - "enabled", -) BEAMER_PRODUCT_ID = "" diff --git a/lms/envs/docs/README.rst b/lms/envs/docs/README.rst index 34211a57517d..e94b62f81b6c 100644 --- a/lms/envs/docs/README.rst +++ b/lms/envs/docs/README.rst @@ -37,6 +37,8 @@ platform, please see `Feature Flags and Settings`_. Derived Settings **************** +TODO UPDATE THIS + In cases where you need to define one or more settings relative to the value of another setting, you can explicitly designate them as derived calculations. This can let you override one setting (such as a path or a feature toggle) and diff --git a/lms/envs/production.py b/lms/envs/production.py index 3ea1c952e479..9e979dc15a18 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -28,7 +28,7 @@ from path import Path as path from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType -from openedx.core.lib.derived import derive_settings +from openedx.core.lib.derived import Derived from openedx.core.lib.logsettings import get_logger_config from xmodule.modulestore.modulestore_settings import convert_module_store_setting_if_needed # lint-amnesty, pylint: disable=wrong-import-order @@ -126,6 +126,73 @@ def get_env_setting(setting): "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}" ) +CC_MERCHANT_NAME = Derived(lambda settings: settings.PLATFORM_NAME) +EMAIL_FILE_PATH = Derived(lambda settings: settings.DATA_DIR / "emails" / "lms") +LMS_INTERNAL_ROOT_URL = Derived(lambda settings: settings.LMS_ROOT_URL) + +# This is the domain that is used to set shared cookies between various sub-domains. +# By default, it's set to the same thing as the SESSION_COOKIE_DOMAIN +SHARED_COOKIE_DOMAIN = Derived(lambda settings: settings.SESSION_COOKIE_DOMAIN) + +# We want Bulk Email running on the high-priority queue, so we define the +# routing key that points to it. At the moment, the name is the same. +# We have to reset the value here, since we have changed the value of the queue name. +BULK_EMAIL_ROUTING_KEY = Derived(lambda settings: settings.HIGH_PRIORITY_QUEUE) + +# We can run smaller jobs on the low priority queue. See note above for why +# we have to reset the value here. +BULK_EMAIL_ROUTING_KEY_SMALL_JOBS = Derived(lambda settings: settings.DEFAULT_PRIORITY_QUEUE) + +# Queue to use for expiring old entitlements +ENTITLEMENTS_EXPIRATION_ROUTING_KEY = Derived(lambda settings: settings.DEFAULT_PRIORITY_QUEUE) + +# Intentional defaults. +ID_VERIFICATION_SUPPORT_LINK = Derived(lambda settings: settings.SUPPORT_SITE_LINK) +PASSWORD_RESET_SUPPORT_LINK = Derived(lambda settings: settings.SUPPORT_SITE_LINK) +ACTIVATION_EMAIL_SUPPORT_LINK = Derived(lambda settings: settings.SUPPORT_SITE_LINK) +LOGIN_ISSUE_SUPPORT_LINK = Derived(lambda settings: settings.SUPPORT_SITE_LINK) + +# Default queues for various routes +GRADES_DOWNLOAD_ROUTING_KEY = Derived(lambda settings: settings.HIGH_MEM_QUEUE) +CREDENTIALS_GENERATION_ROUTING_KEY = Derived(lambda settings: settings.DEFAULT_PRIORITY_QUEUE) +PROGRAM_CERTIFICATES_ROUTING_KEY = Derived(lambda settings: settings.DEFAULT_PRIORITY_QUEUE) +SOFTWARE_SECURE_VERIFICATION_ROUTING_KEY = Derived(lambda settings: settings.HIGH_PRIORITY_QUEUE) + +############## OPEN EDX ENTERPRISE SERVICE CONFIGURATION ###################### +# The Open edX Enterprise service is currently hosted via the LMS container/process. +# However, for all intents and purposes this service is treated as a standalone IDA. +# These configuration settings are specific to the Enterprise service and you should +# not find references to them within the edx-platform project. + +# Publicly-accessible enrollment URL, for use on the client side. +ENTERPRISE_PUBLIC_ENROLLMENT_API_URL = Derived( + lambda settings: (settings.LMS_ROOT_URL or '') + settings.LMS_ENROLLMENT_API_PATH +) + +# Enrollment URL used on the server-side. +ENTERPRISE_ENROLLMENT_API_URL = Derived( + lambda settings: (settings.LMS_INTERNAL_ROOT_URL or '') + settings.LMS_ENROLLMENT_API_PATH +) + +############## ENTERPRISE SERVICE API CLIENT CONFIGURATION ###################### +# The LMS communicates with the Enterprise service via the requests.Session() client +# The below environmental settings are utilized by the LMS when interacting with +# the service, and override the default parameters which are defined in common.py + +def _generate_default_enterprise_api_url(settings): + default_enterprise_api_url = None + if settings.LMS_INTERNAL_ROOT_URL is not None: + default_enterprise_api_url = settings.LMS_INTERNAL_ROOT_URL + '/enterprise/api/v1/' + return default_enterprise_api_url +ENTERPRISE_API_URL = Derived(_generate_default_enterprise_api_url) + +def _generate_default_enterprise_consent_api_url(settings): + default_enterprise_consent_api_url = None + if settings.LMS_INTERNAL_ROOT_URL is not None: + default_enterprise_consent_api_url = settings.LMS_INTERNAL_ROOT_URL + '/consent/api/v1/' +ENTERPRISE_CONSENT_API_URL = Derived(_generate_default_enterprise_consent_api_url) + + ####################################################################################################################### # A file path to a YAML file from which to load all the configuration for the edx platform @@ -199,15 +266,11 @@ def get_env_setting(setting): STATIC_URL += "/" DATA_DIR = path(DATA_DIR) -CC_MERCHANT_NAME = _YAML_TOKENS.get('CC_MERCHANT_NAME', PLATFORM_NAME) -EMAIL_FILE_PATH = _YAML_TOKENS.get('EMAIL_FILE_PATH', DATA_DIR / "emails" / "lms") # TODO: This was for backwards compatibility back when installed django-cookie-samesite (not since 2022). # The DCS_ version of the setting can be DEPR'd at this point. SESSION_COOKIE_SAMESITE = DCS_SESSION_COOKIE_SAMESITE -LMS_INTERNAL_ROOT_URL = _YAML_TOKENS.get('LMS_INTERNAL_ROOT_URL', LMS_ROOT_URL) - for feature, value in _YAML_TOKENS.get('FEATURES', {}).items(): FEATURES[feature] = value @@ -217,10 +280,6 @@ def get_env_setting(setting): FEATURES['PREVIEW_LMS_BASE'], ] -# This is the domain that is used to set shared cookies between various sub-domains. -# By default, it's set to the same thing as the SESSION_COOKIE_DOMAIN, but we want to make it overrideable. -SHARED_COOKIE_DOMAIN = _YAML_TOKENS.get('SHARED_kCOOKIE_DOMAIN', SESSION_COOKIE_DOMAIN) - # Cache used for location mapping -- called many times with the same key/value # in a given request. if 'loc_cache' not in CACHES: @@ -237,17 +296,6 @@ def get_env_setting(setting): # Once we have migrated to service assets off S3, then we can convert this back to # managed by the yaml file contents -# We want Bulk Email running on the high-priority queue, so we define the -# routing key that points to it. At the moment, the name is the same. -# We have to reset the value here, since we have changed the value of the queue name. -BULK_EMAIL_ROUTING_KEY = _YAML_TOKENS.get('BULK_EMAIL_ROUTING_KEY', HIGH_PRIORITY_QUEUE) - -# We can run smaller jobs on the low priority queue. See note above for why -# we have to reset the value here. -BULK_EMAIL_ROUTING_KEY_SMALL_JOBS = _YAML_TOKENS.get('BULK_EMAIL_ROUTING_KEY_SMALL_JOBS', DEFAULT_PRIORITY_QUEUE) - -# Queue to use for expiring old entitlements -ENTITLEMENTS_EXPIRATION_ROUTING_KEY = _YAML_TOKENS.get('ENTITLEMENTS_EXPIRATION_ROUTING_KEY', DEFAULT_PRIORITY_QUEUE) # Build a CELERY_QUEUES dict the way that celery expects, based on a couple lists of queue names from the YAML. _YAML_CELERY_QUEUES = _YAML_TOKENS.get('CELERY_QUEUES', None) @@ -271,12 +319,6 @@ def get_env_setting(setting): MKTG_URL_LINK_MAP.update(_YAML_TOKENS.get('MKTG_URL_LINK_MAP', {})) -# Intentional defaults. -ID_VERIFICATION_SUPPORT_LINK = _YAML_TOKENS.get('ID_VERIFICATION_SUPPORT_LINK', SUPPORT_SITE_LINK) -PASSWORD_RESET_SUPPORT_LINK = _YAML_TOKENS.get('PASSWORD_RESET_SUPPORT_LINK', SUPPORT_SITE_LINK) -ACTIVATION_EMAIL_SUPPORT_LINK = _YAML_TOKENS.get('ACTIVATION_EMAIL_SUPPORT_LINK', SUPPORT_SITE_LINK) -LOGIN_ISSUE_SUPPORT_LINK = _YAML_TOKENS.get('LOGIN_ISSUE_SUPPORT_LINK', SUPPORT_SITE_LINK) - # Timezone overrides TIME_ZONE = CELERY_TIMEZONE @@ -350,17 +392,6 @@ def get_env_setting(setting): # use the one from common.py MODULESTORE = convert_module_store_setting_if_needed(_YAML_TOKENS.get('MODULESTORE', MODULESTORE)) -# After conversion above, the modulestore will have a "stores" list with all defined stores, for all stores, add the -# fs_root entry to derived collection so that if it's a callable it can be resolved. We need to do this because the -# `derived_collection_entry` takes an exact index value but the config file might have overridden the number of stores -# and so we can't be sure that the 2 we define in common.py will be there when we try to derive settings. This could -# lead to exceptions being thrown when the `derive_settings` call later in this file tries to update settings. We call -# the derived_collection_entry function here to ensure that we update the fs_root for any callables that remain after -# we've updated the MODULESTORE setting from our config file. -for idx, store in enumerate(MODULESTORE['default']['OPTIONS']['stores']): - if 'OPTIONS' in store and 'fs_root' in store["OPTIONS"]: - derived_collection_entry('MODULESTORE', 'default', 'OPTIONS', 'stores', idx, 'OPTIONS', 'fs_root') - BROKER_URL = "{}://{}:{}@{}/{}".format(CELERY_BROKER_TRANSPORT, CELERY_BROKER_USER, CELERY_BROKER_PASSWORD, @@ -384,9 +415,6 @@ def get_env_setting(setting): EVENT_TRACKING_SEGMENTIO_EMIT_WHITELIST ) -# Grades download -GRADES_DOWNLOAD_ROUTING_KEY = _YAML_TOKENS.get('GRADES_DOWNLOAD_ROUTING_KEY', HIGH_MEM_QUEUE) - if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): AUTHENTICATION_BACKENDS = _YAML_TOKENS.get('THIRD_PARTY_AUTH_BACKENDS', [ 'social_core.backends.google.GoogleOAuth2', @@ -490,49 +518,6 @@ def get_env_setting(setting): #### JWT configuration #### JWT_AUTH.update(_YAML_TOKENS.get('JWT_AUTH', {})) -################################ Settings for Credentials Service ################################ - -CREDENTIALS_GENERATION_ROUTING_KEY = _YAML_TOKENS.get('CREDENTIALS_GENERATION_ROUTING_KEY', DEFAULT_PRIORITY_QUEUE) - -PROGRAM_CERTIFICATES_ROUTING_KEY = _YAML_TOKENS.get('PROGRAM_CERTIFICATES_ROUTING_KEY', DEFAULT_PRIORITY_QUEUE) - -SOFTWARE_SECURE_VERIFICATION_ROUTING_KEY = _YAML_TOKENS.get( - 'SOFTWARE_SECURE_VERIFICATION_ROUTING_KEY', - HIGH_PRIORITY_QUEUE -) - -############## OPEN EDX ENTERPRISE SERVICE CONFIGURATION ###################### -# The Open edX Enterprise service is currently hosted via the LMS container/process. -# However, for all intents and purposes this service is treated as a standalone IDA. -# These configuration settings are specific to the Enterprise service and you should -# not find references to them within the edx-platform project. - -# Publicly-accessible enrollment URL, for use on the client side. -ENTERPRISE_PUBLIC_ENROLLMENT_API_URL = _YAML_TOKENS.get( - 'ENTERPRISE_PUBLIC_ENROLLMENT_API_URL', - (LMS_ROOT_URL or '') + LMS_ENROLLMENT_API_PATH -) - -# Enrollment URL used on the server-side. -ENTERPRISE_ENROLLMENT_API_URL = _YAML_TOKENS.get( - 'ENTERPRISE_ENROLLMENT_API_URL', - (LMS_INTERNAL_ROOT_URL or '') + LMS_ENROLLMENT_API_PATH -) - -############## ENTERPRISE SERVICE API CLIENT CONFIGURATION ###################### -# The LMS communicates with the Enterprise service via the requests.Session() client -# The below environmental settings are utilized by the LMS when interacting with -# the service, and override the default parameters which are defined in common.py - -DEFAULT_ENTERPRISE_API_URL = None -if LMS_INTERNAL_ROOT_URL is not None: - DEFAULT_ENTERPRISE_API_URL = LMS_INTERNAL_ROOT_URL + '/enterprise/api/v1/' -ENTERPRISE_API_URL = _YAML_TOKENS.get('ENTERPRISE_API_URL', DEFAULT_ENTERPRISE_API_URL) - -DEFAULT_ENTERPRISE_CONSENT_API_URL = None -if LMS_INTERNAL_ROOT_URL is not None: - DEFAULT_ENTERPRISE_CONSENT_API_URL = LMS_INTERNAL_ROOT_URL + '/consent/api/v1/' -ENTERPRISE_CONSENT_API_URL = _YAML_TOKENS.get('ENTERPRISE_CONSENT_API_URL', DEFAULT_ENTERPRISE_CONSENT_API_URL) ############## ENTERPRISE SERVICE LMS CONFIGURATION ################################## # The LMS has some features embedded that are related to the Enterprise service, but diff --git a/openedx/core/lib/derived.py b/openedx/core/lib/derived.py index a62731ef5432..b153091b069a 100644 --- a/openedx/core/lib/derived.py +++ b/openedx/core/lib/derived.py @@ -3,72 +3,83 @@ via callable methods/lambdas. The derivation time can be controlled to happen after all other settings have been set. The derived setting can also be overridden by setting the derived setting to an actual value. -""" -import sys +Example -# Global list holding all settings which will be derived. -__DERIVED = [] +In `lms/envs/common.py`: +``` +# Double some other value that might get set later. +VALUE = lambda settings: settings.SOME_OTHER_VALUE * 2 +# Register this value as one that needs to be derived later. +derived(VALUE) +``` -def derived(*settings): - """ - Registers settings which are derived from other settings. - Can be called multiple times to add more derived settings. +Later in a settings file that depends on common.py - Args: - settings (str): Setting names to register. - """ - __DERIVED.extend(settings) +``` +from lms.envs.common * +# Set some other value however you want. +SOME_OTHER_VALUE = 4 -def derived_collection_entry(collection_name, *accessors): - """ - Registers a setting which is a dictionary or list and needs a derived value for a particular entry. - Can be called multiple times to add more derived settings. +# Derive any settings and pass them this settings file for reference. +# This will update VALUE so that it is the scaler `8` instead of a lambda. +derive_settings(__name__) +``` +""" +from __future__ import annotations - Args: - collection_name (str): Name of setting which contains a dictionary or list. - accessors (int|str): Sequence of dictionary keys and list indices in the collection (and - collections within it) leading to the value which will be derived. - For example: 0, 'DIRS'. +import sys +import typing as t +from collections.abc import Sequence + + +class Derived: + """ + TODO doc + TODO typing """ - __DERIVED.append((collection_name, accessors)) + def __init__(self, calculate_value: t.Callable): + self.calculate_value = calculate_value -def derive_settings(module_name): +def derive_settings(module_name: str): """ Derives all registered settings and sets them onto a particular module. - Skips deriving settings that are set to a value. Args: module_name (str): Name of module to which the derived settings will be added. """ module = sys.modules[module_name] - for derived in __DERIVED: # lint-amnesty, pylint: disable=redefined-outer-name - if isinstance(derived, str): - setting = getattr(module, derived) - if callable(setting): - setting_val = setting(module) - setattr(module, derived, setting_val) - elif isinstance(derived, tuple): - # If a tuple, two elements are expected - else ignore. - if len(derived) == 2: - # The first element is the name of the attribute which is expected to be a dictionary or list. - # The second element is a list of string keys in that dictionary leading to a derived setting. - collection = getattr(module, derived[0]) - accessors = derived[1] - for accessor in accessors[:-1]: - collection = collection[accessor] - setting = collection[accessors[-1]] - if callable(setting): - setting_val = setting(module) - collection[accessors[-1]] = setting_val - - -def clear_for_tests(): + _derive_dict_items(module, vars(module)) + + +def _derive_dict_items(settings, the_dict: dict): + """ + TODO doc + """ + for key, child in the_dict.items(): + if isinstance(child, Derived): + the_dict[key] = child.calculate_value(settings) + elif isinstance(child, Sequence) and not isinstance(child, str): + the_dict[key] = _derive_sequence_items(settings, child) + _derive_sequence_items(settings, child) + elif isinstance(child, dict): + _derive_dict_items(settings, child) + + +def _derive_sequence_items(settings, the_seq: Sequence): """ - Clears all settings to be derived. For tests only. + TODO doc """ - global __DERIVED - __DERIVED = [] + result = [] + for ix, child in enumerate(the_seq): + if isinstance(child, Derived): + result.append(child.calculate_value(settings)) + elif isinstance(child, Sequence) and not isinstance(child, str): + result.append(_derive_sequence_items(settings, child)) + elif isinstance(child, dict): + _derive_dict_items(settings, child) + result.append(child) + return type(the_seq)(result) diff --git a/openedx/core/lib/tests/test_derived.py b/openedx/core/lib/tests/test_derived.py index ef3f98042432..7d3f70fa6ab1 100644 --- a/openedx/core/lib/tests/test_derived.py +++ b/openedx/core/lib/tests/test_derived.py @@ -5,7 +5,7 @@ import sys from unittest import TestCase -from openedx.core.lib.derived import derived, derived_collection_entry, derive_settings, clear_for_tests +from openedx.core.lib.derived import Derived, derive_settings class TestDerivedSettings(TestCase): @@ -14,18 +14,14 @@ class TestDerivedSettings(TestCase): """ def setUp(self): super().setUp() - clear_for_tests() self.module = sys.modules[__name__] self.module.SIMPLE_VALUE = 'paneer' - self.module.DERIVED_VALUE = lambda settings: 'mutter ' + settings.SIMPLE_VALUE - self.module.ANOTHER_DERIVED_VALUE = lambda settings: settings.DERIVED_VALUE + ' with naan' + self.module.DERIVED_VALUE = Derived(lambda settings: 'mutter ' + settings.SIMPLE_VALUE) + self.module.ANOTHER_DERIVED_VALUE = Derived(lambda settings: settings.DERIVED_VALUE + ' with naan') self.module.UNREGISTERED_DERIVED_VALUE = lambda settings: settings.SIMPLE_VALUE + ' is cheese' - derived('DERIVED_VALUE', 'ANOTHER_DERIVED_VALUE') self.module.DICT_VALUE = {} - self.module.DICT_VALUE['test_key'] = lambda settings: settings.DERIVED_VALUE * 3 - derived_collection_entry('DICT_VALUE', 'test_key') - self.module.DICT_VALUE['list_key'] = ['not derived', lambda settings: settings.DERIVED_VALUE] - derived_collection_entry('DICT_VALUE', 'list_key', 1) + self.module.DICT_VALUE['test_key'] = Derived(lambda settings: settings.DERIVED_VALUE * 3) + self.module.DICT_VALUE['list_key'] = ['not derived', Derived(lambda settings: settings.DERIVED_VALUE)] def test_derived_settings_are_derived(self): derive_settings(__name__) diff --git a/xmodule/modulestore/modulestore_settings.py b/xmodule/modulestore/modulestore_settings.py index 05470cf8f1ef..6222f2055a88 100644 --- a/xmodule/modulestore/modulestore_settings.py +++ b/xmodule/modulestore/modulestore_settings.py @@ -6,6 +6,8 @@ import copy import warnings +from openedx.core.lib.derived import Derived + def convert_module_store_setting_if_needed(module_store_setting): """