gooderp18绿色标准版
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

232 lines
7.6KB

  1. #!/usr/bin/env python3
  2. """
  3. Rewrite the entire source code using the scripts found at
  4. /odoo/upgrade_code
  5. Each script is named {version}-{name}.py and exposes an upgrade function
  6. that takes a single argument, the file_manager, and returns nothing.
  7. The file_manager acts as a list of files, files have 3 attributes:
  8. * path: the pathlib.Path where the file is on the file system;
  9. * addon: the odoo addon in which the file is;
  10. * content: the re-writtable content of the file (lazy).
  11. There are additional utilities on the file_manager, such as:
  12. * print_progress(current, total)
  13. Example:
  14. def upgrade(file_manager):
  15. files = [f for f in file_manager if f.path.suffix == '.py']
  16. for fileno, file in enumerate(files, start=1):
  17. file.content = file.content.replace(..., ...)
  18. file_manager.print_progress(fileno, len(files))
  19. The command line offers a way to select and run those scripts.
  20. Please note that all the scripts are doing a best-effort a migrating the
  21. source code, they only help do the heavy-lifting, they are not silver
  22. bullets.
  23. """
  24. import argparse
  25. import sys
  26. from importlib.machinery import SourceFileLoader
  27. from pathlib import Path
  28. from types import ModuleType
  29. from typing import Iterator
  30. ROOT = Path(__file__).parent.parent
  31. try:
  32. import odoo.addons
  33. from . import Command
  34. from odoo import release
  35. from odoo.modules import initialize_sys_path
  36. from odoo.tools import config, parse_version
  37. except ImportError:
  38. # Assume the script is directy executed (by opposition to be
  39. # executed via odoo-bin), happily release/parse_version are
  40. # standalone so we can hack our way there without importing odoo
  41. sys.path.insert(0, str(ROOT))
  42. sys.path.insert(0, str(ROOT / 'tools'))
  43. import release
  44. from parse_version import parse_version
  45. class Command:
  46. pass
  47. config = {'addons_path': ''}
  48. initialize_sys_path = None
  49. UPGRADE = ROOT / 'upgrade_code'
  50. AVAILABLE_EXT = ('.py', '.js', '.css', '.scss', '.xml', '.csv')
  51. class FileAccessor:
  52. addon: Path
  53. path: Path
  54. content: str
  55. def __init__(self, path: Path, addon_path: Path) -> None:
  56. self.path = path
  57. self.addon = addon_path / path.relative_to(addon_path).parts[0]
  58. self._content = None
  59. self.dirty = False
  60. @property
  61. def content(self):
  62. if self._content is None:
  63. self._content = self.path.read_text()
  64. return self._content
  65. @content.setter
  66. def content(self, value):
  67. if self._content != value:
  68. self._content = value
  69. self.dirty = True
  70. class FileManager:
  71. addons_path: list[str]
  72. glob: str
  73. def __init__(self, addons_path: list[str], glob: str = '**/*') -> None:
  74. self.addons_path = addons_path
  75. self.glob = glob
  76. self._files = {
  77. str(path): FileAccessor(path, Path(addon_path))
  78. for addon_path in addons_path
  79. for path in Path(addon_path).glob(glob)
  80. if '__pycache__' not in path.parts
  81. if path.suffix in AVAILABLE_EXT
  82. if path.is_file()
  83. }
  84. def __iter__(self) -> Iterator[FileAccessor]:
  85. return iter(self._files.values())
  86. def __len__(self):
  87. return len(self._files)
  88. def get_file(self, path):
  89. return self._files.get(str(path))
  90. if sys.stdout.isatty():
  91. def print_progress(self, current, total=None):
  92. total = total or len(self) or 1
  93. print(f'{current / total:>4.0%}', end='\r', file=sys.stderr) # noqa: T201
  94. else:
  95. def print_progress(self, current, total=None):
  96. pass
  97. def get_upgrade_code_scripts(from_version: tuple[int, ...], to_version: tuple[int, ...]) -> list[tuple[str, ModuleType]]:
  98. modules: list[tuple[str, ModuleType]] = []
  99. for script_path in sorted(UPGRADE.glob('*.py')):
  100. version = parse_version(script_path.name.partition('-')[0])
  101. if from_version <= version <= to_version:
  102. module = SourceFileLoader(script_path.name, str(script_path)).load_module()
  103. modules.append((script_path.name, module))
  104. return modules
  105. def migrate(
  106. addons_path: list[str],
  107. glob: str,
  108. from_version: tuple[int, ...] | None = None,
  109. to_version: tuple[int, ...] | None = None,
  110. script: str | None = None,
  111. dry_run: bool = False,
  112. ):
  113. if script:
  114. script_path = next(UPGRADE.glob(f'*{script.removesuffix(".py")}*.py'), None)
  115. if not script_path:
  116. raise FileNotFoundError(script)
  117. script_path.relative_to(UPGRADE) # safeguard, prevent going up
  118. module = SourceFileLoader(script_path.name, str(script_path)).load_module()
  119. modules = [(script_path.name, module)]
  120. else:
  121. modules = get_upgrade_code_scripts(from_version, to_version)
  122. file_manager = FileManager(addons_path, glob)
  123. for (name, module) in modules:
  124. file_manager.print_progress(0) # 0%
  125. module.upgrade(file_manager)
  126. file_manager.print_progress(len(file_manager)) # 100%
  127. for file in file_manager:
  128. if file.dirty:
  129. print(file.path) # noqa: T201
  130. if not dry_run:
  131. with file.path.open("w") as f:
  132. f.write(file.content)
  133. return any(file.dirty for file in file_manager)
  134. class UpgradeCode(Command):
  135. """ Rewrite the entire source code using the scripts found at /odoo/upgrade_code """
  136. name = 'upgrade_code'
  137. prog_name = Path(sys.argv[0]).name
  138. def __init__(self):
  139. self.parser = argparse.ArgumentParser(
  140. prog=(
  141. f"{self.prog_name} [--addons-path=PATH,...] {self.name}"
  142. if initialize_sys_path else
  143. self.prog_name
  144. ),
  145. description=__doc__.replace('/odoo/upgrade_code', str(UPGRADE)),
  146. formatter_class=argparse.RawDescriptionHelpFormatter,
  147. )
  148. group = self.parser.add_mutually_exclusive_group(required=True)
  149. group.add_argument(
  150. '--script',
  151. metavar='NAME',
  152. help="run this single script")
  153. group.add_argument(
  154. '--from',
  155. dest='from_version',
  156. type=parse_version,
  157. metavar='VERSION',
  158. help="run all scripts starting from this version, inclusive")
  159. self.parser.add_argument(
  160. '--to',
  161. dest='to_version',
  162. type=parse_version,
  163. default=parse_version(release.version),
  164. metavar='VERSION',
  165. help=f"run all scripts until this version, inclusive (default: {release.version})")
  166. self.parser.add_argument(
  167. '--glob',
  168. default='**/*',
  169. help="select the files to rewrite (default: %(default)s)")
  170. self.parser.add_argument(
  171. '--dry-run',
  172. action='store_true',
  173. help="list the files that would be re-written, but rewrite none")
  174. self.parser.add_argument(
  175. '--addons-path',
  176. default=config['addons_path'],
  177. metavar='PATH,...',
  178. help="specify additional addons paths (separated by commas)",
  179. )
  180. def run(self, cmdargs):
  181. options = self.parser.parse_args(cmdargs)
  182. if initialize_sys_path:
  183. config['addons_path'] = options.addons_path
  184. initialize_sys_path()
  185. options.addons_path = odoo.addons.__path__
  186. else:
  187. options.addons_path = [p for p in options.addons_path.split(',') if p]
  188. if not options.addons_path:
  189. self.parser.error("--addons-path is required when used standalone")
  190. is_dirty = migrate(**vars(options))
  191. sys.exit(int(is_dirty))
  192. if __name__ == '__main__':
  193. UpgradeCode().run(sys.argv[1:])
上海开阖软件有限公司 沪ICP备12045867号-1