Skip to content

Commit 025b234

Browse files
committed
[IMP] snippets: preserve same file module
In `make_pickleable_callback`, when creating a name for the module created via import_script, no longer make the name random, but derive it from the script's path deterministically via a hash function. This way, when this method is called multiple times from the same script for multiple callbacks, it will only import the script once and otherwise re-use the created module. This ensures multiple callbacks from the same script file will run within the same module, seeing e.g. the same values in modified globals, which is much more intuitive. Also, move the function to `misc.py`. With the intention to reuse it, it's a better fit. For that, make it python2 compatible. closes #295 Related: odoo/upgrade#8047 Signed-off-by: Christophe Simonis (chs) <[email protected]>
1 parent eab584e commit 025b234

File tree

2 files changed

+30
-28
lines changed

2 files changed

+30
-28
lines changed

src/util/misc.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
import collections
55
import datetime
66
import functools
7+
import hashlib
8+
import inspect
79
import logging
810
import os
911
import re
12+
import sys
1013
import textwrap
1114
import uuid
1215
from contextlib import contextmanager
@@ -21,7 +24,7 @@
2124
from openerp.modules.module import get_module_path
2225
from openerp.tools.parse_version import parse_version
2326

24-
from .exceptions import SleepyDeveloperError
27+
from .exceptions import MigrationError, SleepyDeveloperError
2528

2629
# python3 shim
2730
try:
@@ -391,6 +394,31 @@ def log(chunk_num, size=chunk_size):
391394
log(i // chunk_size + 1, i % chunk_size)
392395

393396

397+
def make_pickleable_callback(callback):
398+
"""
399+
Make a callable importable.
400+
401+
`ProcessPoolExecutor.map` arguments needs to be pickleable
402+
Functions can only be pickled if they are importable.
403+
However, the callback's file is not importable due to the dash in the filename.
404+
We should then put the executed function in its own importable file.
405+
406+
:meta private: exclude from online docs
407+
"""
408+
callback_filepath = inspect.getfile(callback)
409+
name = "_upgrade_" + hashlib.sha256(callback_filepath.encode()).hexdigest()
410+
if name not in sys.modules:
411+
sys.modules[name] = import_script(callback_filepath, name=name)
412+
try:
413+
return getattr(sys.modules[name], callback.__name__)
414+
except AttributeError:
415+
error_msg = (
416+
"The converter callback `{}` is a nested function in `{}`.\n"
417+
"Move it outside the `migrate()` function to make it top-level."
418+
).format(callback.__name__, callback.__module__)
419+
raise MigrationError(error_msg)
420+
421+
394422
class SelfPrint(object):
395423
"""
396424
Class that will return a self representing string. Used to evaluate domains.

src/util/snippets.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
# -*- coding: utf-8 -*-
2-
import inspect
32
import logging
43
import re
5-
import sys
6-
import uuid
74
from concurrent.futures import ProcessPoolExecutor
85

96
from lxml import etree, html
@@ -12,9 +9,8 @@
129
from psycopg2.extras import Json
1310

1411
from .const import NEARLYWARN
15-
from .exceptions import MigrationError
1612
from .helpers import table_of_model
17-
from .misc import import_script, log_progress
13+
from .misc import log_progress, make_pickleable_callback
1814
from .pg import column_exists, column_type, get_max_workers, table_exists
1915

2016
_logger = logging.getLogger(__name__)
@@ -161,28 +157,6 @@ def html_converter(transform_callback, selector=None):
161157
return HTMLConverter(make_pickleable_callback(transform_callback), selector)
162158

163159

164-
def make_pickleable_callback(callback):
165-
"""
166-
Make a callable importable.
167-
168-
`ProcessPoolExecutor.map` arguments needs to be pickleable
169-
Functions can only be pickled if they are importable.
170-
However, the callback's file is not importable due to the dash in the filename.
171-
We should then put the executed function in its own importable file.
172-
"""
173-
callback_filepath = inspect.getfile(callback)
174-
name = f"_upgrade_{uuid.uuid4().hex}"
175-
mod = sys.modules[name] = import_script(callback_filepath, name=name)
176-
try:
177-
return getattr(mod, callback.__name__)
178-
except AttributeError:
179-
error_msg = (
180-
f"The converter callback `{callback.__name__}` is a nested function in `{callback.__module__}`.\n"
181-
"Move it outside the `migrate()` function to make it top-level."
182-
)
183-
raise MigrationError(error_msg) from None
184-
185-
186160
class BaseConverter:
187161
def __init__(self, callback, selector=None):
188162
self.callback = callback

0 commit comments

Comments
 (0)