Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .github/workflows/changelog.yml

This file was deleted.

1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files

- repo: local
hooks:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v.1.1.2 (2025-09-05)

### Improvements

- add a feature of `fastkit init`, `fastkit startdemo` command to define to make a new project folder at current working directory
- add `setuptools` package at `fastapi-empty` template's dependency list.
- add a feature of `fastkit addroute`command to recognize current working project (with cmd option `.`).

## v1.1.1 (2025-08-15)

### Improvements
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ FastAPI-fastkit uses following stacks:
- isort for import sorting
- mypy for static type checking

### Current Source Structure (Version 1.X.X+)

![fastkit diagram](docs/img/fastkit_diagram.png)

### Quick Setup with Makefile

The easiest way to set up your development environment is using our Makefile:
Expand Down
Binary file added docs/img/fastkit_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/fastapi_fastkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.1.1"
__version__ = "1.1.2"

import os

Expand Down
74 changes: 72 additions & 2 deletions src/fastapi_fastkit/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
import re
from typing import Dict, List

import click

from fastapi_fastkit import console
from fastapi_fastkit.backend.package_managers import PackageManagerFactory
from fastapi_fastkit.backend.transducer import copy_and_convert_template_file
from fastapi_fastkit.core.exceptions import BackendExceptions, TemplateExceptions
from fastapi_fastkit.backend.transducer import (
copy_and_convert_template,
copy_and_convert_template_file,
)
from fastapi_fastkit.core.exceptions import BackendExceptions
from fastapi_fastkit.core.settings import settings
from fastapi_fastkit.utils.logging import debug_log, get_logger
from fastapi_fastkit.utils.main import (
Expand Down Expand Up @@ -697,3 +702,68 @@ def add_new_route(project_dir: str, route_name: str) -> None:
debug_log(f"Unexpected error while adding route {route_name}: {e}", "error")
handle_exception(e, f"Error adding new route: {str(e)}")
raise BackendExceptions(f"Failed to add new route: {str(e)}")


# ------------------------------------------------------------
# Create Project Folder Functions
# ------------------------------------------------------------


def ask_create_project_folder(project_name: str) -> bool:
"""
Ask user whether to create a new project folder.

:param project_name: Name of the project
:return: True if user wants to create a folder, False otherwise
"""
return click.confirm(
f"\nCreate a new project folder named '{project_name}'?\n"
f"Yes: Templates will be placed in './{project_name}/'\n"
f"No: Templates will be placed in current directory",
default=True,
)


def deploy_template_with_folder_option(
target_template: str, user_local: str, project_name: str, create_folder: bool
) -> tuple[str, str]:
"""
Deploy template based on folder creation option.

:param target_template: Path to template directory
:param user_local: User's workspace directory
:param project_name: Name of the project
:param create_folder: Whether to create a new folder
:return: Tuple of (project_dir, deployment_message)
"""
if create_folder:
project_dir = os.path.join(user_local, project_name)
deployment_message = f"FastAPI template project will deploy at '{user_local}' in folder '{project_name}'"
copy_and_convert_template(target_template, user_local, project_name)
else:
project_dir = user_local
deployment_message = (
f"FastAPI template project will deploy directly at '{user_local}'"
)
copy_and_convert_template(target_template, user_local, "")

click.echo(deployment_message)
return project_dir, deployment_message


def get_deployment_success_message(
template: str, project_name: str, user_local: str, create_folder: bool
) -> str:
"""
Get appropriate success message based on deployment option.

:param template: Template name used
:param project_name: Name of the project
:param user_local: User's workspace directory
:param create_folder: Whether folder was created
:return: Success message string
"""
if create_folder:
return f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
else:
return f"FastAPI project '{project_name}' from '{template}' has been deployed directly to {user_local}!"
102 changes: 70 additions & 32 deletions src/fastapi_fastkit/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# TODO : add a feature to automatically fix appropriate fastkit output console size
# --------------------------------------------------------------------------
# The Module defines main and core CLI operations for FastAPI-fastkit.
#
Expand All @@ -17,14 +18,16 @@

from fastapi_fastkit.backend.main import (
add_new_route,
ask_create_project_folder,
create_venv_with_manager,
deploy_template_with_folder_option,
find_template_core_modules,
generate_dependency_file_with_manager,
get_deployment_success_message,
inject_project_metadata,
install_dependencies_with_manager,
read_template_stack,
)
from fastapi_fastkit.backend.transducer import copy_and_convert_template
from fastapi_fastkit.core.exceptions import CLIExceptions
from fastapi_fastkit.core.settings import FastkitConfig
from fastapi_fastkit.utils.logging import get_logger, setup_logging
Expand Down Expand Up @@ -191,6 +194,7 @@ def startdemo(
"""
Create a new FastAPI project from templates and inject metadata.
"""
# TODO : add --template-name option to specify the template name
settings = ctx.obj["settings"]

template_dir = settings.FASTKIT_TEMPLATE_ROOT
Expand Down Expand Up @@ -258,25 +262,27 @@ def startdemo(
print_error("Project creation aborted!")
return

# Ask user whether to create a new project folder
create_project_folder = ask_create_project_folder(project_name)

try:
user_local = settings.USER_WORKSPACE
project_dir = os.path.join(user_local, project_name)

click.echo(f"FastAPI template project will deploy at '{user_local}'")

copy_and_convert_template(target_template, user_local, project_name)
project_dir, _ = deploy_template_with_folder_option(
target_template, user_local, project_name, create_project_folder
)

inject_project_metadata(
project_dir, project_name, author, author_email, description
)

# Create virtual environment and install dependencies with selected package manager
venv_path = create_venv_with_manager(project_dir, package_manager)
install_dependencies_with_manager(project_dir, venv_path, package_manager)

print_success(
f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
success_message = get_deployment_success_message(
template, project_name, user_local, create_project_folder
)
print_success(success_message)

except Exception as e:
if settings.DEBUG_MODE:
Expand Down Expand Up @@ -323,8 +329,11 @@ def init(
) -> None:
"""
Start a empty FastAPI project setup.

This command will automatically create a new FastAPI project directory and a python virtual environment.

Dependencies will be automatically installed based on the selected stack at venv.

Project metadata will be injected to the project files.
"""
settings = ctx.obj["settings"]
Expand Down Expand Up @@ -402,13 +411,15 @@ def init(
print_error("Project creation aborted!")
return

# Ask user whether to create a new project folder
create_project_folder = ask_create_project_folder(project_name)

try:
user_local = settings.USER_WORKSPACE
project_dir = os.path.join(user_local, project_name)

click.echo(f"FastAPI project will deploy at '{user_local}'")

copy_and_convert_template(target_template, user_local, project_name)
project_dir, _ = deploy_template_with_folder_option(
target_template, user_local, project_name, create_project_folder
)

inject_project_metadata(
project_dir, project_name, author, author_email, description
Expand Down Expand Up @@ -439,9 +450,10 @@ def init(
venv_path = create_venv_with_manager(project_dir, package_manager)
install_dependencies_with_manager(project_dir, venv_path, package_manager)

print_success(
f"FastAPI project '{project_name}' has been created successfully and saved to {user_local}!"
success_message = get_deployment_success_message(
template, project_name, user_local, create_project_folder
)
print_success(success_message)

print_info(
"To start your project, run 'fastkit runserver' at newly created FastAPI project directory"
Expand All @@ -457,25 +469,43 @@ def init(


@fastkit_cli.command()
@click.argument("project_name")
@click.argument("route_name")
@click.argument("project_dir", default=".")
Comment on lines 472 to +473
Copy link

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project_dir argument should use click.argument with required=False instead of default='.'. Using default with click.argument is not the standard pattern; consider using @click.option instead or handle the default value logic in the function body.

Copilot uses AI. Check for mistakes.
@click.pass_context
def addroute(ctx: Context, project_name: str, route_name: str) -> None:
def addroute(ctx: Context, route_name: str, project_dir: str) -> None:
"""
Add a new route to the FastAPI project.

Examples:\n
fastkit addroute user . # Add 'user' route to current directory
fastkit addroute user my_project # Add 'user' route to 'my_project' in workspace
"""
settings = ctx.obj["settings"]
user_local = settings.USER_WORKSPACE
project_dir = os.path.join(user_local, project_name)

if project_dir == ".":
actual_project_dir = os.getcwd()
project_name = os.path.basename(actual_project_dir)
else:
user_local = settings.USER_WORKSPACE
actual_project_dir = os.path.join(user_local, project_dir)
project_name = project_dir

# Check if project exists
if not os.path.exists(project_dir):
print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
if not os.path.exists(actual_project_dir):
if project_dir == ".":
print_error("Current directory is not a valid project directory.")
else:
print_error(
f"Project '{project_dir}' does not exist in '{settings.USER_WORKSPACE}'."
)
return

# Verify it's a fastkit project
if not is_fastkit_project(project_dir):
print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
if not is_fastkit_project(actual_project_dir):
if project_dir == ".":
print_error("Current directory is not a FastAPI-fastkit project.")
else:
print_error(f"'{project_dir}' is not a FastAPI-fastkit project.")
return

# Validate route name
Expand All @@ -499,26 +529,34 @@ def addroute(ctx: Context, project_name: str, route_name: str) -> None:
{
"Project": project_name,
"Route Name": route_name,
"Target Directory": project_dir,
"Target Directory": actual_project_dir,
},
)
console.print(table)

# Confirm before proceeding
confirm = click.confirm(
f"\nDo you want to add route '{route_name}' to project '{project_name}'?",
default=True,
)
if project_dir == ".":
confirm_message = (
f"\nDo you want to add route '{route_name}' to the current project?"
)
else:
confirm_message = f"\nDo you want to add route '{route_name}' to project '{project_name}'?"

confirm = click.confirm(confirm_message, default=True)
if not confirm:
print_error("Operation cancelled!")
return

# Add the new route
add_new_route(project_dir, route_name)
add_new_route(actual_project_dir, route_name)

print_success(
f"Successfully added new route '{route_name}' to project `{project_name}`"
)
if project_dir == ".":
print_success(
f"Successfully added new route '{route_name}' to the current project!"
)
else:
print_success(
f"Successfully added new route '{route_name}' to project '{project_name}'!"
)

except Exception as e:
logger = get_logger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ httpx==0.28.1
black==25.1.0
isort==6.0.0
mypy==1.15.0
setuptools==80.9.0
Loading
Loading