Refactor OpenAPI client script and backend server logic.
Simplify and streamline OpenAPI client generation and backend startup logic. Improved error handling, environment configuration, and self-signed SSL certificate management. Added support for generating OpenAPI schema via command-line argument.
This commit is contained in:
parent
176aaa2867
commit
33e3a2d4df
@ -1,4 +1,3 @@
|
|||||||
# app/main.py
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import tomllib
|
import tomllib
|
||||||
@ -6,8 +5,6 @@ from pathlib import Path
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from app import ssl_heidi
|
from app import ssl_heidi
|
||||||
|
|
||||||
|
|
||||||
from app.routers import (
|
from app.routers import (
|
||||||
address,
|
address,
|
||||||
contact,
|
contact,
|
||||||
@ -25,7 +22,6 @@ from app.database import Base, engine, SessionLocal, load_sample_data, load_slot
|
|||||||
|
|
||||||
# Utility function to fetch metadata from pyproject.toml
|
# Utility function to fetch metadata from pyproject.toml
|
||||||
def get_project_metadata():
|
def get_project_metadata():
|
||||||
# Start from the current script's directory and search for pyproject.toml
|
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
for parent in script_dir.parents:
|
for parent in script_dir.parents:
|
||||||
pyproject_path = parent / "pyproject.toml"
|
pyproject_path = parent / "pyproject.toml"
|
||||||
@ -35,8 +31,6 @@ def get_project_metadata():
|
|||||||
name = pyproject["project"]["name"]
|
name = pyproject["project"]["name"]
|
||||||
version = pyproject["project"]["version"]
|
version = pyproject["project"]["version"]
|
||||||
return name, version
|
return name, version
|
||||||
|
|
||||||
# If no pyproject.toml is found, raise FileNotFoundError
|
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
f"pyproject.toml not found in any parent directory of {script_dir}"
|
f"pyproject.toml not found in any parent directory of {script_dir}"
|
||||||
)
|
)
|
||||||
@ -45,21 +39,24 @@ def get_project_metadata():
|
|||||||
# Get project metadata from pyproject.toml
|
# Get project metadata from pyproject.toml
|
||||||
project_name, project_version = get_project_metadata()
|
project_name, project_version = get_project_metadata()
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title=project_name, # Syncs with project `name`
|
title=project_name,
|
||||||
description="Backend for next-gen sample management system",
|
description="Backend for next-gen sample management system",
|
||||||
version=project_version, # Syncs with project `version`
|
version=project_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Determine environment and configuration file path
|
# Determine environment and configuration file path
|
||||||
environment = os.getenv("ENVIRONMENT", "dev")
|
environment = os.getenv("ENVIRONMENT", "dev")
|
||||||
config_file = Path(__file__).resolve().parent.parent / f"config_{environment}.json"
|
config_file = Path(__file__).resolve().parent.parent / f"config_{environment}.json"
|
||||||
|
|
||||||
|
if not config_file.exists():
|
||||||
|
raise FileNotFoundError(f"Config file '{config_file}' does not exist.")
|
||||||
|
|
||||||
# Load configuration
|
# Load configuration
|
||||||
with open(config_file) as f:
|
with open(config_file) as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
cert_path = config["ssl_cert_path"]
|
cert_path = config.get("ssl_cert_path", "ssl/cert.pem")
|
||||||
key_path = config["ssl_key_path"]
|
key_path = config.get("ssl_key_path", "ssl/key.pem")
|
||||||
|
|
||||||
# Generate SSL Key and Certificate if not exist (only for development)
|
# Generate SSL Key and Certificate if not exist (only for development)
|
||||||
if environment == "dev":
|
if environment == "dev":
|
||||||
@ -70,7 +67,7 @@ if environment == "dev":
|
|||||||
# Apply CORS middleware
|
# Apply CORS middleware
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"], # Enable CORS for all origins
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
@ -118,60 +115,47 @@ app.include_router(spreadsheet.router, tags=["spreadsheet"])
|
|||||||
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])
|
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])
|
||||||
app.include_router(sample.router, prefix="/samples", tags=["samples"])
|
app.include_router(sample.router, prefix="/samples", tags=["samples"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from pathlib import Path
|
|
||||||
from app import ssl_heidi
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from multiprocessing import Process
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Fetch environment values
|
# Check if `generate-openapi` option is passed
|
||||||
environment = os.getenv("ENVIRONMENT", "dev")
|
if len(sys.argv) > 1 and sys.argv[1] == "generate-openapi":
|
||||||
port = int(os.getenv("PORT", 8000)) # Default to 8000 if PORT is not set
|
from fastapi.openapi.utils import get_openapi
|
||||||
is_ci = (
|
|
||||||
os.getenv("CI", "false").lower() == "true"
|
|
||||||
) # Detect if running in CI environment
|
|
||||||
|
|
||||||
# Determine SSL certificate and key paths
|
# Generate and save OpenAPI JSON file
|
||||||
if environment == "prod":
|
openapi_schema = get_openapi(
|
||||||
# Production environment must use proper SSL cert and key paths
|
title=app.title,
|
||||||
cert_path = os.getenv("VITE_SSL_CERT_PATH", "ssl/prod-cert.pem")
|
version=app.version,
|
||||||
key_path = os.getenv("VITE_SSL_KEY_PATH", "ssl/prod-key.pem")
|
description=app.description,
|
||||||
if not Path(cert_path).exists() or not Path(key_path).exists():
|
routes=app.routes,
|
||||||
raise FileNotFoundError(
|
|
||||||
f"Production certificates not found."
|
|
||||||
f"Make sure the following files exist:\n"
|
|
||||||
f"Certificate: {cert_path}\nKey: {key_path}"
|
|
||||||
)
|
|
||||||
host = "0.0.0.0" # Allow external traffic
|
|
||||||
print(
|
|
||||||
f"Running in production mode with provided SSL certificates:\n"
|
|
||||||
f" - Certificate: {cert_path}\n - Key: {key_path}"
|
|
||||||
)
|
)
|
||||||
|
with open("openapi.json", "w") as f:
|
||||||
|
json.dump(openapi_schema, f, indent=4)
|
||||||
|
print("openapi.json generated successfully.")
|
||||||
|
sys.exit(0) # Exit after generating the file
|
||||||
|
|
||||||
elif environment in ["test", "dev"]:
|
# Default behavior: Run the server
|
||||||
# Test/Development environments use self-signed certificates
|
environment = os.getenv("ENVIRONMENT", "dev")
|
||||||
|
port = int(os.getenv("PORT", 8000))
|
||||||
|
is_ci = os.getenv("CI", "false").lower() == "true"
|
||||||
|
|
||||||
|
# Development or Test environment
|
||||||
|
if environment in ["test", "dev"]:
|
||||||
cert_path = "ssl/cert.pem"
|
cert_path = "ssl/cert.pem"
|
||||||
key_path = "ssl/key.pem"
|
key_path = "ssl/key.pem"
|
||||||
host = "127.0.0.1" # Restrict to localhost
|
host = "127.0.0.1"
|
||||||
print(f"Running in {environment} mode with self-signed certificates...")
|
|
||||||
|
|
||||||
# Ensure self-signed certificates exist or generate them
|
|
||||||
Path("ssl").mkdir(parents=True, exist_ok=True)
|
|
||||||
if not Path(cert_path).exists() or not Path(key_path).exists():
|
|
||||||
print(f"Generating self-signed SSL certificate at {cert_path}...")
|
|
||||||
ssl_heidi.generate_self_signed_cert(cert_path, key_path)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
cert_path = os.getenv("VITE_SSL_CERT_PATH", "ssl/prod-cert.pem")
|
||||||
f"Unknown environment: {environment}. "
|
key_path = os.getenv("VITE_SSL_KEY_PATH", "ssl/prod-key.pem")
|
||||||
f"Must be one of 'prod', 'test', or 'dev'."
|
host = "0.0.0.0"
|
||||||
)
|
|
||||||
|
|
||||||
# Function to run the server
|
|
||||||
def run_server():
|
def run_server():
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app,
|
app,
|
||||||
@ -182,18 +166,14 @@ if __name__ == "__main__":
|
|||||||
ssl_certfile=cert_path,
|
ssl_certfile=cert_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Continuous Integration handling
|
# Run in CI mode
|
||||||
if is_ci:
|
if is_ci:
|
||||||
from multiprocessing import Process
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
print("CI mode detected: Starting server in a subprocess...")
|
print("CI mode detected: Starting server in a subprocess...")
|
||||||
server_process = Process(target=run_server)
|
server_process = Process(target=run_server)
|
||||||
server_process.start()
|
server_process.start()
|
||||||
sleep(5) # Wait 5 seconds to ensure the server starts without errors
|
sleep(5)
|
||||||
server_process.terminate() # Terminate the server (test purposes)
|
server_process.terminate()
|
||||||
server_process.join() # Ensure proper cleanup
|
server_process.join()
|
||||||
print("CI: Server started and terminated successfully for test validation.")
|
print("CI: Server started and terminated successfully for test validation.")
|
||||||
else:
|
else:
|
||||||
# Run the server normally
|
|
||||||
run_server()
|
run_server()
|
||||||
|
@ -3,65 +3,55 @@
|
|||||||
# Extract values from pyproject.toml
|
# Extract values from pyproject.toml
|
||||||
PYPROJECT_FILE="$(dirname "$0")/pyproject.toml"
|
PYPROJECT_FILE="$(dirname "$0")/pyproject.toml"
|
||||||
|
|
||||||
# Extract name directly and ignore newlines
|
NAME=$(awk -F'= ' '/^name/ { print $2 }' "$PYPROJECT_FILE" | tr -d '"')
|
||||||
NAME=$(awk -F'=' '/^name/ { gsub(/"/, "", $2); print $2 }' "$PYPROJECT_FILE" | xargs)
|
VERSION=$(awk -F'= ' '/^version/ { print $2 }' "$PYPROJECT_FILE" | tr -d '"')
|
||||||
VERSION=$(awk -F'=' '/^version/ { gsub(/"/, "", $2); print $2 }' "$PYPROJECT_FILE" | xargs)
|
|
||||||
|
|
||||||
if [[ -z "$VERSION" || -z "$NAME" ]]; then
|
if [[ -z "$NAME" || -z "$VERSION" ]]; then
|
||||||
echo "Error: Could not determine version or name from pyproject.toml"
|
echo "Error: Unable to extract name or version from pyproject.toml."
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure the extracted name is valid (No spaces or unexpected characters)
|
|
||||||
if ! [[ "$NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
||||||
echo "Error: Invalid project name detected: '$NAME'"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using project name: $NAME"
|
echo "Using project name: $NAME"
|
||||||
echo "Using version: $VERSION"
|
echo "Using version: $VERSION"
|
||||||
|
|
||||||
# Navigate to project root
|
# Navigate to backend directory
|
||||||
cd "$(dirname "$0")/backend" || exit
|
cd "$(dirname "$0")/backend" || exit
|
||||||
|
|
||||||
# Generate OpenAPI JSON file
|
# Generate OpenAPI JSON file
|
||||||
python3 main.py generate-openapi
|
echo "Generating OpenAPI JSON..."
|
||||||
|
python3 -m main generate-openapi
|
||||||
if [[ ! -f openapi.json ]]; then
|
if [[ ! -f openapi.json ]]; then
|
||||||
echo "Error: openapi.json file not generated!"
|
echo "Error: Failed to generate openapi.json!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download OpenAPI generator CLI if not present
|
# Download OpenAPI Generator CLI
|
||||||
OPENAPI_VERSION=7.8.0
|
OPENAPI_VERSION="7.8.0"
|
||||||
if [[ ! -f openapi-generator-cli.jar ]]; then
|
if [[ ! -f openapi-generator-cli.jar ]]; then
|
||||||
wget "https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${OPENAPI_VERSION}/openapi-generator-cli-${OPENAPI_VERSION}.jar" -O openapi-generator-cli.jar
|
wget "https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${OPENAPI_VERSION}/openapi-generator-cli-${OPENAPI_VERSION}.jar" -O openapi-generator-cli.jar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run OpenAPI generator with synced name and version
|
# Generate client
|
||||||
java -jar openapi-generator-cli.jar generate \
|
java -jar openapi-generator-cli.jar generate \
|
||||||
-i "$(pwd)/openapi.json" \
|
-i openapi.json \
|
||||||
-o python-client/ \
|
-o python-client/ \
|
||||||
-g python \
|
-g python \
|
||||||
--additional-properties=packageName="${NAME}client",projectName="${NAME}",packageVersion="${VERSION}" \
|
--additional-properties=packageName="${NAME}_client",projectName="${NAME}",packageVersion="${VERSION}"
|
||||||
--additional-properties=authorName="Guillaume Gotthard",authorEmail="guillaume.gotthard@psi.ch"
|
|
||||||
|
|
||||||
# Check if the generator succeeded
|
|
||||||
if [[ ! -d python-client ]]; then
|
if [[ ! -d python-client ]]; then
|
||||||
echo "OpenAPI generator failed. Exiting."
|
echo "Error: Failed to generate Python client."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build Python package
|
# Build the package
|
||||||
echo "Building Python package..."
|
|
||||||
cd python-client || exit
|
cd python-client || exit
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -U pip build
|
pip install -U pip build
|
||||||
python -m build
|
python3 -m build
|
||||||
|
|
||||||
# Verify build output
|
|
||||||
if [[ ! -d dist || -z "$(ls -A dist)" ]]; then
|
if [[ ! -d dist || -z "$(ls -A dist)" ]]; then
|
||||||
echo "Error: No files generated in 'dist/'. Package build failed."
|
echo "Error: Failed to build Python package."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user