# Coverage for api/cmd_helper.py : 0%

## 298 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1"""

2 Helper utility functions for commandline api

3"""

4 import importlib

5 import json

6 import os

7 import re

8 import sys

9 from subprocess import run

10

11 import click

12 from flask import current_app

13

14 from shopyo.api.cli_content import get_dashboard_html_content

15 from shopyo.api.cli_content import get_global_py_content

16 from shopyo.api.cli_content import get_module_view_content

17 from shopyo.api.constants import SEP_CHAR

18 from shopyo.api.constants import SEP_NUM

19 from shopyo.api.file import get_folders

20 from shopyo.api.file import last_part_of_path

21 from shopyo.api.file import path_exists

22 from shopyo.api.file import trycopytree

23 from shopyo.api.file import trymkdir

24 from shopyo.api.file import trymkfile

25 from shopyo.api.file import tryrmcache

26 from shopyo.api.file import tryrmfile

27 from shopyo.api.file import tryrmtree

28

29

30 def _clean(verbose=False, clear_migration=True, clear_db=True):

31 """

32 Deletes shopyo.db and migrations/ if present in current working directory.

33 Deletes all __pycache__ folders starting from current working directory

34 all the way to leaf directory.

35

36 Parameters

37 ----------

38 - verbose: flag to indicate whether to print to result of clean to

39 stdout or not.

40 - clear_migration: flag to indicate if migration folder is to be deleted or not

41 - clear_db: flag indicating if db is to be cleared or not

42 - db: db to be cleaned

43

44 Returns

45 -------

46 None

47 ...

48

49 """

50 click.echo("Cleaning...")

51 click.echo(SEP_CHAR * SEP_NUM)

52 db = current_app.extensions["sqlalchemy"].db

53

54 if clear_db:

55 db.drop_all()

56 db.engine.execute("DROP TABLE IF EXISTS alembic_version;")

57

58 tryrmfile(os.path.join(os.getcwd(), "shopyo.db"), verbose=verbose)

59 if verbose:

60 click.echo("[x] all tables dropped")

61 elif clear_db is False:

62 if verbose:

63 click.echo("[ ] db clearing skipped")

64

65 tryrmcache(os.getcwd(), verbose=verbose)

66

67 if clear_migration:

68 tryrmtree(os.path.join(os.getcwd(), "migrations"), verbose=verbose)

69 elif clear_migration is False:

70 if verbose:

71 click.echo("[ ] migration folder delete skipped")

72

73

74 def _collectstatic(target_module="modules", verbose=False):

75

76 """

77 Copies ``module/static`` into ``/static/modules/module``.

78 In static it becomes like

79

80 ::

81

82 static/

83 modules/

84 box_something/

85 modulename

86 modulename2

87

88

89 Parameters

90 ----------

91 target_module: str

92 name of module, in alphanumeric-underscore,

93 supports ``module`` or ``box__name/module``

94

95 Returns

96 -------

97 None

98

99 """

100 click.echo("Collecting static...")

101 click.echo(SEP_CHAR * SEP_NUM)

102

103 root_path = os.getcwd()

104 static_path = os.path.join(root_path, "static")

105

106 # if target_module path does not start with 'modules\' add it to as a

107 # prefix to the target_module path

108 if target_module != "modules":

109

110 # normalize the target_module path to be same as that of OS

111 target_module = re.split(r"[/|\\]+", target_module)

112 target_module_start = target_module[0]

113 target_module = os.path.join(*target_module)

114

115 # add the modules folder to start of target_module incase it is not

116 # already present in the path

117 if target_module_start != "modules":

118 target_module = os.path.join("modules", target_module)

119

120 # get the full path for modules (the src). Defaults to ./modules

121 modules_path = os.path.join(root_path, target_module)

122

123 # get the full path of static folder to copy to (the dest).

124 # always ./static/modules

125 modules_path_in_static = os.path.join(static_path, "modules")

126

127 # terminate if modules_path (i.e. src to copy static from) does not exist

128 if not os.path.exists(modules_path):

129 click.echo(f"[ ] path: {modules_path} does not exist")

130 sys.exit(1)

131

132 # clear ./static/modules before coping to it

133 tryrmtree(modules_path_in_static, verbose=verbose)

134

135 # look for static folders in all project

136 for folder in get_folders(modules_path):

137 if folder.startswith("box__"):

138 box_path = os.path.join(modules_path, folder)

139 for subfolder in get_folders(box_path):

140 module_name = subfolder

141 module_static_folder = os.path.join(box_path, subfolder, "static")

142 if not os.path.exists(module_static_folder):

143 continue

144 module_in_static_dir = os.path.join(

145 modules_path_in_static, folder, module_name

146 )

147

148 # copy from ./modules/<box__name>/<submodule> to

149 # ./static/modules

150 trycopytree(module_static_folder, module_in_static_dir, verbose=verbose)

151 else:

152 path_split = ""

153

154 # split the target module if default target_module path name is

155 # not used

156 if target_module != "modules":

157 path_split = re.split(r"[/|\\]", target_module, maxsplit=1)

158 path_split = path_split[1]

159

160 if folder.lower() == "static":

161 module_static_folder = os.path.join(modules_path, folder)

162 module_name = path_split

163 else:

164 module_static_folder = os.path.join(modules_path, folder, "static")

165 module_name = os.path.join(path_split, folder)

166

167 if not os.path.exists(module_static_folder):

168 continue

169 module_in_static_dir = os.path.join(modules_path_in_static, module_name)

170 tryrmtree(module_in_static_dir, verbose=verbose)

171 trycopytree(module_static_folder, module_in_static_dir, verbose=verbose)

172

173 click.echo("")

174

175

176 def _upload_data(verbose=False):

177 click.echo("Uploading initial data to db...")

178 click.echo(SEP_CHAR * SEP_NUM)

179

180 root_path = os.getcwd()

181

182 for folder in os.listdir(os.path.join(root_path, "modules")):

183 if folder.startswith("__"): # ignore __pycache__

184 continue

185 if folder.startswith("box__"):

186 # boxes

187 for sub_folder in os.listdir(os.path.join(root_path, "modules", folder)):

188 if sub_folder.startswith("__"): # ignore __pycache__

189 continue

190 elif sub_folder.endswith(".json"): # box_info.json

191 continue

192

193 try:

194 upload = importlib.import_module(

195 f"modules.{folder}.{sub_folder}.upload"

196 )

197 upload.upload(verbose=verbose)

198 except ImportError as e:

199 if verbose:

200 click.echo(f"[ ] {e}")

201 else:

202 # apps

203 try:

204 upload = importlib.import_module(f"modules.{folder}.upload")

205 upload.upload(verbose=verbose)

206 except ImportError as e:

207 if verbose:

208 click.echo(f"[ ] {e}")

209

210 click.echo("")

211

212

213 def _create_box(boxname, verbose=False):

214

215 base_path = os.path.join("modules", boxname)

216 trymkdir(base_path, verbose=verbose)

217

218 info_json = {

219 "display_string": boxname.capitalize(),

220 "box_name": boxname,

221 "author": {"name": "", "website": "", "mail": ""},

222 }

223

224 box_info = os.path.join(base_path, "box_info.json")

225

226 with open(box_info, "w", encoding="utf-8") as f:

227 json.dump(info_json, f, indent=4, sort_keys=True)

228

229 if verbose:

230 click.echo("'box_info.json' content:")

231 click.echo(json.dumps(info_json, indent=4, sort_keys=True))

232

233 click.echo(f"{boxname} created!")

234

235

236 def _create_module(modulename, base_path=None, verbose=False):

237 """creates module with the structure defined in the modules section in docs

238 Assume valid modulename i.e modulename does not start with ``box__`` and

239 modulename consist only of alphanumeric characters or underscore

240

241 Parameters

242 ----------

243 modulename: str

244 name of module, in alphanumeric-underscore

245

246 Returns

247 -------

248 None

249

250 """

251

252 click.echo(f"creating module: {modulename}")

253 click.echo(SEP_CHAR * SEP_NUM)

254

255 if base_path is None:

256 base_path = os.path.join("modules", modulename)

257

258 # create the module with directories templates, tests, static

259 trymkdir(base_path, verbose=verbose)

260 trymkdir(os.path.join(base_path, "templates"), verbose=verbose)

261 trymkdir(os.path.join(base_path, "templates", modulename), verbose=verbose)

262 trymkdir(os.path.join(base_path, "tests"), verbose=verbose)

263 trymkdir(os.path.join(base_path, "static"), verbose=verbose)

264

265 # create functional test and unit test files for the module

266 test_func_path = os.path.join(

267 base_path, "tests", f"test_{modulename}_functional.py"

268 )

269 test_models_path = os.path.join(base_path, "tests", f"test_{modulename}_models.py")

270 test_func_content = "# Please add your functional tests to this file.\n"

271 test_model_content = "# Please add your models tests to this file.\n"

272 trymkfile(test_func_path, test_func_content, verbose=verbose)

273 trymkfile(test_models_path, test_model_content, verbose=verbose)

274

275 # create view.py, forms.py and model.py files inside the module

276 trymkfile(

277 os.path.join(base_path, "view.py"), get_module_view_content(), verbose=verbose

278 )

279 trymkfile(os.path.join(base_path, "forms.py"), "", verbose=verbose)

280 trymkfile(os.path.join(base_path, "models.py"), "", verbose=verbose)

281

282 # create info.json file inside the module

283 info_json = {

284 "display_string": modulename.capitalize(),

285 "module_name": modulename,

286 "type": "show",

287 "fa-icon": "fa fa-store",

288 "url_prefix": f"/{modulename}",

289 "author": {"name": "", "website": "", "mail": ""},

290 }

291 info_json_path = os.path.join(base_path, "info.json")

292 with open(info_json_path, "w", encoding="utf-8") as f:

293 json.dump(info_json, f, indent=4, sort_keys=True)

294

295 if verbose:

296 click.echo(f"[x] file created at '{info_json_path}' with content: ")

297 click.echo(json.dumps(info_json, indent=4, sort_keys=True))

298

299 # create the sidebar.html inside templates/blocks

300 blocks_path = os.path.join(base_path, "templates", modulename, "blocks")

301 trymkdir(blocks_path, verbose=verbose)

302 trymkfile(os.path.join(blocks_path, "sidebar.html"), "", verbose=verbose)

303

304 # create the dashboard.html inside templates/MODULENAME

305 trymkfile(

306 os.path.join(base_path, "templates", modulename, "dashboard.html"),

307 get_dashboard_html_content(),

308 verbose=verbose,

309 )

310

311 # create the global.py files inside the module

312 trymkfile(

313 os.path.join(base_path, "global.py"), get_global_py_content(), verbose=verbose

314 )

315

316

317 def _run_app(mode):

318 """helper command for running shopyo flask app in debug/production mode"""

319 app_path = os.path.join(os.getcwd(), "app.py")

320

321 if not os.path.exists(app_path):

322 click.secho(f"Unable to find `app.py` in {os.getcwd()}", fg="red")

323 sys.exit(1)

324

325 os.environ["FLASK_APP"] = f"app:create_app('{mode}')"

326 os.environ["FLASK_ENV"] = mode

327 run(["flask", "run"])

328

329

330 def _check_modules_path(root_path):

331 modules_path = os.path.join(root_path, "modules")

332 if path_exists(modules_path):

333 pass

334 else:

335 click.echo("Modules folder not found!")

336 sys.exit()

337

338

339 def _verify_app(app_path, box_name=None):

340 app_folder = last_part_of_path(app_path)

341

342 audit_info = {"path": app_path, "issues": []}

343

344 # verify info.json

345 if not path_exists(os.path.join(app_path, "info.json")):

346 audit_info["issues"].append("severe: info.json not found")

347 else:

348 with open(os.path.join(app_path, "info.json")) as f:

349 json_data = json.load(f)

350

351 to_check_keys = ["module_name", "url_prefix"]

352 not_found = []

353 for key in to_check_keys:

354 if key not in json_data:

355 not_found.append(key)

356 msg = "severe: key {key} not found in info.json".format(key)

357 audit_info["issues"].append(msg)

358

359 if ("module_name" not in not_found) and ("url_prefix" not in not_found):

360

361 if (json_data["module_name"].strip() == "") or (

362 json_data["url_prefix"].strip() == ""

363 ):

364 msg = (

365 "sus: module_name and url_prefix in info.json must not be empty"

366 " ideally"

367 )

368 audit_info["issues"].append(msg)

369

370 if "module_name" not in not_found:

371 if {json_data["module_name"], app_folder} != {app_folder}:

372 msg = """severe: currently module_name "{}" in info.json and app folder "{}" must have the same value""".format(

373 json_data["module_name"], app_folder

374 )

375 audit_info["issues"].append(msg)

376

377 # verify components

378

379 if not path_exists(os.path.join(app_path, "templates")):

380 audit_info["issues"].append("warning: templates folder not found")

381

382 if not path_exists(os.path.join(app_path, "view.py")):

383 audit_info["issues"].append("severe: view.py not found")

384

385 if not path_exists(os.path.join(app_path, "forms.py")):

386 audit_info["issues"].append("info: forms.py not found")

387

388 if not path_exists(os.path.join(app_path, "models.py")):

389 audit_info["issues"].append("info: models.py not found")

390

391 if not path_exists(os.path.join(app_path, "global.py")):

392 audit_info["issues"].append("info: global.py not found")

393

394 return audit_info

395

396

397 def _verify_box(box_path):

398 audit_info = {"issues": []}

399

400 # verify box_info.json

401 if not path_exists(os.path.join(box_path, "box_info.json")):

402 audit_info["issues"].append("warning: box_info.json not found")

403 else:

404 with open(os.path.join(box_path, "box_info.json")) as f:

405 json_data = json.load(f)

406

407 to_check_keys = ["box_name"]

408

409 for key in to_check_keys:

410 if key not in json_data:

411 msg = f"severe: key {key} not found in box_info.json"

412 audit_info["issues"].append(msg)

413

414 return audit_info

415

416

417 def _check_apps(root_path):

418 issues_found = []

419

420 modules_path = os.path.join(root_path, "modules")

421 apps = get_folders(modules_path)

422 apps = [a for a in apps if not a.startswith("__") and not a.startswith("box__")]

423

424 for app in apps:

425 app_path = os.path.join(modules_path, app)

426 app_issues = _verify_app(app_path)

427 issues_found.append(app_issues)

428

429 return issues_found

430

431

432 def _check_boxes(root_path):

433 box_issues = []

434

435 modules_path = os.path.join(root_path, "modules")

436 boxes = get_folders(modules_path)

437 boxes = [b for b in boxes if b.startswith("box__")]

438

439 for b in boxes:

440 box_info = {"path": os.path.join(modules_path, b), "apps_issues": []}

441 box_info["issues"] = _verify_box(os.path.join(modules_path, b))["issues"]

442

443 for app in get_folders(os.path.join(modules_path, b)):

444 app_issues = _verify_app(os.path.join(modules_path, b, app), box_name=b)

445 box_info["apps_issues"].append(app_issues)

446 box_issues.append(box_info)

447

448 return box_issues

449

450

451 def _audit():

452 """

453 checks if modules are corrupted

454 """

455 root_path = os.getcwd()

456 _check_modules_path(root_path)

457 apps_issues = _check_apps(root_path)

458 boxes_issues = _check_boxes(root_path)

459

460 click.echo("Running audit ...")

461

462 click.echo("Checking apps ...")

463 for app_issue in apps_issues:

464 click.echo(app_issue["path"])

465 for issue in app_issue["issues"]:

466 click.echo(" " + issue)

467 click.echo("")

468

469 for box_issue in boxes_issues:

470 click.echo(box_issue["path"])

471 for issue in box_issue["issues"]:

472 click.echo(" " + issue)

473

474 for app_issue in box_issue["apps_issues"]:

475 click.echo(" " + app_issue["path"])

476 for issue in app_issue["issues"]:

477 click.echo(" " + issue)

478 click.echo("")

479

480 click.echo("Audit finished!")

481

482

483 def _verify_app_name(app_name):

484 if (app_name.startswith("box__")) and (app_name.count("/") != 1):

485 return False

486

487 if app_name.endswith("/"):

488 return False

489

490 return True

491

492

493 def name_is_box(app_name):

494 if app_name.startswith("box__"):

495 return True

496

497 return False

498

499

500 def _rename_app(old_app_name, new_app_name):

501

502 # box_

503 if old_app_name.startswith("box") and not old_app_name.startswith("box__"):

504 click.echo('Box names start with two __, example: "box__default"')

505 sys.exit()

506 if new_app_name.startswith("box") and not new_app_name.startswith("box__"):

507 click.echo('Box names start with two __, example: "box__default"')

508 sys.exit()

509

510 if (not _verify_app_name(old_app_name)) and (not _verify_app_name(new_app_name)):

511 click.echo('App names should be in the format "app" or "box_name/app"')

512 sys.exit()

513

514 root_path = os.getcwd()

515 modules_path = os.path.join(root_path, "modules")

516

517 if name_is_box(old_app_name):

518 box_name = old_app_name.split("/")[0]

519 if name_is_box(new_app_name):

520 app_part = old_app_name.split("/")[1]

521 app_name = new_app_name.split("/")[1]

522

523 if not path_exists(os.path.join(modules_path, box_name, app_part)):

524 click.echo(f"App {old_app_name} does not exist")

525 sys.exit()

526

527 try:

528 os.rename(

529 os.path.join(modules_path, old_app_name),

530 os.path.join(modules_path, new_app_name),

531 )

532

533 with open(os.path.join(modules_path, new_app_name, "info.json")) as f:

534 json_data = json.load(f)

535

536 with open(os.path.join(modules_path, new_app_name, "info.json"), "w+") as f:

537 if name_is_box(new_app_name):

538 module_name = new_app_name.split("/")[1]

539 else:

540 module_name = new_app_name

541 json_data["module_name"] = module_name

542 json.dump(json_data, f, indent=4)

543

544 click.echo(f"Renamed app {old_app_name} to {new_app_name}")

545 except Exception as e:

546 raise e