Skip to content

Commit 3c24879

Browse files
authored
Merge pull request #20 from eadwinCode/cli_commands_and_project_scaffold_init
WIP: CLI Commands And Basic Project Scaffolding
2 parents 1ce7bc9 + 5f50c0d commit 3c24879

File tree

104 files changed

+2957
-239
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+2957
-239
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ name: Test
22

33
on:
44
push:
5-
branches:
6-
- master
5+
pull_request:
6+
types: [assigned, opened, synchronize, reopened]
77

88
jobs:
99
test_coverage:

.github/workflows/test_full.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Set up Python
3333
uses: actions/setup-python@v4
3434
with:
35-
python-version: 3.9
35+
python-version: 3.8
3636
- name: Install Flit
3737
run: pip install flit
3838
- name: Install Dependencies

.pre-commit-config.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ repos:
99
- id: yesqa
1010
- repo: local
1111
hooks:
12-
- id: code_linting
12+
- id: code_formatting
1313
args: []
14+
name: Code Formatting
15+
entry: "make fmt"
16+
types: [python]
17+
language_version: python3.6
18+
language: python
19+
- id: code_linting
20+
args: [ ]
1421
name: Code Linting
1522
entry: "make lint"
16-
types: [python]
23+
types: [ python ]
1724
language_version: python3.6
1825
language: python
1926
- repo: https://github.com/pre-commit/pre-commit-hooks

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ clean: ## Removing cached python compiled files
1313
install: ## Install dependencies
1414
flit install --deps develop --symlink
1515

16+
install-full: ## Install dependencies
17+
make install
18+
pre-commit install -f
19+
1620
lint: ## Run code linters
1721
black --check ellar tests
1822
isort --check ellar tests
@@ -33,3 +37,9 @@ test-cov: ## Run tests with coverage
3337
doc-deploy: ## Run Deploy Documentation
3438
make clean
3539
mkdocs gh-deploy --force
40+
41+
42+
pre-commit-lint: ## Runs Requires commands during pre-commit
43+
make clean
44+
make fmt
45+
make lint

ellar/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
"""Ellar is python web framework for building fast, efficient and scalable server-side applications."""
1+
"""Ellar - Python ASGI web framework for building fast, efficient and scalable RESTAPIs and server-side application."""
22

33
__version__ = "0.1.4"

ellar/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
from ellar.cli import main
1+
if __name__ == "__main__":
2+
from ellar.cli import main
23

3-
main()
4+
main()

ellar/cli.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

ellar/cli/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
import sys
3+
import typing as t
4+
5+
from typer import echo
6+
7+
from ._main import _typer, build_typers
8+
9+
__all__ = ["main"]
10+
11+
12+
def register_commands(*typer_commands: t.Any) -> None:
13+
for typer_command in typer_commands:
14+
_typer.add_typer(typer_command)
15+
16+
17+
@_typer.command()
18+
def say_hi(name: str):
19+
echo(f"Welcome {name}, to Ellar CLI, python web framework")
20+
21+
22+
# register all EllarTyper(s) to root typer
23+
# register_commands(*other_commands)
24+
25+
26+
def main():
27+
sys.path.append(os.getcwd())
28+
build_typers()
29+
_typer(prog_name="Ellar, Python Web framework")
30+
31+
32+
if __name__ == "__main__":
33+
main()

ellar/cli/_main.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import getopt
2+
import sys
3+
import typing as t
4+
5+
import typer
6+
from typer import Typer
7+
from typer.models import CommandInfo
8+
9+
from ellar.commands import EllarTyper
10+
from ellar.constants import CALLABLE_COMMAND_INFO, ELLAR_META, MODULE_METADATA
11+
from ellar.core.factory import AppFactory
12+
from ellar.services import Reflector
13+
14+
from .manage_commands import create_module, create_project, runserver
15+
from .service import EllarCLIService
16+
17+
__all__ = ["build_typers", "_typer", "typer_callback"]
18+
19+
_typer = Typer(name="ellar")
20+
21+
_typer.command()(runserver)
22+
_typer.command(name="create-project")(create_project)
23+
_typer.command(name="create-module")(create_module)
24+
25+
26+
@_typer.callback()
27+
def typer_callback(
28+
ctx: typer.Context,
29+
project: t.Optional[str] = typer.Option(
30+
None,
31+
"-p",
32+
"--project",
33+
show_default=True,
34+
exists=True,
35+
help="Run Specific Command on a specific project",
36+
),
37+
) -> None:
38+
meta_: EllarCLIService = EllarCLIService.import_project_meta(project)
39+
ctx.meta[ELLAR_META] = meta_
40+
41+
42+
def build_typers() -> None:
43+
try:
44+
options, args = getopt.getopt(
45+
sys.argv[1:],
46+
"p:",
47+
[
48+
"project=",
49+
],
50+
)
51+
app_name: t.Optional[str] = None
52+
53+
for k, v in options:
54+
if k in ["-p", "--project"] and v:
55+
app_name = v
56+
except Exception:
57+
raise typer.Abort()
58+
59+
meta_: EllarCLIService = EllarCLIService.import_project_meta(app_name)
60+
61+
if meta_ and meta_.has_meta:
62+
modules = AppFactory.get_all_modules(meta_.import_root_module())
63+
reflector = Reflector()
64+
65+
for module in modules:
66+
typers_commands = reflector.get(MODULE_METADATA.COMMANDS, module) or []
67+
for typer_command in typers_commands:
68+
if isinstance(typer_command, EllarTyper):
69+
_typer.add_typer(typer_command)
70+
elif hasattr(typer_command, CALLABLE_COMMAND_INFO):
71+
command_info: CommandInfo = typer_command.__dict__[
72+
CALLABLE_COMMAND_INFO
73+
]
74+
_typer.registered_commands.append(command_info)

ellar/cli/file_scaffolding.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import typing as t
3+
from abc import abstractmethod
4+
5+
from jinja2 import Environment
6+
7+
from ellar.core.schema import EllarScaffoldList, EllarScaffoldSchema
8+
9+
from .service import EllarCLIService
10+
11+
__all__ = ["FileTemplateScaffold"]
12+
13+
14+
class ProjectScaffoldContext(dict):
15+
def __init__(self, environment: Environment, **kwargs):
16+
super().__init__(kwargs)
17+
self.environment = environment
18+
self.environment.globals.update(kwargs)
19+
20+
21+
class FileTemplateScaffold:
22+
def __init__(
23+
self,
24+
*,
25+
schema: EllarScaffoldSchema,
26+
working_project_name: str,
27+
working_directory: str,
28+
scaffold_ellar_template_root_path: str,
29+
ellar_cli_service: EllarCLIService,
30+
):
31+
self._schema = schema
32+
self._working_project_name = working_project_name
33+
self._ctx = ProjectScaffoldContext(
34+
Environment(), **self.get_scaffolding_context(working_project_name)
35+
)
36+
self._working_directory = working_directory
37+
self._scaffold_ellar_template_root_path = scaffold_ellar_template_root_path
38+
self.ellar_cli_service = ellar_cli_service
39+
self.validate_project_name()
40+
41+
def get_scaffolding_context(self, working_project_name: str) -> t.Dict:
42+
return {}
43+
44+
@classmethod
45+
def read_file_content(cls, path: str) -> str:
46+
with open(path, mode="r") as fp:
47+
return fp.read()
48+
49+
def create_root_path(self) -> str:
50+
root_dir = os.path.join(
51+
self._working_directory, self._working_project_name.lower()
52+
)
53+
os.mkdir(root_dir)
54+
return root_dir
55+
56+
def create_file(self, base_path: str, file_name: str, content: t.Any) -> None:
57+
with open(
58+
os.path.join(base_path, file_name.replace("ellar", "py")), mode="w"
59+
) as fw:
60+
refined_content = self._ctx.environment.from_string(content).render()
61+
fw.writelines(refined_content)
62+
63+
def scaffold(self) -> None:
64+
self.on_scaffold_started()
65+
for file in self._schema.files:
66+
self.create_directory(
67+
file,
68+
scaffold_ellar_template_path=self._scaffold_ellar_template_root_path,
69+
working_directory=self._working_directory,
70+
)
71+
self.on_scaffold_completed()
72+
73+
def on_scaffold_completed(self) -> None:
74+
pass
75+
76+
def on_scaffold_started(self) -> None:
77+
for context in self._schema.context:
78+
assert self._ctx[context], f"{context} template context is missing."
79+
80+
def create_directory(
81+
self, file: EllarScaffoldList, scaffold_ellar_template_path, working_directory
82+
) -> None:
83+
name = file.name
84+
if name in self._ctx:
85+
name = self._ctx.get(file.name, file.name)
86+
87+
scaffold_template_path = os.path.join(scaffold_ellar_template_path, file.name)
88+
89+
if file.is_directory:
90+
new_scaffold_dir = os.path.join(working_directory, name)
91+
os.makedirs(new_scaffold_dir, exist_ok=True)
92+
for file in file.files or []:
93+
self.create_directory(
94+
file=file,
95+
working_directory=new_scaffold_dir,
96+
scaffold_ellar_template_path=scaffold_template_path,
97+
)
98+
else:
99+
content = self.read_file_content(scaffold_template_path)
100+
self.create_file(
101+
base_path=working_directory, file_name=name, content=content
102+
)
103+
104+
@abstractmethod
105+
def validate_project_name(self) -> None:
106+
# Check it's a valid directory name.
107+
pass

0 commit comments

Comments
 (0)