instance_admin.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import filecmp
  2. import json
  3. import os
  4. import shutil
  5. import sys
  6. from django.utils.crypto import get_random_string
  7. from django.core.management import execute_from_command_line as django_command_line
  8. import iepy
  9. from iepy import defaults
  10. from iepy.utils import DIRS, unzip_from_url
  11. THIS_FOLDER = os.path.dirname(os.path.abspath(__file__))
  12. class InstanceManager:
  13. # class instead of a fuction just for allowing better code organization
  14. files_to_copy = [
  15. "csv_to_iepy.py",
  16. "preprocess.py",
  17. "iepy_runner.py",
  18. "iepy_rules_runner.py",
  19. "rules_verifier.py",
  20. "manage.py",
  21. "gazettes_loader.py",
  22. ]
  23. steps = [
  24. 'create_folders',
  25. 'create_init_file',
  26. 'copy_bin',
  27. 'create_rules_file',
  28. 'create_extractor_config_file',
  29. # Put this following step at the end of all the steps that doesnt need settings
  30. 'configure_settings_file',
  31. 'migrate_db',
  32. 'create_db_user',
  33. 'greetings',
  34. ]
  35. def __init__(self, folder_path, lang='en'):
  36. self.folder_path = folder_path
  37. self.abs_folder_path = os.path.abspath(self.folder_path)
  38. self.creating = True
  39. self.lang = lang
  40. def _run_steps(self):
  41. for step_name in self.steps:
  42. step = getattr(self, step_name)
  43. step()
  44. def create(self):
  45. if os.path.exists(self.folder_path):
  46. print("Error: folder already exists")
  47. sys.exit(1)
  48. self._run_steps()
  49. def upgrade(self):
  50. if not os.path.exists(self.folder_path):
  51. print("Error: instance folder does not exist")
  52. sys.exit(1)
  53. try:
  54. actual_path = iepy.setup(self.folder_path, _safe_mode=True)
  55. except ValueError as err:
  56. print(err)
  57. sys.exit(1)
  58. finally:
  59. self.folder_path = actual_path
  60. self.abs_folder_path = os.path.abspath(self.folder_path)
  61. from django.conf import settings
  62. self.old_version = settings.IEPY_VERSION
  63. if settings.IEPY_VERSION == iepy.__version__:
  64. print("Iepy instance '{}' is already up to date.".format(self.folder_path))
  65. return
  66. print("Upgrading iepy instance '{}' from {} to {}".format(
  67. self.folder_path, self.old_version, iepy.__version__))
  68. self.creating = False
  69. self.old_version_path = self.download_old_iepy_version()
  70. self._run_steps()
  71. def download_old_iepy_version(self):
  72. oldv = self.old_version
  73. url = "https://pypi.python.org/packages/source/i/iepy/iepy-{}.tar.gz".format(oldv)
  74. old_versions_path = os.path.join(DIRS.user_data_dir, 'old_versions')
  75. os.makedirs(old_versions_path, exist_ok=True)
  76. asked_tag_path = os.path.join(old_versions_path, 'iepy-{}'.format(oldv))
  77. if not os.path.exists(asked_tag_path):
  78. print ('Downloading old iepy version {} for allowing patches'.format(oldv))
  79. unzip_from_url(url, old_versions_path)
  80. print('Done')
  81. return asked_tag_path
  82. def create_folders(self):
  83. self.bin_folder = os.path.join(self.folder_path, "bin")
  84. os.makedirs(self.bin_folder, exist_ok=not self.creating)
  85. def create_init_file(self):
  86. rules_filepath = os.path.join(self.folder_path, "__init__.py")
  87. with open(rules_filepath, "w") as filehandler:
  88. filehandler.write("from . import rules")
  89. def copy_bin(self):
  90. # Create folders
  91. for filename in self.files_to_copy:
  92. destination = os.path.join(self.bin_folder, filename)
  93. self._copy_file(filename, destination)
  94. def create_rules_file(self):
  95. rules_filepath = os.path.join(self.folder_path, "rules.py")
  96. if self.creating:
  97. with open(rules_filepath, "w") as filehandler:
  98. filehandler.write("# Write here your rules\n")
  99. filehandler.write("# RELATION = 'your relation here'\n")
  100. def create_extractor_config_file(self):
  101. # Create extractor config
  102. config_filepath = os.path.join(self.folder_path, "extractor_config.json")
  103. def do_it():
  104. with open(config_filepath, "w") as filehandler:
  105. json.dump(defaults.extractor_config, filehandler, indent=4)
  106. if self.creating:
  107. do_it()
  108. else:
  109. if json.load(open(config_filepath)) != defaults.extractor_config:
  110. msg = 'Should we override your extraction config settings?'
  111. msg += ' (existing version will be backed up first)'
  112. if self.prompt(msg):
  113. self.preserve_old_file_version_as_copy(config_filepath)
  114. do_it()
  115. print ('Extraction configs upgraded.')
  116. else:
  117. print ('Extraction configs left untouched.')
  118. def _copy_file(self, filename, destination):
  119. filepath = os.path.join(THIS_FOLDER, filename)
  120. def do_it():
  121. shutil.copyfile(filepath, destination)
  122. if self.creating:
  123. do_it()
  124. else:
  125. # first check if the file is already present
  126. if not os.path.exists(destination):
  127. do_it()
  128. else:
  129. # file exists. Let's check if what the instance has is the
  130. # vainilla old version or not
  131. old_version_filepath = os.path.join(
  132. self.old_version_path, 'iepy', 'instantiation', filename)
  133. if os.path.exists(old_version_filepath) and filecmp.cmp(old_version_filepath, destination):
  134. # vainilla old version. Let's simply upgrade it
  135. do_it()
  136. else:
  137. # customized file.
  138. # Check if it's exactly what the new version provides
  139. if filecmp.cmp(filepath, destination):
  140. # you have local changes that are 100% new version. Sounds like
  141. # you made your instance with a nightly built.. dunno, but we'll
  142. # do nothing, since is not needed.
  143. return
  144. # We'll back it up, and later upgrade
  145. self.preserve_old_file_version_as_copy(destination)
  146. do_it()
  147. def prompt(self, msg):
  148. answer = input("%s (y/n) " % msg).lower().strip()
  149. while answer not in ['y', 'n']:
  150. print ('Invalid answer "{}".'.format(answer))
  151. answer = input("%s (y/n) " % msg).lower().strip()
  152. return answer == 'y'
  153. def preserve_old_file_version_as_copy(self, fpath):
  154. i = 1
  155. while True:
  156. back_up_path = fpath + '.backup_{}'.format(i)
  157. if not os.path.exists(back_up_path):
  158. break
  159. i += 1
  160. shutil.copyfile(fpath, back_up_path)
  161. print("Backed up your instance version of {} at {}. "
  162. "Remove it if you don't need it".format(fpath, back_up_path))
  163. def configure_settings_file(self):
  164. # Create the settings file
  165. folder_name = os.path.basename(self.folder_path) # aka iepy instance name
  166. settings_filepath = os.path.join(self.folder_path, "settings.py")
  167. def do_it():
  168. print("Initializing database")
  169. print("By default, we will create an SQLite database although "
  170. "it has a very poor performance, specialy with large amounts "
  171. "of text.\nYou might want to change this in the future.")
  172. database_name = input("Database name [{}]: ".format(folder_name))
  173. if not database_name:
  174. database_name = folder_name
  175. database_path = os.path.join(self.abs_folder_path, database_name)
  176. settings_data = get_settings_string(database_path, self.lang)
  177. with open(settings_filepath, "w") as filehandler:
  178. filehandler.write(settings_data)
  179. if self.creating:
  180. do_it()
  181. else:
  182. if self.old_version in ["0.9", "0.9.0", "0.9.1"]:
  183. old_settings_filepath = os.path.join(
  184. self.folder_path, "{}_settings.py".format(folder_name)
  185. )
  186. shutil.move(old_settings_filepath, settings_filepath)
  187. with open(settings_filepath, 'a') as fhandler:
  188. msg = 'Remove line declaring the old IEPY_VERSION above.'
  189. fhandler.write(
  190. "IEPY_VERSION = '{}' # {}\n".format(iepy.__version__, msg))
  191. print ('Patched IEPY_VERSION at {}.'.format(settings_filepath))
  192. def migrate_db(self):
  193. # Setup IEPY with the new instance
  194. os.chdir(self.abs_folder_path)
  195. iepy.setup(self.abs_folder_path)
  196. django_command_line(["", "migrate"])
  197. def create_db_user(self):
  198. # Setup the database user
  199. if self.creating:
  200. print("\nCreating database user")
  201. django_command_line(["", "createsuperuser"])
  202. def greetings(self):
  203. print("\n IEPY instance ready to use at '{}'".format(self.abs_folder_path))
  204. def get_settings_string(database_path, lang):
  205. template_settings_filepath = os.path.join(THIS_FOLDER, "settings.py.template")
  206. with open(template_settings_filepath) as filehandler:
  207. settings_data = filehandler.read()
  208. if not database_path.endswith(".sqlite"):
  209. database_path += ".sqlite"
  210. chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
  211. secret_key = get_random_string(50, chars)
  212. settings_data = settings_data.replace("{SECRET_KEY}", secret_key)
  213. settings_data = settings_data.replace("{DATABASE_PATH}", database_path)
  214. settings_data = settings_data.replace("{IEPY_VERSION}", iepy.__version__)
  215. settings_data = settings_data.replace("{LANG}", lang)
  216. return settings_data