-
Notifications
You must be signed in to change notification settings - Fork 23
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
[BB-933] Add pluggable_override
#64
[BB-933] Add pluggable_override
#64
Conversation
Thanks for the pull request, @Agrendalath! I've created OSPR-5033 to keep track of it in JIRA, where we prioritize reviews. Please note that it may take us up to several weeks or months to complete a review and merge your PR. Feel free to add as much of the following information to the ticket:
All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here. Please let us know once your PR is ready for our review and all tests are green. |
3464f3f
to
f5ce0b4
Compare
Hi @Agrendalath! While I understand the point of this PR, it makes me worried about its consequences. I'm afraid it will add an extra easy-to-add but hard-to-maintain extension point everywhere If I understand correctly, using the
(Ironically, I recently wrote this code for an xblock feature) The main difference with this piece of code is that the Of course, the alternative would be to use monkey-patching, which comes with its own set of issues. To summarize, I'm not opposed to this, but I'd like to make sure we are aware of the consequences: adding In any case, if we do decide to move forward with this, the |
I have some of the same concerns, but don't really know of a better alternative for allowing operators to customize more obscure parts of the platform. (The use cases we at OpenCraft have had for this PR so far are things like customizing XBlock unit icons seen in the LMS, and requiring login to view certificates - things that we're fairly sure are idosyncratic requirements.) We have a few choices: refuse such customizations entirely, fork the platform and maintain code drift (we avoid this), merge these idiosyncratic features into the codebase (makes the code more complex for all, benefits few), or use this approach so that at least there is an API to support such customizations without requiring operators to fork or modify the platform. To me, this seems like the best option, giving people options for customization via a straighforward API, and keeping the custom code out of the core platform. If you have any suggestions on better ways to make the platform more customizable and flexible like this, please share. One last note: as you can see in #21433 where we add this to the
Yes, I introduced
Is it really a strange design pattern? It's conceptually pretty similar to a regular python method decorator, just used without the This design allows chaining of multiple overrides, allows the custom function to defer to the original implementation / modify the arguments passed to the original implementation / modify the return value of the original implementation, while avoiding monkey patching. I think this approach is much better than monkey patching because one can clearly see the |
Me neither, so I agree this is probably the best possible solution. However, I'll insist again on the documentation of pluggable overrides:
I suggest you create a code annotation to document those entrypoints. This is the mechanism that we use to document feature toggles and settings in edx-platform: https://code-annotations.readthedocs.io/en/latest/getting_started.html The annotation format would probably look like this:
Those annotations would then be collected by an ad-hoc Sphinx extension: https://code-annotations.readthedocs.io/en/latest/sphinx_extensions.html Finally, these entrypoints would find their way to the edx-platform docs, similarly to the setting docs: https://github.com/edx/edx-platform/blob/master/docs/technical/settings.rst If this sounds like a good approach to you, please let me know if I can help you getting familiar with code annotations.
I didn't know this was a design pattern. The way I see it, a pluggable override is different from a python decorator in the sense that it requires the overridden function to be passed as argument. With pluggable overrides, I can imagine many cases where the overridden function will be an unused argument. For instance, the I'm not hell-bent on this issue though -- much less than on the documentation. If other developers agree with your approach then I'm fine with it. |
@Agrendalath I am late to the party, but thank you for your contribution. Please let me know once this is ready for our review, unless @regisb wants to review as a core committer. |
@natabene, since @nasthagiri and @nedbat were already looking into it on edx/edx-platform#21433 (from which this was extracted), would it be reasonable to get a second look from them? |
@nedbat @nasthagiri Would you like to review this? |
@nedbat had offered to do so. |
@nedbat, did you have a moment to take a look at this? |
@nedbat Do you think you could find some time to review this in the next 2 weeks? |
@regisb: FYI: I am not going to do any doc updates at this time, but I did publish to readthedocs if anyone wants to clean up the docs: https://edx.readthedocs.io/projects/edx-django-utils/en/latest/. Note: we don't have a published standard for how to organize the docs, but edx-toggles index takes a stab at this that includes renaming some of the items in the left-hand nav to make it more clear. |
def decorator(f): | ||
@functools.wraps(f) | ||
def wrapper(*args, **kwargs): | ||
prev_fn = functools.partial(f) # The base function in `edx-platform`. |
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.
I don't understand why you use functools.partial
here? Now prev_fn
will behave exactly like f
, so what is gained?
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.
That's odd - I cannot find any references to why we have added this, so I've just removed it and tested that it works correctly in a few different scenarios.
def wrapper(*args, **kwargs): | ||
prev_fn = functools.partial(f) # The base function in `edx-platform`. | ||
|
||
override_functions = getattr(settings, override, None) |
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.
This code could be simplified with:
override_functions = getattr(settings, override, ())
and then letting the for impl in override_functions:
loop do nothing.
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.
Good idea, changed.
Sorry it has taken me long to get to this. @regisb does the latest annotation work have something for us to use to document these new "entrypoints"? |
@Agrendalath this will need to be rebased to move from Travis to GitHub Actions. |
@Agrendalath thanks for the detailed docstring. We should make sure it ends up in the published docs. |
The following steps need to be performed to document these entrypoints:
|
This adds a new extension point - a `pluggable_override` decorator that allows overriding any function or method by pointing to its alternative implementation in settings.
f5ce0b4
to
ec41b65
Compare
@nedbat, thank you for reviewing this. We have addressed your comments. Regarding the automated generation of the documentation, would it be reasonable to do this once the status of this feature is changed from "Trial" to "Adopted", and we have more overrides available? Currently, we have only one such override, proposed in edx/edx-platform#21433. |
Thanks for making the changes, and sorry for my slow pace. I definitely want to get this into the annotation toolchain, but we can merge this and keep making progress. |
@Agrendalath 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future. |
Description:
This adds a new extension point - a
pluggable_override
decorator that allows overriding any function or method by pointing to its alternative implementation in settings.This ticket is a part of edx/edx-platform#21433 and should be merged before that one.
JIRA:
OSPR-3801
Dependencies:
edx/edx-platform#21433
Merge deadline:
Before edx/edx-platform#21433.
Installation instructions:
Listed in edx/edx-platform#21433.
Testing instructions:
Listed in edx/edx-platform#21433.
Reviewers:
Merge checklist:
Post merge:
finished.