diff --git a/fastapi/30-fastapi.sh b/fastapi/30-fastapi.sh new file mode 100755 index 0000000..737853c --- /dev/null +++ b/fastapi/30-fastapi.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Install FastAPI and Uvicorn +poetry add 'fastapi==*' +poetry add 'uvicorn[standard]==*' +poetry add --group dev 'httpx==*' + +# Check if the FASTAPI_PROJECT_NAME environment variable is set +if [[ -z "$FASTAPI_PROJECT_NAME" ]]; then + echo "FASTAPI_PROJECT_NAME environment variable is not set. Using the current directory name as the project name." + FASTAPI_PROJECT_NAME=$(basename "$PWD") + # Replace forbidden characters in project name + FASTAPI_PROJECT_NAME=${FASTAPI_PROJECT_NAME//[-.]/_} + export FASTAPI_PROJECT_NAME +fi + +# Create the main application directory +mkdir -p "$FASTAPI_PROJECT_NAME" + +# Create config module +cat > "$FASTAPI_PROJECT_NAME/config.py" < "$FASTAPI_PROJECT_NAME/main.py" < "$FASTAPI_PROJECT_NAME/__init__.py" < "test_app.py" <> "README.md" + +## Running the Application + +Start the development server: + + poetry run uvicorn $FASTAPI_PROJECT_NAME.main:app --reload + +The API will be available at http://localhost:8000 +EOF + +poetry run isort . +git add --all +git commit -m "Initialize FastAPI application" diff --git a/fastapi/31-sqlalchemy.sh b/fastapi/31-sqlalchemy.sh new file mode 100755 index 0000000..c95ff0f --- /dev/null +++ b/fastapi/31-sqlalchemy.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Install SQLAlchemy with async support +poetry add 'sqlalchemy[asyncio]==*' +poetry add 'asyncpg==*' + +# Create database configuration module +cat > "$FASTAPI_PROJECT_NAME/database.py" < "$FASTAPI_PROJECT_NAME/models.py" < "alembic/env.py" < None: + """Run migrations in 'offline' mode.""" + url = get_url() + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection): + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations(): + configuration = config.get_section(config.config_ini_section) + configuration["sqlalchemy.url"] = get_url() + connectable = async_engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() +EOF + +# Add to README +cat <> "README.md" + +## Database Migrations + +Create a new migration: + + poetry run alembic revision --autogenerate -m "Description" + +Apply migrations: + + poetry run alembic upgrade head +EOF + +poetry run isort . +git add --all +git commit -m "Install and configure Alembic for database migrations" diff --git a/fastapi/33-environ.sh b/fastapi/33-environ.sh new file mode 100755 index 0000000..902e08c --- /dev/null +++ b/fastapi/33-environ.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Install pydantic-settings for environment configuration +poetry add 'pydantic-settings==*' + +# Create example.env file +cat < example.env +DEBUG=True +DATABASE_URL=postgresql+asyncpg://localhost/$POETRY_PROJECT_NAME +SECRET_KEY=your_secret_key_here +EOF + +# Add .env to .gitignore +echo ".env" >> .gitignore + +# Add to README +cat <> "README.md" + +## Environment Variables + +Configure quick-start env vars: + + cp example.env .env +EOF + +git add --all +git commit -m "Install pydantic-settings and configure environment" diff --git a/fastapi/34-sentry.sh b/fastapi/34-sentry.sh new file mode 100755 index 0000000..c4856a1 --- /dev/null +++ b/fastapi/34-sentry.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Install Sentry SDK for FastAPI +poetry add 'sentry-sdk[fastapi]==*' + +# Update config to include SENTRY_DSN and traces sample rate +sed -i '/secret_key: str = "changeme"/a\ sentry_dsn: str = ""\n sentry_traces_sample_rate: float = 0.01' "$FASTAPI_PROJECT_NAME/config.py" + +# Add Sentry initialization to main.py +sed -i '1i import sentry_sdk' "$FASTAPI_PROJECT_NAME/main.py" +sed -i '/from fastapi import FastAPI/a\from .config import settings' "$FASTAPI_PROJECT_NAME/main.py" + +# Add Sentry initialization after imports +sed -i '/^from .config import settings/a\ +\ +if settings.sentry_dsn:\ + sentry_sdk.init(\ + dsn=settings.sentry_dsn,\ + traces_sample_rate=settings.sentry_traces_sample_rate,\ + )' "$FASTAPI_PROJECT_NAME/main.py" + +# Add SENTRY_DSN and traces sample rate to example.env +echo "SENTRY_DSN=" >> example.env +echo "SENTRY_TRACES_SAMPLE_RATE=0.01" >> example.env + +poetry run isort . +git add --all +git commit -m "Install and configure Sentry for FastAPI" diff --git a/fastapi/everything.sh b/fastapi/everything.sh new file mode 100755 index 0000000..1c7c94f --- /dev/null +++ b/fastapi/everything.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status +set -o pipefail # Prevent errors in a pipeline from being masked + +# Get the directory path of the current script +script_dir=$(dirname "$0") + +# Execute the initialization scripts +source "$script_dir/../10-git.sh" +source "$script_dir/../20-poetry.sh" +source "$script_dir/../21-flake.sh" +source "$script_dir/../22-isort.sh" +source "$script_dir/../23-pytest.sh" +source "$script_dir/33-environ.sh" +source "$script_dir/30-fastapi.sh" +source "$script_dir/31-sqlalchemy.sh" +source "$script_dir/32-alembic.sh" +source "$script_dir/34-sentry.sh" diff --git a/fastapi/test.sh b/fastapi/test.sh new file mode 100755 index 0000000..9e617a5 --- /dev/null +++ b/fastapi/test.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status +set -o pipefail # Prevent errors in a pipeline from being masked + +# Create a temporary directory +tmp_dir=$(mktemp -d) + +trap 'rm -rf "$tmp_dir"' EXIT + +# Store the current directory +original_dir=$(pwd) + +# Navigate to the temporary directory +pushd "$tmp_dir" + +# Execute the pipeline script from the original directory +"$original_dir/fastapi/everything.sh" + +poetry run flake8 +cp example.env .env +poetry run pytest + +# remove the virtual env, it may be stored outside the temporary directory +poetry env remove --all + +# Navigate back to the original directory +popd + +echo "All good!"