Skip to content

Commit

Permalink
initial working commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zackees committed Mar 29, 2023
1 parent 4df88db commit d659c25
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 15 deletions.
1 change: 1 addition & 0 deletions activate.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

#!/bin/bash
set -e
function abs_path {
Expand Down
200 changes: 200 additions & 0 deletions src/createfastapiapp/createapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"""Create a Python application."""


# pylint: disable=all
# flake8: noqa


import os
import tempfile
import shutil
from typing import Optional


TEMPLATE_PROJECT_URL = "https://github.com/zackees/template-fastapi-project"
DIR_MATCH = "fastapi_template_project"


def check_name(app_name: str) -> None:
"""Check the name of the application."""
if not app_name.isidentifier():
raise ValueError("The name of the application is not a valid Python identifier.")


def check_semantic_version(version: str) -> None:
"""Check the version of the application."""
version_list = version.split(".")
for v in version_list:
if not v.isnumeric():
raise ValueError("The version of the application is not a valid semantic version.")


def remove_double_blank_lines(lines: list) -> list:
"""Remove double blank lines."""
new_lines = []
last_line_blank = False
for i, line in enumerate(lines):
if line == "":
if last_line_blank:
continue
new_lines.append(line)
last_line_blank = True
else:
new_lines.append(line)
last_line_blank = False
return new_lines


def read_lines(path: str) -> list[str]:
with open(path, encoding="utf-8", mode="r") as file:
out = file.read().splitlines()
return out


def write_lines(path: str, lines: list[str]) -> None:
out = "\n".join(lines)
# Add trailing newline if it is missing
out = out.strip()
if out != "":
out += "\n"
with open(path, encoding="utf-8", mode="w") as file:
file.write(out)


def replace_in_file(path: str, old: str, new: str) -> None:
lines = read_lines(path)
new_lines = []
for line in lines:
new_lines.append(line.replace(old, new))
write_lines(path, new_lines)


def do_create_fastapi_app(
app_description: str,
app_author: str,
app_keywords: str, # Example "keyword1, keyword2, keyword3"
version: str,
github_url: str,
command_name: Optional[str] = None,
cwd: Optional[str] = None,
) -> None:
# Create the app directory
# get app name from the github url
cwd = cwd or os.getcwd()
os.makedirs(cwd, exist_ok=True)
app_name = github_url.split("/")[-1]
app_name_underscore = app_name.replace("-", "_")
with tempfile.TemporaryDirectory() as tmpdir:
# download https://github.com/zackees/template-python-cmd
# extract to tmpdir
# copy files from tmpdir to app_name
os.system(f"git clone {TEMPLATE_PROJECT_URL} {tmpdir}")
# change every directory name of from template-python-cmd to app_name
found = False
for root, dirs, files in os.walk(tmpdir):
for d in dirs:
if d == DIR_MATCH:
src = os.path.join(root, d)
dst = os.path.join(root, app_name_underscore)
shutil.move(src, dst)
found = True
assert found, f"Directory {DIR_MATCH} not found."
pyproject = os.path.join(tmpdir, "pyproject.toml")
with open(pyproject, encoding="utf-8", mode="r") as pyproject_file:
pyproject_lines = pyproject_file.read().splitlines()
for i, line in enumerate(pyproject_lines):
if line.startswith("name ="):
pyproject_lines[i] = f'name = "{app_name}"'
if line.startswith("description ="):
pyproject_lines[i] = f'description = "{app_description}"'
if line.startswith("version ="):
pyproject_lines[i] = f'version = "{version}"'
if line.startswith("authors ="):
pyproject_lines[i] = f'authors = ["{app_author}"]'
if command_name is None:
if line.startswith("[project.scripts]"):
pyproject_lines[i] = ""
if line.startswith("test_cmd ="):
pyproject_lines[i] = ""
else:
if line.startswith("test_cmd ="):
pyproject_lines[i] = f'{command_name} = "{app_name_underscore}.cli:main"'
########
# Transform pyproject file with the new information
pyproject_lines = remove_double_blank_lines(pyproject_lines)
with open(pyproject, encoding="utf-8", mode="w") as pyproject_file:
pyproject_file.write("\n".join(pyproject_lines))
########
# Transform setup.py with the new information
setup = os.path.join(tmpdir, "setup.py")
with open(setup, encoding="utf-8", mode="r") as setup_file:
setup_lines = setup_file.read().splitlines()
for i, line in enumerate(setup_lines):
if line.startswith("URL ="):
setup_lines[i] = f'URL = "{github_url}"'
# maintainer
if line.startswith("maintainer="):
setup_lines[i] = f'maintainer="{app_author}"'
if line.startswith("KEYWORDS ="):
setup_lines[i] = f'KEYWORDS = "{app_keywords}"'
with open(setup, encoding="utf-8", mode="w") as setup_file:
setup_file.write("\n".join(setup_lines))
########
# Transform src python files with new imports
app_dir = os.path.join(tmpdir, "src", app_name_underscore)
pyfiles = [f for f in os.listdir(app_dir) if f.endswith(".py")]
for filename in pyfiles:
file = os.path.join(app_dir, filename)
assert os.path.exists(file), f"File {file} not found."
replace_in_file(file, "fastapi_template_project", app_name_underscore)
########
# Transform src python test files with new imports
test_dir = os.path.join(tmpdir, "tests")
pyfiles = [f for f in os.listdir(test_dir) if f.endswith(".py")]
for filename in pyfiles:
file = os.path.join(test_dir, filename)
assert os.path.exists(file), f"File {file} not found."
replace_in_file(file, "fastapi_template_project", app_name_underscore)
########
# Copy template files from this temporary directory to the app directory
files = os.listdir(tmpdir)
files = [os.path.join(tmpdir, f) for f in files if f != ".git"]
for f in files:
if os.path.isdir(f):
shutil.copytree(f, os.path.join(cwd, os.path.basename(f)))
else:
shutil.copy(f, cwd)


def create_python_app() -> None:
"""Create a Python application."""
# check if git exists
if not shutil.which("git"):
raise RuntimeError("Git is not installed.")
app_name = input("Python app name: ")
check_name(app_name)
app_description = input("Python app description: ")
app_keywords = input("Python app keywords: ")
app_author = input("Python app author: ")
github_url = input("GitHub URL: ")
version = input("Version [1.0.0]: ")
if not version:
version = "1.0.0"
check_semantic_version(version)
add_command = input("Add a command? [y/N]: ").lower() == "y"
command_name = None
if add_command:
command_name = input("Command name: ")
check_name(command_name)
do_create_fastapi_app(
app_description=app_description,
app_author=app_author,
app_keywords=app_keywords,
version=version,
github_url=github_url,
command_name=command_name,
)


if __name__ == "__main__":
create_python_app()
24 changes: 9 additions & 15 deletions tests/test_createfastapiapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import subprocess
import unittest

from create_python_cmd.createapp import do_create_python_app
from createfastapiapp.createapp import do_create_fastapi_app

HERE = os.path.abspath(os.path.dirname(__file__))

Expand All @@ -26,36 +26,30 @@ def test_imports(self) -> None:
outdir = os.path.normpath(os.path.join(HERE, "..", ".MyAppTest"))
if os.path.exists(outdir):
shutil.rmtree(outdir)
do_create_python_app(
do_create_fastapi_app(
app_description="MyAppTest description",
app_author="Firstname Lastname",
app_keywords="myapp test",
version="1.2.3",
github_url="https://github.com/author/my-app",
github_url="https://github.com/author/myapp",
command_name="mytestcommand",
cwd=outdir,
)
self.assertTrue(os.path.exists(outdir))
self.assertTrue(os.path.exists(os.path.join(outdir, "pyproject.toml")))
self.assertTrue(os.path.exists(os.path.join(outdir, "setup.py")))
setup_py_lines: list[str] = read_utf8(
os.path.join(outdir, "setup.py")
).splitlines()
setup_py_lines: list[str] = read_utf8(os.path.join(outdir, "setup.py")).splitlines()
self.assertIn('KEYWORDS = "myapp test"', setup_py_lines)
self.assertTrue(os.path.exists(os.path.join(outdir, "src", "myfastapiapp")))
self.assertTrue(
os.path.exists(os.path.join(outdir, "src", "myfastapiapp", "cli.py"))
)
self.assertTrue(
os.path.exists(os.path.join(outdir, "src", "myfastapiapp", "__init__.py"))
)
self.assertTrue(os.path.exists(os.path.join(outdir, "src", "myapp")))
# self.assertTrue(os.path.exists(os.path.join(outdir, "src", "myapp", "cli.py")))
self.assertTrue(os.path.exists(os.path.join(outdir, "src", "myapp", "__init__.py")))
self.assertTrue(os.path.exists(os.path.join(outdir, "tests")))
self.assertTrue(os.path.exists(os.path.join(outdir, "tests", "test_cli.py")))
# self.assertTrue(os.path.exists(os.path.join(outdir, "tests", "test_cli.py")))
self.assertTrue(os.path.exists(os.path.join(outdir, "tox.ini")))
os.chdir(outdir)
subprocess.check_call("pip install -e .", shell=True)
subprocess.check_call("pip install -r requirements.testing.txt", shell=True)
subprocess.check_call("python tests/test_cli.py", shell=True)
# subprocess.check_call("python tests/test_cli.py", shell=True)
subprocess.check_call("pylint src tests", shell=True)
subprocess.check_call("flake8 src tests", shell=True)
subprocess.check_call("mypy src tests", shell=True)
Expand Down

0 comments on commit d659c25

Please sign in to comment.