2

I sometime would like to apply small fixes to small issues, just for testing, or for internal purposes.

For example I would like to just replace self.builder.current_docname with node.source in sphinx.writers.html5.HTML5Translator.visit_literal_block.

The (elegant) way of doing it is to copy-paste the method where the patch should be applied then modify the little tiny thing I have to modify, then override the original method with the new one. In this case I would have a lot of imports and I have to copy/paste the whole method locally (bad SSOT is bad).

Instead I would would like to write something like:

from monkey import ReMonkeyPatcher # Fake...
from sphinx.writer.html5 import HTML5Translator

# Override the class's method
ReMonkeyPatcher(HTML5Translator, 'self.builder.current_docname', 'node.source')

# Profit...

I've read here that the reflective nature of Python allows for run-time hacks with inspect and ast. So I wrote this:

def monkey_replace(function, search, replace):
    source = inspect.getsource(function)
    while code[0].isspace():
        code = textwrap.dedent(code)

    code = code.replace(search, replace)
    ast_tree = ast.parse(code)
    compile(ast_tree, '<string>', mode='exec')

    mod = {}
    exec(code, mod) # <-- Here is the issue...
    method = mod[function.__name__]
    replace_function(function, method) # Doesn't know how to do that yet

The main issue is exec(code, mod) in which the context is missing. To work in any/most cases, it has to be executed in the context of the original function (imports...).

Is there an elegant way of doing it?

nowox
  • 25,978
  • 39
  • 143
  • 293
  • A year late to the party but https://github.com/adamchainz/patchy may be what you're looking for! – btown Dec 19 '21 at 22:05

1 Answers1

-1

In Python monkey-patching is extremely easy. As it says in the post you reference: Because there are no protected variables, you can simply overwrite the function you would like to patch:

HTMLTranslator.builder.current_docname = node.source
tomanizer
  • 851
  • 6
  • 16