-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement a new initialization pipeline step and runtime patchi… #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| """ | ||
| This module defines utility functions for dynamic runtime patching. | ||
|
|
||
| Functions: | ||
|
|
||
| form_field_getattr_patch: | ||
| Defines a dynamic attribute resolver that returns a callable when | ||
| the accessed attribute matches a specific naming pattern. | ||
| """ | ||
| import re | ||
|
|
||
| from crum import get_current_request | ||
|
|
||
| from eox_nelp.edxapp_wrapper.site_configuration import configuration_helpers | ||
| from eox_nelp.edxapp_wrapper.user_authn import form_fields | ||
|
|
||
|
|
||
| def form_field_getattr_patch(attribute): | ||
| """ | ||
| Resolves dynamic attribute access based on a naming pattern. | ||
|
|
||
| If the given attribute name matches the expected pattern (e.g. `add_<field_name>_field`) | ||
| and the corresponding field is listed in the site configuration, a callable is returned | ||
| that can generate the field definition at runtime. | ||
|
|
||
| Args: | ||
| attribute (str): Attribute name being accessed. | ||
|
|
||
| Returns: | ||
| function: Callable that generates a form field definition. | ||
|
|
||
| Raises: | ||
| AttributeError: If the attribute name does not correspond to a configured field. | ||
| """ | ||
|
|
||
| def _generate_handler(field_name): | ||
| """ | ||
| Creates a callable for generating a form field definition. | ||
|
|
||
| This helper retrieves field metadata such as label translations from | ||
| site configuration and prepares a function that can be invoked to | ||
| construct the appropriate form field. | ||
|
|
||
| Args: | ||
| field_name (str): Name of the field to generate. | ||
|
|
||
| Returns: | ||
| function: Callable that, when executed, creates the field definition. | ||
| """ | ||
| request = get_current_request() | ||
|
|
||
| extended_profile_fields_translations = configuration_helpers.get_value( | ||
| "extended_profile_fields_translations", | ||
| {}, | ||
| ) | ||
| translations = extended_profile_fields_translations.get(request.LANGUAGE_CODE, {}) | ||
| label = translations.get(field_name, field_name).capitalize() | ||
|
|
||
| def handler(is_field_required=True): | ||
| """ | ||
| Creates a form field using the given parameters. | ||
|
|
||
| Args: | ||
| is_field_required (bool): Whether the field is mandatory. Defaults to True. | ||
|
|
||
| Returns: | ||
| Any: Field instance created by the underlying form field utility. | ||
| """ | ||
| # pylint: disable=protected-access | ||
| return form_fields._add_field_with_configurable_select_options(field_name, label, is_field_required) | ||
|
|
||
| return handler | ||
|
|
||
| pattern = r"^add_(?P<field_name>.+?)_field$" | ||
|
|
||
| field_name = re.match(pattern, attribute).group('field_name') | ||
| extended_profile_fields = configuration_helpers.get_value("extended_profile_fields", []) | ||
|
|
||
| if field_name in extended_profile_fields: | ||
| return _generate_handler(field_name) | ||
|
|
||
| raise AttributeError(f"Invalid attribute: '{attribute}'.") |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| """This file contains all the tests for the dynamic patch utilities. | ||
|
|
||
| Classes: | ||
| FormFieldGetAttrPatchTestCase: Tests for dynamic attribute resolution and field handler generation. | ||
| """ | ||
| from django.test import TestCase | ||
| from mock import Mock, patch | ||
|
|
||
| from eox_nelp.edxapp_wrapper.site_configuration import configuration_helpers | ||
| from eox_nelp.user_authn.api import patches | ||
|
|
||
|
|
||
| class FormFieldGetAttrPatchTestCase(TestCase): | ||
| """Tests for form_field_getattr_patch function.""" | ||
|
|
||
| def setUp(self): | ||
| """ | ||
| Set up mocks for configuration and request context. | ||
| """ | ||
| self.mock_request = Mock() | ||
| self.mock_request.LANGUAGE_CODE = "es" | ||
|
|
||
| def tearDown(self): | ||
| """Reset mocks.""" | ||
| configuration_helpers.reset_mock() | ||
| self.mock_request.reset_mock() | ||
|
|
||
| @patch("eox_nelp.user_authn.api.patches.get_current_request") | ||
| @patch("eox_nelp.user_authn.api.patches.form_fields._add_field_with_configurable_select_options") | ||
| def test_dynamic_handler_generation(self, mock_add_field, mock_get_request): | ||
| """Test that form_field_getattr_patch generates a handler for configured fields. | ||
|
|
||
| Expected behaviour: | ||
| - When attribute name matches `add_<field>_field`, and field is configured, | ||
| a callable is returned. | ||
| """ | ||
| field_name = "hobby" | ||
| attribute = f"add_{field_name}_field" | ||
| mock_get_request.return_value = self.mock_request | ||
| configuration_helpers.get_value.side_effect = lambda key, default=None: ( | ||
| ["hobby", "sport", "movie"] | ||
| if key == "extended_profile_fields" | ||
| else {} | ||
| ) | ||
|
|
||
| handler = patches.form_field_getattr_patch(attribute) | ||
| handler() # invoke handler | ||
|
|
||
| self.assertTrue(callable(handler)) | ||
| mock_add_field.assert_called_once_with(field_name, field_name.capitalize(), True) | ||
|
|
||
| @patch("eox_nelp.user_authn.api.patches.get_current_request") | ||
| @patch("eox_nelp.user_authn.api.patches.form_fields._add_field_with_configurable_select_options") | ||
| def test_dynamic_handler_with_translations(self, mock_add_field, mock_get_request): | ||
| """Test that form_field_getattr_patch applies translated labels when available. | ||
|
|
||
| Expected behaviour: | ||
| - Translation for the field is fetched from `extended_profile_fields_translations`. | ||
| - The label passed to `_add_field_with_configurable_select_options` should use | ||
| the localized version instead of the field name. | ||
| """ | ||
| field_name = "sport" | ||
| translated_label = "Deporte" | ||
| attribute = f"add_{field_name}_field" | ||
| mock_get_request.return_value = self.mock_request | ||
| configuration_helpers.get_value.side_effect = lambda key, default=None: { | ||
| "extended_profile_fields": ["sport"], | ||
| "extended_profile_fields_translations": { | ||
| "es": {field_name: translated_label}, | ||
| }, | ||
| }.get(key, default) | ||
|
|
||
| handler = patches.form_field_getattr_patch(attribute) | ||
| handler(is_field_required=False) | ||
|
|
||
| self.assertTrue(callable(handler)) | ||
| mock_add_field.assert_called_once_with(field_name, translated_label, False) | ||
|
|
||
| def test_attribute_error_for_unconfigured_field(self): | ||
| """Test that AttributeError is raised for unconfigured fields. | ||
|
|
||
| Expected behaviour: | ||
| - When the field is not included in the site configuration, calling | ||
| form_field_getattr_patch raises AttributeError. | ||
| """ | ||
| configuration_helpers.get_value.side_effect = lambda key, default=None: [] | ||
| attribute = "add_unknown_field" | ||
|
|
||
| with self.assertRaises(AttributeError): | ||
| patches.form_field_getattr_patch(attribute) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice update of docstrings =)