Coverage for api/cli.py : 0%

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
9import click
10from flask.cli import FlaskGroup
11from flask.cli import pass_script_info
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
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()
40 config_name = os.environ.get("SHOPYO_CONFIG_PROFILE") or "development"
41 return create_app(config_name=config_name)
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
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``.
62 ``BOXNAME`` is the name of the ``box`` which holds modules
63 """
64 path = os.path.join("modules", boxname)
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)
70 _create_box(boxname, verbose=verbose)
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.``
82 BOXNAME the name of box to create the MODULENAME in. Must start with
83 ``box__``, otherwise error is thrown
85 MODULENAME the name of module to be created. Must not start with
86 ``box__``, otherwise error is thrown
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.
93 Following structure of module is created inside `modules` when running
94 `shopyo startapp demo`
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)
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)
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)
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)
140 module_path = get_module_path_if_exists(modulename)
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)
149 if boxname != "":
150 box_path = get_module_path_if_exists(boxname)
151 if box_path is None:
152 _create_box(boxname, verbose=verbose)
154 module_path = os.path.join("modules", boxname, modulename)
155 _create_module(modulename, base_path=module_path, verbose=verbose)
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/``
165 ``SRC`` is the module path relative to ``modules/`` where ``static/``
166 exists.
168 Ex usage for::
170 \b
171 .
172 └── modules/
173 └── box__default/
174 ├── auth/
175 │ └── static
176 └── appadmin/
177 └── static
179 To collect static in only one module, run either of two commands::
181 $ shopyo collectstatic box__default/auth
183 $ shopyo collectstatic modules/box__default/auth
185 To collect static in all modules inside a box, run either of two commands
186 below::
188 $ shopyo collectstatic box__default
190 $ shopyo collectstatic modules/box__default
192 To collect static in all modules run either of the two commands below::
194 $ shopyo collectstatic
196 $ shopyo collectstatic modules
197 """
198 _collectstatic(target_module=src, verbose=verbose)
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)
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...")
239 # drop db, remove mirgration/ and shopyo.db
240 _clean(verbose=verbose, clear_migration=clear_migration, clear_db=clear_db)
242 # load all models available inside modules
243 autoload_models(verbose=verbose)
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("")
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("")
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("")
273 # collect all static folders inside modules/ and add it to global
274 # static/
275 _collectstatic(verbose=verbose)
277 # Upload models data in upload.py files inside each module
278 _upload_data(verbose=verbose)
280 click.echo("All Done!")
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.
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
294 ``PROJNAME`` is the name of the project that you want to create.
295 """
297 modules_flag = modules
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
315 here = os.getcwd()
317 if projname == "":
319 projname = os.path.basename(here)
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
326 # the current project path in which we will create the project
327 project_path = os.path.join(here, projname)
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)
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)
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)
347 # the current project path in which we will create the project
348 project_path = os.path.join(here, projname, projname)
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)
356 click.echo(f"creating project {projname}...")
357 click.echo(SEP_CHAR * SEP_NUM)
359 # the shopyo src path that the new project will mimic
360 src_shopyo_shopyo = Path(__file__).parent.parent.absolute()
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 )
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 )
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 )
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 )
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 )
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 )
420 # create .gitignore in root
421 trymkfile(
422 os.path.join(root_proj_path, ".gitignore"),
423 get_gitignore_content(),
424 verbose=verbose,
425 )
427 # create pytest.ini
428 trymkfile(
429 os.path.join(root_proj_path, "pytest.ini"),
430 get_pytest_ini_content(),
431 verbose=verbose,
432 )
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 )
441 # override the __init__.py file
442 trymkfile(
443 os.path.join(project_path, "__init__.py"), get_init_content(), verbose=verbose
444 )
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 )
451 # # app.py
452 # trycopy(
453 # os.path.join(src_shopyo_shopyo, "app.txt"), os.path.join(project_path, "app.py")
454 # )
456 sphinx_src = os.path.join(root_proj_path, "docs")
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 )
486 click.echo(f"[x] Project {projname} created successfully!\n")
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)
498@cli.command("rundebug", with_appcontext=False)
499def rundebug():
500 """runs the shopyo flask app in development mode"""
501 _run_app("development")
504@cli.command("runserver", with_appcontext=False)
505def runserver():
506 """runs the shopyo flask app in production mode"""
507 _run_app("production")
510@cli.command("audit", with_appcontext=False)
511def audit():
512 """Audits the project and finds issues"""
513 _audit()
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)
525@cli.command("testok", with_appcontext=False)
526def testok():
527 """Just testing if the cli works"""
528 click.echo('test ok!')
530if __name__ == "__main__":
531 cli()