Hide keyboard shortcuts

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

1import os 

2import sys 

3from pathlib import Path 

4from shutil import copytree 

5from shutil import ignore_patterns 

6from subprocess import PIPE 

7from subprocess import run 

8 

9import click 

10from flask.cli import FlaskGroup 

11from flask.cli import pass_script_info 

12 

13from shopyo.api.cmd_helper import _audit 

14from shopyo.api.cmd_helper import _clean 

15from shopyo.api.cmd_helper import _collectstatic 

16from shopyo.api.cmd_helper import _create_box 

17from shopyo.api.cmd_helper import _create_module 

18from shopyo.api.cmd_helper import _rename_app 

19from shopyo.api.cmd_helper import _run_app 

20from shopyo.api.cmd_helper import _upload_data 

21from shopyo.api.constants import SEP_CHAR 

22from shopyo.api.constants import SEP_NUM 

23from shopyo.api.database import autoload_models 

24from shopyo.api.file import trycopy 

25from shopyo.api.info import printinfo 

26from shopyo.api.validators import get_module_path_if_exists 

27from shopyo.api.validators import is_alpha_num_underscore 

28 

29 

30def _create_shopyo_app(): 

31 sys.path.append(os.getcwd()) 

32 try: 

33 from app import create_app 

34 except ImportError: 

35 click.echo( 

36 "Cannot find create_app from app. Make sure you are in the right folder!" 

37 ) 

38 sys.exit() 

39 

40 config_name = os.environ.get("SHOPYO_CONFIG_PROFILE") or "development" 

41 return create_app(config_name=config_name) 

42 

43 

44@click.group(cls=FlaskGroup, create_app=_create_shopyo_app) 

45@click.option("--config", default="development", help="Flask app configuration type") 

46@pass_script_info 

47def cli(info, **parmams): 

48 """CLI for shopyo""" 

49 printinfo() 

50 config_name = parmams["config"] 

51 info.data["config"] = config_name 

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

53 os.environ["FLASK_ENV"] = config_name 

54 

55 

56@cli.command("startbox", with_appcontext=False) 

57@click.argument("boxname") 

58@click.option("--verbose", "-v", is_flag=True, default=False) 

59def create_box(boxname, verbose): 

60 """creates ``box`` with ``box_info.json``. 

61 

62 ``BOXNAME`` is the name of the ``box`` which holds modules 

63 """ 

64 path = os.path.join("modules", boxname) 

65 

66 if os.path.exists(os.path.join("modules", boxname)): 

67 click.echo(f"[ ] unable to create. Box {path} already exists!", err=True) 

68 sys.exit(1) 

69 

70 _create_box(boxname, verbose=verbose) 

71 

72 

73@cli.command("startapp", with_appcontext=False) 

74@click.argument("modulename") 

75@click.argument("boxname", required=False, default="") 

76@click.option("--verbose", "-v", is_flag=True, default=False) 

77def create_module(modulename, boxname, verbose): 

78 """ 

79 create a module/app ``MODULENAME`` inside ``modules/``. If ``BOXNAME`` is 

80 provided, creates the module inside ``modules/BOXNAME.`` 

81 

82 BOXNAME the name of box to create the MODULENAME in. Must start with 

83 ``box__``, otherwise error is thrown 

84 

85 MODULENAME the name of module to be created. Must not start with 

86 ``box__``, otherwise error is thrown 

87 

88 \b 

89 If box ``BOXNAME`` does not exist, it is created. 

90 If ``MODULENAME`` already exists, an error is thrown and command is 

91 terminated. 

92 

93 Following structure of module is created inside `modules` when running 

94 `shopyo startapp demo` 

95 

96 \b 

97 demo/ 

98 ├── forms.py 

99 ├── global.py 

100 ├── info.json 

101 ├── models.py 

102 ├── static 

103 ├── templates 

104 │   └── demo 

105 │   ├── blocks 

106 │   │   └── sidebar.html 

107 │   └── dashboard.html 

108 ├── tests 

109 │   ├── test_demo_functional.py 

110 │   └── test_demo_models.py 

111 └── view.py 

112 """ 

113 if boxname != "" and not boxname.startswith("box__"): 

114 click.echo( 

115 f"[ ] Invalid BOXNAME '{boxname}'. BOXNAME should start with 'box__' prefix" 

116 ) 

117 sys.exit(1) 

118 

119 if modulename.startswith("box_"): 

120 click.echo( 

121 f"[ ] Invalid MODULENAME '{modulename}'. " 

122 "MODULENAME cannot start with box_ prefix" 

123 ) 

124 sys.exit(1) 

125 

126 if not is_alpha_num_underscore(modulename): 

127 click.echo( 

128 "[ ] Error: MODULENAME is not valid, please use alphanumeric " 

129 "and underscore only" 

130 ) 

131 sys.exit(1) 

132 

133 if boxname != "" and not is_alpha_num_underscore(boxname): 

134 click.echo( 

135 "[ ] Error: BOXNAME is not valid, please use alphanumeric " 

136 "and underscore only" 

137 ) 

138 sys.exit(1) 

139 

140 module_path = get_module_path_if_exists(modulename) 

141 

142 if module_path is not None: 

143 click.echo( 

144 f"[ ] Unable to create module '{modulename}'. " 

145 f"MODULENAME already exists inside modules/ at {module_path}" 

146 ) 

147 sys.exit(1) 

148 

149 if boxname != "": 

150 box_path = get_module_path_if_exists(boxname) 

151 if box_path is None: 

152 _create_box(boxname, verbose=verbose) 

153 

154 module_path = os.path.join("modules", boxname, modulename) 

155 _create_module(modulename, base_path=module_path, verbose=verbose) 

156 

157 

158@cli.command("collectstatic", with_appcontext=False) 

159@click.argument("src", required=False, type=click.Path(), default="modules") 

160@click.option("--verbose", "-v", is_flag=True, default=False) 

161def collectstatic(src, verbose): 

162 """Copies ``static/`` in ``modules/`` or ``modules/SRC`` into 

163 ``/static/modules/`` 

164 

165 ``SRC`` is the module path relative to ``modules/`` where ``static/`` 

166 exists. 

167 

168 Ex usage for:: 

169 

170 \b 

171 . 

172 └── modules/ 

173 └── box__default/ 

174 ├── auth/ 

175 │ └── static 

176 └── appadmin/ 

177 └── static 

178 

179 To collect static in only one module, run either of two commands:: 

180 

181 $ shopyo collectstatic box__default/auth 

182 

183 $ shopyo collectstatic modules/box__default/auth 

184 

185 To collect static in all modules inside a box, run either of two commands 

186 below:: 

187 

188 $ shopyo collectstatic box__default 

189 

190 $ shopyo collectstatic modules/box__default 

191 

192 To collect static in all modules run either of the two commands below:: 

193 

194 $ shopyo collectstatic 

195 

196 $ shopyo collectstatic modules 

197 """ 

198 _collectstatic(target_module=src, verbose=verbose) 

199 

200 

201@cli.command("clean") 

202@click.option( 

203 "--clear-migration/--no-clear-migration", 

204 "clear_migration", 

205 "-cm", 

206 is_flag=True, 

207 default=True, 

208) 

209@click.option( 

210 "--clear-db/--no-clear-db", "clear_db", "-cdb", is_flag=True, default=True 

211) 

212@click.option("--verbose", "-v", is_flag=True, default=False) 

213def clean(verbose, clear_migration, clear_db): 

214 """removes ``__pycache__``, ``migrations/``, ``shopyo.db`` files and drops 

215 ``db`` if present 

216 """ 

217 _clean(verbose=verbose, clear_migration=clear_migration, clear_db=clear_db) 

218 

219 

220@cli.command("initialise") 

221@click.option("--verbose", "-v", is_flag=True, default=False) 

222@click.option( 

223 "--clear-migration/--no-clear-migration", 

224 "clear_migration", 

225 "-cm", 

226 is_flag=True, 

227 default=True, 

228) 

229@click.option( 

230 "--clear-db/--no-clear-db", "clear_db", "-cdb", is_flag=True, default=True 

231) 

232@click.option("--verbose", "-v", is_flag=True, default=False) 

233def initialise(verbose, clear_migration, clear_db): 

234 """ 

235 Creates ``db``, ``migration/``, adds default users, add settings 

236 """ 

237 click.echo("initializing...") 

238 

239 # drop db, remove mirgration/ and shopyo.db 

240 _clean(verbose=verbose, clear_migration=clear_migration, clear_db=clear_db) 

241 

242 # load all models available inside modules 

243 autoload_models(verbose=verbose) 

244 

245 # add a migrations folder to your application. 

246 click.echo("Creating db...") 

247 click.echo(SEP_CHAR * SEP_NUM) 

248 if verbose: 

249 run(["flask", "db", "init"]) 

250 else: 

251 run(["flask", "db", "init"], stdout=PIPE, stderr=PIPE) 

252 click.echo("") 

253 

254 # generate an initial migration i.e autodetect changes in the 

255 # tables (table autodetection is limited. See 

256 # https://flask-migrate.readthedocs.io/en/latest/ for more details) 

257 click.echo("Migrating db...") 

258 click.echo(SEP_CHAR * SEP_NUM) 

259 if verbose: 

260 run(["flask", "db", "migrate"]) 

261 else: 

262 run(["flask", "db", "migrate"], stdout=PIPE, stderr=PIPE) 

263 click.echo("") 

264 

265 click.echo("Upgrading db...") 

266 click.echo(SEP_CHAR * SEP_NUM) 

267 if verbose: 

268 run(["flask", "db", "upgrade"]) 

269 else: 

270 run(["flask", "db", "upgrade"], stdout=PIPE, stderr=PIPE) 

271 click.echo("") 

272 

273 # collect all static folders inside modules/ and add it to global 

274 # static/ 

275 _collectstatic(verbose=verbose) 

276 

277 # Upload models data in upload.py files inside each module 

278 _upload_data(verbose=verbose) 

279 

280 click.echo("All Done!") 

281 

282 

283@cli.command("new", with_appcontext=False) 

284@click.argument("projname", required=False, default="") 

285@click.option("--verbose", "-v", is_flag=True, default=False) 

286@click.option("--modules", "-m", is_flag=True, default=False) 

287def new(projname, verbose, modules): 

288 """Creates a new shopyo project. 

289 

290 By default it will create the project(folder) of same name as the parent 

291 folder. If ``PROJNAME`` is provided, it will create ``PROJNAME/PROJNAME`` 

292 under parent folder 

293 

294 ``PROJNAME`` is the name of the project that you want to create. 

295 """ 

296 

297 modules_flag = modules 

298 

299 from shopyo.__init__ import __version__ 

300 from shopyo.api.file import trymkfile 

301 from shopyo.api.file import trymkdir 

302 from shopyo.api.cli_content import get_tox_ini_content 

303 from shopyo.api.cli_content import get_dev_req_content 

304 from shopyo.api.cli_content import get_gitignore_content 

305 from shopyo.api.cli_content import get_sphinx_conf_py 

306 from shopyo.api.cli_content import get_sphinx_makefile 

307 from shopyo.api.cli_content import get_index_rst_content 

308 from shopyo.api.cli_content import get_docs_rst_content 

309 from shopyo.api.cli_content import get_pytest_ini_content 

310 from shopyo.api.cli_content import get_manifest_ini_content 

311 from shopyo.api.cli_content import get_init_content 

312 from shopyo.api.cli_content import get_cli_content 

313 from shopyo.api.cli_content import get_setup_py_content 

314 

315 here = os.getcwd() 

316 

317 if projname == "": 

318 

319 projname = os.path.basename(here) 

320 

321 # the base/root project folder where files such as README, docs, 

322 # .gitignore etc. will be stored will be same as current working 

323 # directory i.e ./ 

324 root_proj_path = here 

325 

326 # the current project path in which we will create the project 

327 project_path = os.path.join(here, projname) 

328 

329 if os.path.exists(project_path): 

330 click.echo( 

331 f"[ ] Error: Unable to create new project. Path {project_path} exits" 

332 ) 

333 sys.exit(1) 

334 

335 else: 

336 if not is_alpha_num_underscore(projname): 

337 click.echo( 

338 "[ ] Error: PROJNAME is not valid, please use alphanumeric " 

339 "and underscore only" 

340 ) 

341 sys.exit(1) 

342 

343 # the base/root project folder where files such as README, docs, 

344 # .gitignore etc. will be stored will be ./projname 

345 root_proj_path = os.path.join(here, projname) 

346 

347 # the current project path in which we will create the project 

348 project_path = os.path.join(here, projname, projname) 

349 

350 if os.path.exists(root_proj_path): 

351 click.echo( 

352 f"[ ] Error: Unable to create new project. Path {root_proj_path} exits" 

353 ) 

354 sys.exit(1) 

355 

356 click.echo(f"creating project {projname}...") 

357 click.echo(SEP_CHAR * SEP_NUM) 

358 

359 # the shopyo src path that the new project will mimic 

360 src_shopyo_shopyo = Path(__file__).parent.parent.absolute() 

361 

362 # copy the shopyo/shopyo content to the new project 

363 copytree( 

364 src_shopyo_shopyo, 

365 project_path, 

366 ignore=ignore_patterns( 

367 "__main__.py", 

368 "api", 

369 ".tox", 

370 ".coverage", 

371 "*.db", 

372 "coverage.xml", 

373 "setup.cfg", 

374 "instance", 

375 "migrations", 

376 "__pycache__", 

377 "*.pyc", 

378 "sphinx_source", 

379 "config.json", 

380 "pyproject.toml", 

381 "modules", 

382 ), 

383 ) 

384 

385 # create requirements.txt in root 

386 trymkfile( 

387 os.path.join(root_proj_path, "requirements.txt"), 

388 f"shopyo=={__version__}\n", 

389 verbose=verbose, 

390 ) 

391 

392 # copy the dev_requirement.txt in root 

393 trymkfile( 

394 os.path.join(root_proj_path, "dev_requirements.txt"), 

395 get_dev_req_content(), 

396 verbose=verbose, 

397 ) 

398 

399 # copy the tox.ini in root 

400 trymkfile( 

401 os.path.join(root_proj_path, "tox.ini"), 

402 get_tox_ini_content(projname), 

403 verbose=verbose, 

404 ) 

405 

406 # create MANIFEST.in needed for tox 

407 trymkfile( 

408 os.path.join(root_proj_path, "MANIFEST.in"), 

409 get_manifest_ini_content(projname), 

410 verbose=verbose, 

411 ) 

412 

413 # create README.md in root 

414 trymkfile( 

415 os.path.join(root_proj_path, "README.md"), 

416 f"# Welcome to {projname}", 

417 verbose=verbose, 

418 ) 

419 

420 # create .gitignore in root 

421 trymkfile( 

422 os.path.join(root_proj_path, ".gitignore"), 

423 get_gitignore_content(), 

424 verbose=verbose, 

425 ) 

426 

427 # create pytest.ini 

428 trymkfile( 

429 os.path.join(root_proj_path, "pytest.ini"), 

430 get_pytest_ini_content(), 

431 verbose=verbose, 

432 ) 

433 

434 # create setup.py 

435 trymkfile( 

436 os.path.join(root_proj_path, "setup.py"), 

437 get_setup_py_content(projname), 

438 verbose=verbose, 

439 ) 

440 

441 # override the __init__.py file 

442 trymkfile( 

443 os.path.join(project_path, "__init__.py"), get_init_content(), verbose=verbose 

444 ) 

445 

446 # add cli.py for users to add their own cli 

447 trymkfile( 

448 os.path.join(project_path, "cli.py"), get_cli_content(projname), verbose=verbose 

449 ) 

450 

451 # # app.py 

452 # trycopy( 

453 # os.path.join(src_shopyo_shopyo, "app.txt"), os.path.join(project_path, "app.py") 

454 # ) 

455 

456 sphinx_src = os.path.join(root_proj_path, "docs") 

457 

458 # create sphinx docs in project root 

459 trymkdir(sphinx_src, verbose=verbose) 

460 # create sphinx conf.py inside docs 

461 trymkfile( 

462 os.path.join(sphinx_src, "conf.py"), 

463 get_sphinx_conf_py(projname), 

464 verbose=verbose, 

465 ) 

466 # create _static sphinx folder 

467 trymkdir(os.path.join(sphinx_src, "_static"), verbose=verbose) 

468 trymkfile(os.path.join(sphinx_src, "_static", "custom.css"), "", verbose=verbose) 

469 # create sphinx Makefile inside docs 

470 trymkfile( 

471 os.path.join(sphinx_src, "Makefile"), get_sphinx_makefile(), verbose=verbose 

472 ) 

473 # create index page 

474 trymkfile( 

475 os.path.join(sphinx_src, "index.rst"), 

476 get_index_rst_content(projname), 

477 verbose=verbose, 

478 ) 

479 # create docs page 

480 trymkfile( 

481 os.path.join(sphinx_src, "docs.rst"), 

482 get_docs_rst_content(projname), 

483 verbose=verbose, 

484 ) 

485 

486 click.echo(f"[x] Project {projname} created successfully!\n") 

487 

488 if modules_flag: 

489 copytree( 

490 os.path.join(src_shopyo_shopyo, "modules"), 

491 os.path.join(project_path, "modules"), 

492 ) 

493 else: 

494 # empty modules folder 

495 trymkdir(os.path.join(project_path, "modules"), verbose=verbose) 

496 

497 

498@cli.command("rundebug", with_appcontext=False) 

499def rundebug(): 

500 """runs the shopyo flask app in development mode""" 

501 _run_app("development") 

502 

503 

504@cli.command("runserver", with_appcontext=False) 

505def runserver(): 

506 """runs the shopyo flask app in production mode""" 

507 _run_app("production") 

508 

509 

510@cli.command("audit", with_appcontext=False) 

511def audit(): 

512 """Audits the project and finds issues""" 

513 _audit() 

514 

515 

516@cli.command("rename", with_appcontext=False) 

517@click.argument("old_name", required=True) 

518@click.argument("new_name", required=True) 

519@click.option("--verbose", "-v", is_flag=True, default=False) 

520def rename(old_name, new_name, verbose): 

521 """Renames apps""" 

522 _rename_app(old_name, new_name) 

523 

524 

525@cli.command("testok", with_appcontext=False) 

526def testok(): 

527 """Just testing if the cli works""" 

528 click.echo('test ok!') 

529 

530if __name__ == "__main__": 

531 cli()