Coverage for api/cli.py: 0%

# Coverage for api/cli.py : 0%

## 203 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 import os

2 import sys

3 from pathlib import Path

4 from shutil import copytree

5 from shutil import ignore_patterns

6 from subprocess import PIPE

7 from subprocess import run

8

9 import click

10 from flask.cli import FlaskGroup

11 from flask.cli import pass_script_info

12

13 from shopyo.api.cmd_helper import _audit

14 from shopyo.api.cmd_helper import _clean

15 from shopyo.api.cmd_helper import _collectstatic

16 from shopyo.api.cmd_helper import _create_box

17 from shopyo.api.cmd_helper import _create_module

18 from shopyo.api.cmd_helper import _rename_app

19 from shopyo.api.cmd_helper import _run_app

20 from shopyo.api.cmd_helper import _upload_data

21 from shopyo.api.constants import SEP_CHAR

22 from shopyo.api.constants import SEP_NUM

23 from shopyo.api.database import autoload_models

24 from shopyo.api.file import trycopy

25 from shopyo.api.info import printinfo

26 from shopyo.api.validators import get_module_path_if_exists

27 from shopyo.api.validators import is_alpha_num_underscore

28

29

30 def _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

47 def 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)

59 def 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)

77 def 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)

161 def 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)

213 def 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)

233 def 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)

287 def 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)

499 def rundebug():

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

501 _run_app("development")

502

503

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

505 def runserver():

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

507 _run_app("production")

508

509

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

511 def 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)

520 def 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)

526 def testok():

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

528 click.echo('test ok!')

529

530 if __name__ == "__main__":

531 cli()