diff --git a/.github/workflows/template-security-scan.yml b/.github/workflows/template-security-scan.yml new file mode 100644 index 0000000..821f5c9 --- /dev/null +++ b/.github/workflows/template-security-scan.yml @@ -0,0 +1,184 @@ +name: Weekly Template Security Scan + +on: + schedule: + - cron: "0 23 * * 0" + workflow_dispatch: + inputs: + templates: + description: "Specific templates to scan (comma-separated, leave empty for all)" + required: false + default: "" + +permissions: + contents: read + issues: write + +jobs: + security-scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install pip-audit + run: pip install pip-audit + + - name: Run security scan on templates + id: scan + run: | + TEMPLATE_DIR="src/fastapi_fastkit/fastapi_project_template" + RESULTS_FILE="security_scan_results.json" + SCAN_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + # Initialize results + echo '{' > $RESULTS_FILE + echo ' "scan_date": "'$SCAN_DATE'",' >> $RESULTS_FILE + echo ' "templates": [' >> $RESULTS_FILE + + TEMPLATES_INPUT="${{ github.event.inputs.templates }}" + FIRST_TEMPLATE=true + TOTAL_VULNERABILITIES=0 + AFFECTED_TEMPLATES="" + + for template_dir in $TEMPLATE_DIR/fastapi-*/; do + template_name=$(basename "$template_dir") + + # Skip if specific templates are requested and this isn't one + if [ -n "$TEMPLATES_INPUT" ]; then + if ! echo "$TEMPLATES_INPUT" | grep -q "$template_name"; then + continue + fi + fi + + req_file="$template_dir/requirements.txt-tpl" + if [ -f "$req_file" ]; then + echo "๐Ÿ” Scanning $template_name..." + + # Create temp requirements file + temp_req=$(mktemp) + cp "$req_file" "$temp_req" + + # Run pip-audit and capture output + audit_output=$(pip-audit -r "$temp_req" --format json 2>/dev/null || echo '[]') + rm "$temp_req" + + # Count vulnerabilities + vuln_count=$(echo "$audit_output" | python3 -c "import sys, json; data = json.load(sys.stdin); print(len(data))" 2>/dev/null || echo "0") + + if [ "$vuln_count" -gt 0 ]; then + TOTAL_VULNERABILITIES=$((TOTAL_VULNERABILITIES + vuln_count)) + AFFECTED_TEMPLATES="$AFFECTED_TEMPLATES $template_name" + echo "โš ๏ธ Found $vuln_count vulnerabilities in $template_name" + else + echo "โœ… No vulnerabilities in $template_name" + fi + + # Add to JSON + if [ "$FIRST_TEMPLATE" = true ]; then + FIRST_TEMPLATE=false + else + echo ' ,' >> $RESULTS_FILE + fi + + echo ' {' >> $RESULTS_FILE + echo ' "name": "'$template_name'",' >> $RESULTS_FILE + echo ' "vulnerability_count": '$vuln_count',' >> $RESULTS_FILE + echo ' "vulnerabilities": '$audit_output >> $RESULTS_FILE + echo ' }' >> $RESULTS_FILE + fi + done + + echo ' ],' >> $RESULTS_FILE + echo ' "total_vulnerabilities": '$TOTAL_VULNERABILITIES',' >> $RESULTS_FILE + echo ' "affected_templates": "'$(echo $AFFECTED_TEMPLATES | xargs)'"' >> $RESULTS_FILE + echo '}' >> $RESULTS_FILE + + # Set outputs for later steps + echo "total_vulnerabilities=$TOTAL_VULNERABILITIES" >> $GITHUB_OUTPUT + echo "affected_templates=$AFFECTED_TEMPLATES" >> $GITHUB_OUTPUT + + - name: Upload scan results + uses: actions/upload-artifact@v4 + if: always() + with: + name: security-scan-results + path: security_scan_results.json + retention-days: 30 + + - name: Create Issue on Vulnerabilities + if: steps.scan.outputs.total_vulnerabilities != '0' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + let issueBody = `## ๐Ÿ”’ Template Dependency Security Scan Results\n\n`; + issueBody += `**Scan Date:** ${new Date().toISOString()}\n`; + issueBody += `**Total Vulnerabilities Found:** ${{ steps.scan.outputs.total_vulnerabilities }}\n\n`; + + try { + const results = JSON.parse(fs.readFileSync('security_scan_results.json', 'utf8')); + + results.templates.forEach(template => { + if (template.vulnerability_count > 0) { + issueBody += `### โš ๏ธ ${template.name}\n\n`; + issueBody += `Found **${template.vulnerability_count}** vulnerabilities:\n\n`; + issueBody += `| Package | Installed | Fix Versions | Vulnerability ID |\n`; + issueBody += `|---------|-----------|--------------|------------------|\n`; + + template.vulnerabilities.forEach(vuln => { + const fixVersions = vuln.fix_versions ? vuln.fix_versions.join(', ') : 'N/A'; + issueBody += `| ${vuln.name} | ${vuln.version} | ${fixVersions} | ${vuln.id} |\n`; + }); + + issueBody += `\n`; + } + }); + + } catch (error) { + issueBody += `\nError reading detailed results: ${error.message}\n`; + } + + issueBody += `\n---\n**Workflow:** [${context.workflow}](${context.payload.repository.html_url}/actions/runs/${context.runId})`; + + // Determine severity label + const totalVulns = parseInt('${{ steps.scan.outputs.total_vulnerabilities }}'); + let severityLabel = 'security: low'; + if (totalVulns >= 10) { + severityLabel = 'security: critical'; + } else if (totalVulns >= 5) { + severityLabel = 'security: high'; + } else if (totalVulns >= 2) { + severityLabel = 'security: medium'; + } + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `๐Ÿ”’ Template Security Alert: ${totalVulns} vulnerabilities found - ${new Date().toISOString().split('T')[0]}`, + body: issueBody, + labels: ['security', severityLabel, 'automated', 'template'] + }); + + - name: Report Summary + if: always() + run: | + echo "## ๐Ÿ”’ Security Scan Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.scan.outputs.total_vulnerabilities }}" = "0" ]; then + echo "โœ… **No vulnerabilities found in any template!**" >> $GITHUB_STEP_SUMMARY + else + echo "โš ๏ธ **Found ${{ steps.scan.outputs.total_vulnerabilities }} vulnerabilities**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Affected templates: ${{ steps.scan.outputs.affected_templates }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "A GitHub Issue has been created with detailed information." >> $GITHUB_STEP_SUMMARY + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5130688..1228c85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,6 +99,7 @@ Key commands for contributors: - `make test` - Run all tests - `make test-verbose` - Run tests with verbose output - `make test-coverage` - Run tests with coverage report +- `make coverage-report` - Generate detailed coverage report (supports FORMAT=html/xml/json/all) #### Installation and Building - `make install-test` - Install package for testing (uninstall + reinstall) @@ -109,6 +110,9 @@ Key commands for contributors: - `make build-docs` - Build documentation - `make serve-docs` - Serve documentation locally +#### Translation +- `make translate` - Translate documentation (supports LANG, PROVIDER, MODEL parameters) + ### Development Workflow 1. **Before making changes:** @@ -170,6 +174,13 @@ Run tests using these commands: make test-coverage ``` +4. **Detailed coverage report with options:** + ```bash + make coverage-report # Terminal output + make coverage-report FORMAT=html # HTML report (opens in browser) + make coverage-report FORMAT=all # All formats (term, html, xml, json) + ``` + ### Making PRs Use these tags in PR title: @@ -423,7 +434,21 @@ This migrates existing English docs to `docs/en/` and creates language directori ### Translating Documentation -#### Translate to Specific Language +#### Using Make Commands (Recommended) + +```bash +# Translate all docs to all configured languages +make translate + +# Translate to specific language +make translate LANG=ko + +# Specify API provider and model +make translate LANG=ko PROVIDER=github MODEL=gpt-4o-mini +make translate LANG=ko PROVIDER=openai MODEL=gpt-4 +``` + +#### Using Script Directly ```bash # Translate all docs to Korean diff --git a/Makefile b/Makefile index cbcc1af..3970ebc 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install install-dev install-test uninstall test test-verbose lint format format-check clean build build-docs serve-docs version-update all +.PHONY: help install install-dev install-test uninstall test test-verbose lint format format-check clean build build-docs serve-docs version-update all translate coverage-report # Default target help: ## Show this help message @@ -137,3 +137,21 @@ all: ## Run complete development workflow $(MAKE) dev-setup $(MAKE) dev-check $(MAKE) build + +# Translation commands +translate: ## Translate documentation - Usage: make translate LANG=ko PROVIDER=github MODEL=gpt-4o-mini + @echo "Starting translation..." + @CMD="python scripts/translate.py"; \ + if [ -n "$(LANG)" ]; then CMD="$$CMD --target-lang $(LANG)"; fi; \ + if [ -n "$(PROVIDER)" ]; then CMD="$$CMD --api-provider $(PROVIDER)"; fi; \ + if [ -n "$(MODEL)" ]; then CMD="$$CMD --model $(MODEL)"; fi; \ + echo "Running: $$CMD"; \ + $$CMD + +# Coverage report commands +coverage-report: ## Generate detailed coverage report - Usage: make coverage-report FORMAT=html + @if [ -z "$(FORMAT)" ]; then \ + ./scripts/coverage-report.sh; \ + else \ + ./scripts/coverage-report.sh -f "$(FORMAT)"; \ + fi diff --git a/docs/en/contributing/development-setup.md b/docs/en/contributing/development-setup.md index 4e03306..ec02e86 100644 --- a/docs/en/contributing/development-setup.md +++ b/docs/en/contributing/development-setup.md @@ -191,6 +191,7 @@ The project Makefile provides convenient commands for common development tasks: | `make test-unit` | Run unit tests only | | `make test-integration` | Run integration tests only | | `make test-coverage` | Run tests with coverage report | +| `make coverage-report` | Generate detailed coverage report (FORMAT=html/xml/json/all) | | `make test-watch` | Run tests in watch mode | ### Documentation Commands @@ -201,6 +202,12 @@ The project Makefile provides convenient commands for common development tasks: | `make docs-build` | Build documentation | | `make docs-deploy` | Deploy documentation to GitHub Pages | +### Translation Commands + +| Command | Description | +|---------|-----------| +| `make translate` | Translate documentation (LANG, PROVIDER, MODEL parameters) | + ### Examples
@@ -231,6 +238,15 @@ src/cli.py 89 5 94% src/templates.py 67 3 96% -------------------------------------------- TOTAL 201 10 95% + +# Generate HTML coverage report +$ make coverage-report FORMAT=html +๐ŸŒ Opening HTML coverage report in browser... + +# Translate documentation to Korean +$ make translate LANG=ko PROVIDER=github MODEL=gpt-4o-mini +Starting translation... +Running: python scripts/translate.py --target-lang ko --api-provider github --model gpt-4o-mini ```
diff --git a/docs/en/contributing/translation-guide.md b/docs/en/contributing/translation-guide.md index 699cbad..872c664 100644 --- a/docs/en/contributing/translation-guide.md +++ b/docs/en/contributing/translation-guide.md @@ -66,7 +66,25 @@ gh auth login ## Usage -### Translate All Documentation +### Using Make Commands (Recommended) + +The easiest way to run translations: + +```bash +# Translate all docs to all languages +make translate + +# Translate to specific language +make translate LANG=ko + +# Specify API provider and model +make translate LANG=ko PROVIDER=openai MODEL=gpt-4 +make translate LANG=ko PROVIDER=github MODEL=gpt-4o-mini +``` + +### Using Script Directly + +#### Translate All Documentation Translate all documentation to all supported languages: diff --git a/docs/ko/changelog.md b/docs/ko/changelog.md new file mode 100644 index 0000000..f4d16fd --- /dev/null +++ b/docs/ko/changelog.md @@ -0,0 +1 @@ +{!CHANGELOG.md!} diff --git a/docs/ko/index.md b/docs/ko/index.md new file mode 100644 index 0000000..793d142 --- /dev/null +++ b/docs/ko/index.md @@ -0,0 +1,550 @@ +

+ FastAPI-fastkit +

+

+FastAPI-fastkit: Python๊ณผ FastAPI ์‹ ๊ทœ ์‚ฌ์šฉ์ž์šฉ ๋น ๋ฅด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ์Šคํƒ€ํ„ฐ ํ‚คํŠธ +

+

+ + PyPI - Version + + + GitHub Release + + + PyPI Downloads + +

+ +--- + +์ด ํ”„๋กœ์ ํŠธ๋Š” Python๊ณผ [FastAPI](https://github.com/fastapi/fastapi) ์‹ ๊ทœ ์‚ฌ์šฉ์ž๊ฐ€ Python ๊ธฐ๋ฐ˜ ์›น ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ์„ ๋”์šฑ ๋น ๋ฅด๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด ํ”„๋กœ์ ํŠธ๋Š” `SpringBoot initializer` ๋ฐ Python Django์˜ `django-admin` CLI ๋™์ž‘์—์„œ ์˜๊ฐ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. + +## ์ฃผ์š” ๊ธฐ๋Šฅ + +- **โšก Immediate FastAPI project creation** : [Python Django](https://github.com/django/django)์˜ `django-admin` ๊ธฐ๋Šฅ์—์„œ ์˜๊ฐ์„ ๋ฐ›์€ CLI๋ฅผ ํ†ตํ•ด ์ดˆ๊ณ ์† FastAPI ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋ฐ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ +- **โœจ ๋Œ€ํ™”ํ˜• ํ”„๋กœ์ ํŠธ ๋นŒ๋”**: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์ธ์ฆ, ์บ์‹ฑ, ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ ๊ธฐ๋Šฅ์„ ๋‹จ๊ณ„๋ณ„๋กœ ์•ˆ๋‚ดํ•˜์—ฌ ์„ ํƒํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ž๋™ ์ƒ์„ฑ +- **๐ŸŽจ Prettier CLI outputs** : [rich library](https://github.com/Textualize/rich) ๊ธฐ๋ฐ˜์˜ ์•„๋ฆ„๋‹ค์šด CLI ๊ฒฝํ—˜ +- **๐Ÿ“‹ Standards-based FastAPI project templates** : ๋ชจ๋“  FastAPI-fastkit ํ…œํ”Œ๋ฆฟ์€ Python ํ‘œ์ค€๊ณผ FastAPI์˜ ์ผ๋ฐ˜์  ์‚ฌ์šฉ ํŒจํ„ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑ +- **๐Ÿ” Automated template quality assurance** : ์ฃผ๊ฐ„ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋กœ ๋ชจ๋“  ํ…œํ”Œ๋ฆฟ์ด ์ •์ƒ ๋™์ž‘ํ•˜๋ฉฐ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€๋จ์„ ๋ณด์žฅ +- **๐Ÿš€ Multiple project templates** : ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋งž์ถ˜ ์‚ฌ์ „ ๊ตฌ์„ฑ ํ…œํ”Œ๋ฆฟ ์„ ํƒ ๊ฐ€๋Šฅ(async CRUD, Docker, PostgreSQL ๋“ฑ) +- **๐Ÿ“ฆ Multiple package manager support** : ์˜์กด์„ฑ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์„ ํ˜ธํ•˜๋Š” Python ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์ž(pip, uv, pdm, poetry)๋ฅผ ์„ ํƒ ๊ฐ€๋Šฅ + +## ์„ค์น˜ + +Python ํ™˜๊ฒฝ์— `FastAPI-fastkit`๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”. + +
+ +```console +$ pip install FastAPI-fastkit +---> 100% +``` + +
+ + +## ์‚ฌ์šฉ๋ฒ• + +### FastAPI ํ”„๋กœ์ ํŠธ ์›Œํฌ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ์„ ์ฆ‰์‹œ ์ƒ์„ฑ + +์ด์ œ FastAPI-fastkit์œผ๋กœ ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์ƒˆ FastAPI ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! + +๋‹ค์Œ์œผ๋กœ ์ฆ‰์‹œ ์ƒˆ FastAPI ํ”„๋กœ์ ํŠธ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”: + +
+ +```console +$ fastkit init +Enter the project name: my-awesome-project +Enter the author name: John Doe +Enter the author email: john@example.com +Enter the project description: My awesome FastAPI project + + Project Information +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Project Name โ”‚ my-awesome-project โ”‚ +โ”‚ Author โ”‚ John Doe โ”‚ +โ”‚ Author Email โ”‚ john@example.com โ”‚ +โ”‚ Description โ”‚ My awesome FastAPI project โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +Available Stacks and Dependencies: + MINIMAL Stack +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Dependency 1 โ”‚ fastapi โ”‚ +โ”‚ Dependency 2 โ”‚ uvicorn โ”‚ +โ”‚ Dependency 3 โ”‚ pydantic โ”‚ +โ”‚ Dependency 4 โ”‚ pydantic-settings โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + STANDARD Stack +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Dependency 1 โ”‚ fastapi โ”‚ +โ”‚ Dependency 2 โ”‚ uvicorn โ”‚ +โ”‚ Dependency 3 โ”‚ sqlalchemy โ”‚ +โ”‚ Dependency 4 โ”‚ alembic โ”‚ +โ”‚ Dependency 5 โ”‚ pytest โ”‚ +โ”‚ Dependency 6 โ”‚ pydantic โ”‚ +โ”‚ Dependency 7 โ”‚ pydantic-settings โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + FULL Stack +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Dependency 1 โ”‚ fastapi โ”‚ +โ”‚ Dependency 2 โ”‚ uvicorn โ”‚ +โ”‚ Dependency 3 โ”‚ sqlalchemy โ”‚ +โ”‚ Dependency 4 โ”‚ alembic โ”‚ +โ”‚ Dependency 5 โ”‚ pytest โ”‚ +โ”‚ Dependency 6 โ”‚ redis โ”‚ +โ”‚ Dependency 7 โ”‚ celery โ”‚ +โ”‚ Dependency 8 โ”‚ pydantic โ”‚ +โ”‚ Dependency 9 โ”‚ pydantic-settings โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +Select stack (minimal, standard, full): minimal + +Available Package Managers: + Package Managers +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PIP โ”‚ Standard Python package manager โ”‚ +โ”‚ UV โ”‚ Fast Python package manager โ”‚ +โ”‚ PDM โ”‚ Modern Python dependency management โ”‚ +โ”‚ POETRY โ”‚ Python dependency management and packaging โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +Select package manager (pip, uv, pdm, poetry) [uv]: uv +Do you want to proceed with project creation? [y/N]: y +FastAPI project will deploy at '~your-project-path~' + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ„น Injected metadata into setup.py โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ„น Injected metadata into config file โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + + Creating Project: + my-awesome-project +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Component โ”‚ Collected โ”‚ +โ”‚ fastapi โ”‚ โœ“ โ”‚ +โ”‚ uvicorn โ”‚ โœ“ โ”‚ +โ”‚ pydantic โ”‚ โœ“ โ”‚ +โ”‚ pydantic-settings โ”‚ โœ“ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +Creating virtual environment... + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ„น venv created at โ”‚ +โ”‚ ~your-project-path~/my-awesome-project/.venv โ”‚ +โ”‚ To activate the virtual environment, run: โ”‚ +โ”‚ โ”‚ +โ”‚ source โ”‚ +โ”‚ ~your-project-path~/my-awesome-project/.venv/bin/act โ”‚ +โ”‚ ivate โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +Installing dependencies... +โ ™ Setting up project environment...Collecting + +---> 100% + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Success โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ Dependencies installed successfully โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Success โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ FastAPI project 'my-awesome-project' has been โ”‚ +โ”‚ created successfully and saved to โ”‚ +โ”‚ ~your-project-path~! โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ„น To start your project, run 'fastkit runserver' at โ”‚ +โ”‚ newly created FastAPI project directory โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` + +
+ +์ด ๋ช…๋ น์€ Python ๊ฐ€์ƒ ํ™˜๊ฒฝ๊ณผ ํ•จ๊ป˜ ์ƒˆ๋กœ์šด FastAPI ํ”„๋กœ์ ํŠธ ์›Œํฌ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +### ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ๋กœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ โœจ NEW! + +๋ณด๋‹ค ๋ณต์žกํ•œ ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ์šฐ, ์ง€๋Šฅํ˜• ๊ธฐ๋Šฅ ์„ ํƒ์„ ํ†ตํ•ด ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ๋กœ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌ์„ฑํ•˜์„ธ์š”: + +
+ +```console +$ fastkit init --interactive + +โšก FastAPI-fastkit Interactive Project Setup โšก + +๐Ÿ“‹ Basic Project Information +Enter the project name: my-fullstack-project +Enter the author name: John Doe +Enter the author email: john@example.com +Enter the project description: Full-stack FastAPI project with PostgreSQL and JWT + +๐Ÿ—„๏ธ Database Selection +Select database (PostgreSQL, MySQL, MongoDB, Redis, SQLite, None): + 1. PostgreSQL - PostgreSQL database with SQLAlchemy + 2. MySQL - MySQL database with SQLAlchemy + 3. MongoDB - MongoDB with motor async driver + 4. Redis - Redis for caching and session storage + 5. SQLite - SQLite database for development + 6. None - No database + +Select database: 1 + +๐Ÿ” Authentication Selection +Select authentication (JWT, OAuth2, FastAPI-Users, Session-based, None): + 1. JWT - JSON Web Token authentication + 2. OAuth2 - OAuth2 with password flow + 3. FastAPI-Users - Full featured user management + 4. Session-based - Cookie-based sessions + 5. None - No authentication + +Select authentication: 1 + +โš™๏ธ Background Tasks Selection +Select background tasks (Celery, Dramatiq, None): + 1. Celery - Distributed task queue + 2. Dramatiq - Fast and reliable task processing + 3. None - No background tasks + +Select background tasks: 1 + +๐Ÿ’พ Caching Selection +Select caching (Redis, fastapi-cache2, None): + 1. Redis - Redis caching + 2. fastapi-cache2 - Simple caching for FastAPI + 3. None - No caching + +Select caching: 1 + +๐Ÿ“Š Monitoring Selection +Select monitoring (Loguru, OpenTelemetry, Prometheus, None): + 1. Loguru - Simple and powerful logging + 2. OpenTelemetry - Observability framework + 3. Prometheus - Metrics and monitoring + 4. None - No monitoring + +Select monitoring: 3 +``` + +๐Ÿงช ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์„ ํƒ +ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š” (Basic, Coverage, Advanced, None): + 1. Basic - API ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ pytest + httpx + 2. Coverage - Basic + ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€ + 3. Advanced - Coverage + ํ”ฝ์Šค์ฒ˜๋ฅผ ์œ„ํ•œ faker + factory-boy + 4. None - ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์—†์Œ + +ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์„ ํƒ: 2 + +๐Ÿ› ๏ธ ์ถ”๊ฐ€ ์œ ํ‹ธ๋ฆฌํ‹ฐ +์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š” (์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ˆซ์ž, ์˜ˆ: 1,3,4): + 1. CORS - Cross-Origin Resource Sharing + 2. Rate-Limiting - ์š”์ฒญ ์†๋„ ์ œํ•œ + 3. Pagination - ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ง€์› + 4. WebSocket - WebSocket ์ง€์› + +์œ ํ‹ธ๋ฆฌํ‹ฐ ์„ ํƒ: 1 + +๐Ÿš€ ๋ฐฐํฌ ๊ตฌ์„ฑ +๋ฐฐํฌ ์˜ต์…˜์„ ์„ ํƒํ•˜์„ธ์š”: + 1. Docker - Dockerfile ์ƒ์„ฑ + 2. docker-compose - docker-compose.yml ์ƒ์„ฑ(Docker ํฌํ•จ) + 3. None - ๋ฐฐํฌ ๊ตฌ์„ฑ ์—†์Œ + +๋ฐฐํฌ ์˜ต์…˜ ์„ ํƒ: 2 + +๐Ÿ“ฆ ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ์„ ํƒ +ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋ฅผ ์„ ํƒํ•˜์„ธ์š” (pip, uv, pdm, poetry): uv + +๐Ÿ“ ์‚ฌ์šฉ์ž ์ •์˜ ํŒจํ‚ค์ง€(์„ ํƒ ์‚ฌํ•ญ) +์‚ฌ์šฉ์ž ์ •์˜ ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”(์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„, ๊ฑด๋„ˆ๋›ฐ๋ ค๋ฉด Enter): + +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ ํƒ๋œ ๊ตฌ์„ฑ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ํ”„๋กœ์ ํŠธ: my-fullstack-project +๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: PostgreSQL +์ธ์ฆ: JWT +๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…: Celery +์บ์‹ฑ: Redis +๋ชจ๋‹ˆํ„ฐ๋ง: Prometheus +ํ…Œ์ŠคํŠธ: Coverage +์œ ํ‹ธ๋ฆฌํ‹ฐ: CORS +๋ฐฐํฌ: Docker, docker-compose +ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €: uv +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์„ ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? [Y/n]: y + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ์„ ํƒํ•œ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ main.py ์ƒ์„ฑ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์„ฑ ์ƒ์„ฑ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ์ธ์ฆ ๊ตฌ์„ฑ ์ƒ์„ฑ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ ์ƒ์„ฑ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ Docker ๋ฐฐํฌ ํŒŒ์ผ ์ƒ์„ฑ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•˜๋Š” ์ค‘... +์ข…์†์„ฑ์„ ์„ค์น˜ํ•˜๋Š” ์ค‘... + +----> 100% + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ FastAPI ํ”„๋กœ์ ํŠธ 'my-fullstack-project'๊ฐ€ โ”‚ +โ”‚ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! โ”‚ +โ”‚ โ”‚ +โ”‚ ์ƒ์„ฑ๋œ ํŒŒ์ผ: โ”‚ +โ”‚ โ€ข main.py (์„ ํƒํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ ํฌํ•จ) โ”‚ +โ”‚ โ€ข src/config/database.py โ”‚ +โ”‚ โ€ข src/config/auth.py โ”‚ +โ”‚ โ€ข tests/conftest.py โ”‚ +โ”‚ โ€ข Dockerfile โ”‚ +โ”‚ โ€ข docker-compose.yml โ”‚ +โ”‚ โ€ข pyproject.toml / requirements.txt โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` + +
+ +๋Œ€ํ™”ํ˜• ๋ชจ๋“œ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ: +- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์ธ์ฆ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…, ์บ์‹ฑ, ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œํ˜• ์„ ํƒ +- ์„ ํƒํ•œ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ž๋™ ์ฝ”๋“œ ์ƒ์„ฑ(main.py, ๊ตฌ์„ฑ ํŒŒ์ผ, Docker ํŒŒ์ผ) +- ์ž๋™ pip ํ˜ธํ™˜์„ฑ์˜ ์Šค๋งˆํŠธ ์ข…์†์„ฑ ๊ด€๋ฆฌ +- ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ์กฐํ•ฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ธฐ๋Šฅ ๊ฒ€์ฆ +- ์ตœ๋Œ€ ์œ ์—ฐ์„ฑ์„ ์œ„ํ•œ Always Empty project๋ฅผ ๊ธฐ๋ณธ ๋ฒ ์ด์Šค๋กœ ์ œ๊ณต + +### FastAPI ํ”„๋กœ์ ํŠธ์— ์ƒˆ ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ + +`FastAPI-fastkit`์€ FastAPI ํ”„๋กœ์ ํŠธ ํ™•์žฅ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. + +๋‹ค์Œ์œผ๋กœ FastAPI ํ”„๋กœ์ ํŠธ์— ์ƒˆ ๋ผ์šฐํŠธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”: + +
+ +```console +$ fastkit addroute my-awesome-project user + ์ƒˆ ๋ผ์šฐํŠธ ์ถ”๊ฐ€ ์ค‘ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ํ”„๋กœ์ ํŠธ โ”‚ my-awesome-project โ”‚ +โ”‚ ๋ผ์šฐํŠธ ์ด๋ฆ„ โ”‚ user โ”‚ +โ”‚ ๋Œ€์ƒ ๋””๋ ‰ํ„ฐ๋ฆฌ โ”‚ ~your-project-path~ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +ํ”„๋กœ์ ํŠธ 'my-awesome-project'์— ๋ผ์šฐํŠธ 'user'๋ฅผ ์ถ”๊ฐ€ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? [Y/n]: y + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ •๋ณด โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ„น API ๋ผ์šฐํ„ฐ๋ฅผ ํฌํ•จํ•˜๋„๋ก main.py๋ฅผ ์—…๋ฐ์ดํŠธํ–ˆ์Šต๋‹ˆ๋‹ค โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ์ƒˆ ๋ผ์šฐํŠธ 'user'๋ฅผ ํ”„๋กœ์ ํŠธ์— ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค โ”‚ +โ”‚ `my-awesome-project` โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` + +
+ +### ๊ตฌ์กฐํ™”๋œ FastAPI ๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐ”๋กœ ๋ฐฐ์น˜ํ•˜๊ธฐ + +๊ตฌ์กฐํ™”๋œ FastAPI ๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ  ์Šคํƒ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ ๊ฐ„๋‹จํ•œ ์•„์ดํ…œ CRUD ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ๊ตฌ์กฐํ™”๋œ FastAPI ๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฆ‰์‹œ ๋ฐฐ์น˜ํ•˜์„ธ์š”: + +
+ +```console +$ fastkit startdemo +ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”: my-awesome-demo +์ž‘์„ฑ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”: John Doe +์ž‘์„ฑ์ž ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”: john@example.com +ํ”„๋กœ์ ํŠธ ์„ค๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”: My awesome FastAPI demo +'fastapi-default' ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜์—ฌ FastAPI ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค +ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ: +/~fastapi_fastkit-package-path~/fastapi_project_template/fastapi-default + + ํ”„๋กœ์ ํŠธ ์ •๋ณด +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„โ”‚ my-awesome-demo โ”‚ +โ”‚ ์ž‘์„ฑ์ž โ”‚ John Doe โ”‚ +โ”‚ ์ž‘์„ฑ์ž ์ด๋ฉ”์ผโ”‚ john@example.com โ”‚ +โ”‚ ์„ค๋ช… โ”‚ My awesome FastAPI demo โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + ํ…œํ”Œ๋ฆฟ ์ข…์†์„ฑ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ์ข…์†์„ฑ 1 โ”‚ fastapi โ”‚ +โ”‚ ์ข…์†์„ฑ 2 โ”‚ uvicorn โ”‚ +โ”‚ ์ข…์†์„ฑ 3 โ”‚ pydantic โ”‚ +โ”‚ ์ข…์†์„ฑ 4 โ”‚ pydantic-settings โ”‚ +โ”‚ ์ข…์†์„ฑ 5 โ”‚ python-dotenv โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €: + ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PIP โ”‚ ํ‘œ์ค€ Python ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์ž โ”‚ +โ”‚ UV โ”‚ ๋น ๋ฅธ Python ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์ž โ”‚ +โ”‚ PDM โ”‚ ํ˜„๋Œ€์ ์ธ Python ์ข…์†์„ฑ ๊ด€๋ฆฌ โ”‚ +โ”‚ POETRY โ”‚ Python ์ข…์†์„ฑ ๊ด€๋ฆฌ ๋ฐ ํŒจํ‚ค์ง• โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋ฅผ ์„ ํƒํ•˜์„ธ์š” (pip, uv, pdm, poetry) [uv]: uv +ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์„ ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? [y/N]: y +FastAPI ํ…œํ”Œ๋ฆฟ ํ”„๋กœ์ ํŠธ๊ฐ€ '~your-project-path~'์— ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค + +---> 100% + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ ์ข…์†์„ฑ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์น˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ฑ๊ณต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โœจ FastAPI ํ”„๋กœ์ ํŠธ 'my-awesome-demo'๊ฐ€ โ”‚ +โ”‚ 'fastapi-default'์—์„œ ์ƒ์„ฑ๋˜์–ด ๋‹ค์Œ ์œ„์น˜์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค โ”‚ +โ”‚ ~your-project-path~! โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` + +
+ +์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ FastAPI ๋ฐ๋ชจ ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”: + +
+ +```console +$ fastkit list-templates + ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ…œํ”Œ๋ฆฟ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ fastapi-custom-response โ”‚ ์ปค์Šคํ…€ ์‘๋‹ต ์‹œ์Šคํ…œ์„ ๊ฐ–์ถ˜ ๋น„๋™๊ธฐ โ”‚ +โ”‚ โ”‚ ์•„์ดํ…œ ๊ด€๋ฆฌ API โ”‚ +โ”‚ fastapi-dockerized โ”‚ Docker๋กœ ์ปจํ…Œ์ด๋„ˆํ™”๋œ FastAPI โ”‚ +โ”‚ โ”‚ ์•„์ดํ…œ ๊ด€๋ฆฌ API โ”‚ +โ”‚ fastapi-empty โ”‚ ์„ค๋ช… ์—†์Œ โ”‚ +โ”‚ fastapi-async-crud โ”‚ ๋น„๋™๊ธฐ ์•„์ดํ…œ ๊ด€๋ฆฌ API ์„œ๋ฒ„ โ”‚ +โ”‚ fastapi-psql-orm โ”‚ PostgreSQL์„ ์‚ฌ์šฉํ•˜๋Š” Docker๋กœ โ”‚ +โ”‚ โ”‚ ์ปจํ…Œ์ด๋„ˆํ™”๋œ FastAPI ์•„์ดํ…œ ๊ด€๋ฆฌ API โ”‚ +โ”‚ fastapi-default โ”‚ ๊ฐ„๋‹จํ•œ FastAPI ํ”„๋กœ์ ํŠธ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +
+ +## ๋ฌธ์„œ + +ํฌ๊ด„์ ์ธ ๊ฐ€์ด๋“œ์™€ ์ƒ์„ธํ•œ ์‚ฌ์šฉ๋ฒ•์€ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”: + +- ๐Ÿ“š [User Guide](user-guide/quick-start.md) - ์ž์„ธํ•œ ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ +- ๐ŸŽฏ [Tutorial](tutorial/getting-started.md) - ์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ๋‹จ๊ณ„๋ณ„ ํŠœํ† ๋ฆฌ์–ผ +- ๐Ÿ“– [CLI Reference](user-guide/cli-reference.md) - ์™„์ „ํ•œ ๋ช…๋ น์–ด ๋ ˆํผ๋Ÿฐ์Šค +- ๐Ÿ” [Template Quality Assurance](reference/template-quality-assurance.md) - ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ์™€ ํ’ˆ์งˆ ๊ธฐ์ค€ + +## ๐Ÿš€ ํ…œํ”Œ๋ฆฟ ๊ธฐ๋ฐ˜ ํŠœํ† ๋ฆฌ์–ผ + +์‚ฌ์ „ ๊ตฌ์„ฑ๋œ ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์‹ค์ „ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ํ†ตํ•ด FastAPI ๊ฐœ๋ฐœ์„ ํ•™์Šตํ•˜์„ธ์š”: + +### ๐Ÿ“– ์ฝ”์–ด ํŠœํ† ๋ฆฌ์–ผ + +- [๊ธฐ๋ณธ API ์„œ๋ฒ„ ๊ตฌ์ถ•](tutorial/basic-api-server.md) - `fastapi-default` ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•ด ์ฒซ FastAPI ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ +- [๋น„๋™๊ธฐ CRUD API ๊ตฌ์ถ•](tutorial/async-crud-api.md) - `fastapi-async-crud` ํ…œํ”Œ๋ฆฟ์œผ๋กœ ๊ณ ์„ฑ๋Šฅ ๋น„๋™๊ธฐ API ๊ฐœ๋ฐœ + +### ๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ์ธํ”„๋ผ + +- [๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ†ตํ•ฉ](tutorial/database-integration.md) - `fastapi-psql-orm` ํ…œํ”Œ๋ฆฟ์œผ๋กœ PostgreSQL + SQLAlchemy ํ™œ์šฉ +- [Dockerizing ๋ฐ ๋ฐฐํฌ](tutorial/docker-deployment.md) - `fastapi-dockerized` ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•ด ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์„ฑ + +### โšก ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ + +- **[์ปค์Šคํ…€ ์‘๋‹ต ์ฒ˜๋ฆฌ & ๊ณ ๊ธ‰ API ์„ค๊ณ„](tutorial/custom-response-handling.md)** - `fastapi-custom-response` ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ API ๊ตฌ์ถ• +- **[MCP์™€์˜ ํ†ตํ•ฉ](tutorial/mcp-integration.md)** - `fastapi-mcp` ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•ด AI ๋ชจ๋ธ๊ณผ ํ†ตํ•ฉ๋œ API ์„œ๋ฒ„ ์ƒ์„ฑ + +๊ฐ ํŠœํ† ๋ฆฌ์–ผ์€ ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: +- โœ… **์‹ค์šฉ์ ์ธ ์˜ˆ์ œ** - ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ +- โœ… **๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ** - ์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผ ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒ์„ธ ์„ค๋ช… +- โœ… **๋ชจ๋ฒ” ์‚ฌ๋ก€** - ์—…๊ณ„ ํ‘œ์ค€ ํŒจํ„ด๊ณผ ๋ณด์•ˆ ๊ณ ๋ ค ์‚ฌํ•ญ +- โœ… **ํ™•์žฅ ๋ฐฉ๋ฒ•** - ํ”„๋กœ์ ํŠธ๋ฅผ ํ•œ ๋‹จ๊ณ„ ๋” ๋ฐœ์ „์‹œํ‚ค๋Š” ๊ฐ€์ด๋“œ + +## ๊ธฐ์—ฌ + +์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๊ธฐ์—ฌ๋ฅผ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! FastAPI-fastkit์€ Python๊ณผ FastAPI ์ž…๋ฌธ์ž๋ฅผ ๋•๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ธฐ์—ฌ๋Š” ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ๊ธฐ์—ฌํ•  ์ˆ˜ ์žˆ๋Š” ํ•ญ๋ชฉ + +- ๐Ÿš€ **์ƒˆ๋กœ์šด FastAPI ํ…œํ”Œ๋ฆฟ** - ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•œ ํ…œํ”Œ๋ฆฟ ์ถ”๊ฐ€ +- ๐Ÿ› **๋ฒ„๊ทธ ์ˆ˜์ •** - ์•ˆ์ •์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ ๊ฐœ์„ ์— ๋„์›€ +- ๐Ÿ“š **๋ฌธ์„œํ™”** - ๊ฐ€์ด๋“œ, ์˜ˆ์ œ, ๋ฒˆ์—ญ ๊ฐœ์„  +- ๐Ÿงช **ํ…Œ์ŠคํŠธ** - ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์žฅ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ +- ๐Ÿ’ก **๊ธฐ๋Šฅ** - ์ƒˆ๋กœ์šด CLI ๊ธฐ๋Šฅ ์ œ์•ˆ ๋ฐ ๊ตฌํ˜„ + +### ๊ธฐ์—ฌ ์‹œ์ž‘ํ•˜๊ธฐ + +FastAPI-fastkit์— ๊ธฐ์—ฌ๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ๋‹ค์Œ์˜ ์ข…ํ•ฉ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”: + +- **[๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •](contributing/development-setup.md)** - ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •์— ๋Œ€ํ•œ ์™„์ „ํ•œ ๊ฐ€์ด๋“œ +- **[์ฝ”๋“œ ๊ฐ€์ด๋“œ๋ผ์ธ](contributing/code-guidelines.md)** - ์ฝ”๋”ฉ ํ‘œ์ค€ ๋ฐ ๋ชจ๋ฒ” ์‚ฌ๋ก€ +- **[CONTRIBUTING.md](https://github.com/bnbong/FastAPI-fastkit/blob/main/CONTRIBUTING.md)** - ์ข…ํ•ฉ ๊ธฐ์—ฌ ๊ฐ€์ด๋“œ +- **[CODE_OF_CONDUCT.md](https://github.com/bnbong/FastAPI-fastkit/blob/main/CODE_OF_CONDUCT.md)** - ํ”„๋กœ์ ํŠธ ์›์น™๊ณผ ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธฐ์ค€ +- **[SECURITY.md](https://github.com/bnbong/FastAPI-fastkit/blob/main/SECURITY.md)** - ๋ณด์•ˆ ์ง€์นจ ๋ฐ ์‹ ๊ณ  ๋ฐฉ๋ฒ• + +## FastAPI-fastkit์˜ ์˜์˜ + +FastAPI-fastkit์€ Python๊ณผ FastAPI ์‹ ๊ทœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋น ๋ฅด๊ณ  ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํƒ€ํ„ฐ ํ‚คํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค. + +์ด ์•„์ด๋””์–ด๋Š” FastAPI ์ž…๋ฌธ์ž๊ฐ€ ์ฒ˜์Œ๋ถ€ํ„ฐ ํ•™์Šตํ•˜๋„๋ก ๋•๊ธฐ ์œ„ํ•œ ์ทจ์ง€๋กœ ์‹œ์ž‘๋˜์—ˆ์œผ๋ฉฐ, [FastAPI 0.111.0 ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ](https://github.com/fastapi/fastapi/releases/tag/0.111.0)์—์„œ ์ถ”๊ฐ€๋œ FastAPI-cli ํŒจํ‚ค์ง€์˜ ํ”„๋กœ๋•์…˜์  ์˜์˜์™€๋„ ๋งฅ๋ฝ์„ ๊ฐ™์ดํ•ฉ๋‹ˆ๋‹ค. + +์˜ค๋žซ๋™์•ˆ FastAPI๋ฅผ ์‚ฌ์šฉํ•ด ์˜ค๋ฉฐ ์‚ฌ๋ž‘ํ•ด์˜จ ์‚ฌ๋žŒ์œผ๋กœ์„œ, FastAPI ๊ฐœ๋ฐœ์ž [tiangolo](https://github.com/tiangolo)๊ฐ€ ๋ฐํžŒ [๋ฉ‹์ง„ ๋™๊ธฐ](https://github.com/fastapi/fastapi/pull/11522#issuecomment-2264639417)๋ฅผ ์‹คํ˜„ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. + +FastAPI-fastkit์€ ๋‹ค์Œ์„ ์ œ๊ณตํ•จ์œผ๋กœ์จ ์‹œ์ž‘ ๋‹จ๊ณ„์™€ ํ”„๋กœ๋•์…˜ ์ค€๋น„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์ถ• ์‚ฌ์ด์˜ ๊ฐ„๊ทน์„ ๋ฉ”์›๋‹ˆ๋‹ค: + +- **์ฆ‰๊ฐ์ ์ธ ์ƒ์‚ฐ์„ฑ**: ์ดˆ๊ธฐ ์„ค์ •์˜ ๋ณต์žก์„ฑ์— ์••๋„๋  ์ˆ˜ ์žˆ๋Š” ์‹ ๊ทœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰์‹œ ์ƒ์‚ฐ์„ฑ์„ ์ œ๊ณต +- **๋ชจ๋ฒ” ์‚ฌ๋ก€**: ๋ชจ๋“  ํ…œํ”Œ๋ฆฟ์— ๋ชจ๋ฒ” ์‚ฌ๋ก€๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์˜ฌ๋ฐ”๋ฅธ FastAPI ํŒจํ„ด ํ•™์Šต์— ๋„์›€ +- **ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋ฐ˜**: ์ดˆ๋ณด์ž์—์„œ ์ „๋ฌธ๊ฐ€๋กœ ์„ฑ์žฅํ•จ์— ๋”ฐ๋ผ ํ•จ๊ป˜ ํ™•์žฅ๋˜๋Š” ๊ธฐ๋ฐ˜ +- **์ปค๋ฎค๋‹ˆํ‹ฐ ์ฃผ๋„ ํ…œํ”Œ๋ฆฟ**: ์‹ค์ œ FastAPI ์‚ฌ์šฉ ํŒจํ„ด์„ ๋ฐ˜์˜ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ค‘์‹ฌ ํ…œํ”Œ๋ฆฟ + +## ๋‹ค์Œ ๋‹จ๊ณ„ + +FastAPI-fastkit์„ ์‹œ์ž‘ํ•  ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”? ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ผ ์ง„ํ–‰ํ•˜์„ธ์š”: + +### ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +1. **[์„ค์น˜](user-guide/installation.md)**: FastAPI-fastkit ์„ค์น˜ +2. **[ํ€ต ์Šคํƒ€ํŠธ](user-guide/quick-start.md)**: 5๋ถ„ ๋งŒ์— ์ฒซ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ +3. **[์ž…๋ฌธ ํŠœํ† ๋ฆฌ์–ผ](tutorial/getting-started.md)**: ๋‹จ๊ณ„๋ณ„ ์ƒ์„ธ ํŠœํ† ๋ฆฌ์–ผ + +### ๐Ÿ“š ์‹ฌํ™” ํ•™์Šต + +- **[ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ](user-guide/creating-projects.md)**: ๋‹ค์–‘ํ•œ ์Šคํƒ์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ +- **[๋ผ์šฐํŠธ ์ถ”๊ฐ€](user-guide/adding-routes.md)**: ํ”„๋กœ์ ํŠธ์— API ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ +- **[ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉ](user-guide/using-templates.md)**: ์‚ฌ์ „ ๊ตฌ์ถ•๋œ ํ”„๋กœ์ ํŠธ ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉ + +### ๐Ÿ› ๏ธ ๊ธฐ์—ฌํ•˜๊ธฐ + +FastAPI-fastkit์— ๊ธฐ์—ฌํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? + +- **[๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •](contributing/development-setup.md)**: ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ • +- **[์ฝ”๋“œ ๊ฐ€์ด๋“œ๋ผ์ธ](contributing/code-guidelines.md)**: ์ฝ”๋”ฉ ํ‘œ์ค€ ๋ฐ ๋ชจ๋ฒ” ์‚ฌ๋ก€ ์ค€์ˆ˜ +- **[๊ธฐ์—ฌ ๊ฐ€์ด๋“œ๋ผ์ธ](https://github.com/bnbong/FastAPI-fastkit/blob/main/CONTRIBUTING.md)**: ์ข…ํ•ฉ ๊ธฐ์—ฌ ๊ฐ€์ด๋“œ + +### ๐Ÿ” ์ฐธ๊ณ  ์ž๋ฃŒ + +- **[CLI ๋ ˆํผ๋Ÿฐ์Šค](user-guide/cli-reference.md)**: ์ „์ฒด CLI ๋ช…๋ น ๋ ˆํผ๋Ÿฐ์Šค +- **[ํ…œํ”Œ๋ฆฟ ํ’ˆ์งˆ ๋ณด์ฆ](reference/template-quality-assurance.md)**: ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ ๋ฐ ํ’ˆ์งˆ ๊ธฐ์ค€ +- **[FAQ](reference/faq.md)**: ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ +- **[GitHub ์ €์žฅ์†Œ](https://github.com/bnbong/FastAPI-fastkit)**: ์†Œ์Šค ์ฝ”๋“œ ๋ฐ ์ด์Šˆ ํŠธ๋ž˜ํ‚น + +## ๋ผ์ด์„ ์Šค + +์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ ์Šค ํ•˜์— ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค - ์ž์„ธํ•œ ๋‚ด์šฉ์€ [LICENSE](https://github.com/bnbong/FastAPI-fastkit/blob/main/LICENSE) ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜์„ธ์š”. diff --git a/scripts/translate.py b/scripts/translate.py index 1794393..0ef7c6f 100755 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -13,11 +13,11 @@ # @author bnbong bbbong9@gmail.com # -------------------------------------------------------------------------- import argparse -import json import logging import os import subprocess import sys +import time from dataclasses import dataclass, field from pathlib import Path from typing import List, Optional, Tuple @@ -39,10 +39,16 @@ class TranslationConfig: default_factory=lambda: ["ko", "ja", "zh", "es", "fr", "de"] ) docs_dir: Path = Path(__file__).parent.parent / "docs" / source_lang - api_provider: str = "openai" # openai, anthropic, etc. + api_provider: str = "openai" # openai, github, etc. api_key: Optional[str] = None + api_base_url: Optional[str] = None # Custom API endpoint (e.g., GitHub Models) + model_name: str = "gpt-4o-mini" # Default model name create_pr: bool = True branch_prefix: str = "translation" + # Rate limit handling settings + max_retries: int = 5 # Maximum number of retries on rate limit + retry_delay: float = 60.0 # Initial delay between retries (seconds) + request_delay: float = 2.0 # Delay between consecutive API requests (seconds) class TranslationAPI: @@ -94,40 +100,239 @@ def get_translation_prompt( class OpenAITranslator(TranslationAPI): - """OpenAI API translator.""" - - def translate(self, text: str, target_lang: str, source_lang: str = "en") -> str: - """Translate using OpenAI API.""" - try: + """OpenAI API translator (compatible with GitHub Models API).""" + + # Approximate token limits for different models (conservative estimates) + MODEL_TOKEN_LIMITS = { + "gpt-5": 3000, # GitHub Models gpt-5 has ~4000 limit, use 3000 for safety + "openai/gpt-5": 3000, + "gpt-4o": 12000, + "openai/gpt-4o": 12000, + "gpt-4o-mini": 12000, + "openai/gpt-4o-mini": 12000, + } + DEFAULT_TOKEN_LIMIT = 3000 # Conservative default + + def __init__( + self, + api_key: str, + base_url: Optional[str] = None, + model_name: str = "openai/gpt-4o-mini", + max_retries: int = 5, + retry_delay: float = 60.0, + request_delay: float = 2.0, + max_chunk_chars: Optional[int] = None, + ): + super().__init__(api_key) + self.base_url = base_url + self.model_name = model_name + self.max_retries = max_retries + self.retry_delay = retry_delay + self.request_delay = request_delay + self._client = None + # Estimate max chars based on token limit (roughly 4 chars per token for English) + token_limit = self.MODEL_TOKEN_LIMITS.get(model_name, self.DEFAULT_TOKEN_LIMIT) + self.max_chunk_chars = max_chunk_chars or ( + token_limit * 3 + ) # Conservative: 3 chars per token + + def _get_client(self): + """Lazy initialization of OpenAI client.""" + if self._client is None: import openai - client = openai.OpenAI(api_key=self.api_key) + client_kwargs = {"api_key": self.api_key} + if self.base_url: + client_kwargs["base_url"] = self.base_url + + self._client = openai.OpenAI(**client_kwargs) + return self._client + + def _is_rate_limit_error(self, error: Exception) -> bool: + """Check if the error is a rate limit error.""" + error_str = str(error).lower() + return any( + keyword in error_str + for keyword in [ + "rate limit", + "ratelimit", + "429", + "too many requests", + "quota", + ] + ) + + def _is_payload_too_large_error(self, error: Exception) -> bool: + """Check if the error is a payload too large error.""" + error_str = str(error).lower() + return any( + keyword in error_str + for keyword in [ + "413", + "payload too large", + "tokens_limit_reached", + "too large", + ] + ) + + def _split_text_into_chunks(self, text: str) -> List[str]: + """Split text into chunks that fit within token limits. + + Tries to split at natural boundaries (paragraphs, headers) to maintain context. + """ + if len(text) <= self.max_chunk_chars: + return [text] + + chunks = [] + current_chunk = "" + + # Split by double newlines (paragraphs) first + paragraphs = text.split("\n\n") + + for para in paragraphs: + # If adding this paragraph would exceed the limit + if len(current_chunk) + len(para) + 2 > self.max_chunk_chars: + if current_chunk: + chunks.append(current_chunk.strip()) + current_chunk = "" + + # If a single paragraph is too large, split by lines + if len(para) > self.max_chunk_chars: + lines = para.split("\n") + for line in lines: + if len(current_chunk) + len(line) + 1 > self.max_chunk_chars: + if current_chunk: + chunks.append(current_chunk.strip()) + current_chunk = "" + + # If a single line is still too large, force split + if len(line) > self.max_chunk_chars: + for i in range(0, len(line), self.max_chunk_chars): + chunks.append(line[i : i + self.max_chunk_chars]) + else: + current_chunk = line + else: + current_chunk += ("\n" if current_chunk else "") + line + else: + current_chunk = para + else: + current_chunk += ("\n\n" if current_chunk else "") + para + + if current_chunk: + chunks.append(current_chunk.strip()) + + return chunks + + def _translate_single_chunk( + self, + text: str, + target_lang: str, + source_lang: str, + is_continuation: bool = False, + ) -> str: + """Translate a single chunk of text.""" + client = self._get_client() + + if is_continuation: + # Simplified prompt for continuation chunks + prompt = f"""Continue translating the following text to {target_lang}. +Maintain the same style and formatting as before. +IMPORTANT: Preserve all markdown formatting, code blocks, and technical terms. + +Text: +{text} + +Provide only the translated text without any additional commentary.""" + else: prompt = self.get_translation_prompt(text, target_lang, source_lang) - response = client.chat.completions.create( - model="gpt-5-nano", - messages=[ - { - "role": "system", - "content": "You are a professional technical documentation translator.", - }, - {"role": "user", "content": prompt}, - ], - temperature=0.3, - max_tokens=4096, - ) + last_error = None + for attempt in range(self.max_retries + 1): + try: + if attempt > 0: + wait_time = self.retry_delay * (2 ** (attempt - 1)) + logger.warning( + f"Rate limit hit. Waiting {wait_time:.1f}s before retry {attempt}/{self.max_retries}..." + ) + time.sleep(wait_time) + + # Build API call parameters - GPT-5 doesn't support temperature + api_params = { + "model": self.model_name, + "messages": [ + { + "role": "system", + "content": "You are a professional technical documentation translator.", + }, + {"role": "user", "content": prompt}, + ], + } + + # Only add temperature for models that support it + if "gpt-5" not in self.model_name.lower(): + api_params["temperature"] = 0.3 + + response = client.chat.completions.create(**api_params) + + response_content = response.choices[0].message.content + if response_content is None: + return text + + time.sleep(self.request_delay) + return response_content.strip() - response_content = response.choices[0].message.content - if response_content is None: - return text - return response_content.strip() + except Exception as e: + last_error = e + if self._is_rate_limit_error(e): + if attempt < self.max_retries: + continue + else: + logger.error( + f"Max retries ({self.max_retries}) exceeded due to rate limiting." + ) + raise + else: + raise + + if last_error: + raise last_error + return text + + def translate(self, text: str, target_lang: str, source_lang: str = "en") -> str: + """Translate text, automatically chunking if necessary.""" + try: + import openai except ImportError: logger.error("OpenAI package not installed. Run: pip install openai") raise + + # Try direct translation first + try: + return self._translate_single_chunk(text, target_lang, source_lang) except Exception as e: - logger.error(f"Translation failed: {e}") - raise + if not self._is_payload_too_large_error(e): + logger.error(f"Translation failed: {e}") + raise + + # If payload too large, split into chunks and translate each + logger.info(f"Document too large, splitting into chunks for translation...") + chunks = self._split_text_into_chunks(text) + logger.info(f"Split into {len(chunks)} chunks") + + translated_chunks = [] + for i, chunk in enumerate(chunks): + logger.info(f"Translating chunk {i + 1}/{len(chunks)}...") + try: + translated = self._translate_single_chunk( + chunk, target_lang, source_lang, is_continuation=(i > 0) + ) + translated_chunks.append(translated) + except Exception as chunk_error: + logger.error(f"Failed to translate chunk {i + 1}: {chunk_error}") + raise + + return "\n\n".join(translated_chunks) class DocumentTranslator: @@ -144,8 +349,20 @@ def _initialize_translator(self) -> TranslationAPI: "API key is required. Set via --api-key or environment variable." ) - if self.config.api_provider == "openai": - return OpenAITranslator(self.config.api_key) + if self.config.api_provider in ("openai", "github"): + # For GitHub Models, set default base_url if not provided + base_url = self.config.api_base_url + if self.config.api_provider == "github" and not base_url: + base_url = "https://models.github.ai/inference" + + return OpenAITranslator( + api_key=self.config.api_key, + base_url=base_url, + model_name=self.config.model_name, + max_retries=self.config.max_retries, + retry_delay=self.config.retry_delay, + request_delay=self.config.request_delay, + ) else: raise ValueError(f"Unsupported API provider: {self.config.api_provider}") @@ -283,14 +500,25 @@ def main() -> None: parser.add_argument( "--api-provider", type=str, - choices=["openai"], + choices=["openai", "github"], default="openai", - help="AI API provider for translation", + help="AI API provider for translation (openai or github)", ) parser.add_argument( "--api-key", type=str, - help="API key for translation service. Can also be set via TRANSLATION_API_KEY environment variable.", + help="API key for translation service. Can also be set via TRANSLATION_API_KEY (OpenAI) or GITHUB_TOKEN (GitHub) environment variable.", + ) + parser.add_argument( + "--api-base-url", + type=str, + help="Custom API base URL. Defaults to https://models.github.ai/inference for github provider.", + ) + parser.add_argument( + "--model", + type=str, + default="gpt-4o-mini", + help="Model name to use for translation (default: gpt-4o-mini). For GitHub, use format like 'openai/gpt-4o-mini'.", ) parser.add_argument( "--no-pr", action="store_true", help="Skip creating GitHub Pull Request" @@ -302,20 +530,51 @@ def main() -> None: help="Specific files to translate (relative to docs directory)", ) parser.add_argument("--docs-dir", type=str, help="Path to documentation directory") + # Rate limit handling options + parser.add_argument( + "--max-retries", + type=int, + default=5, + help="Maximum number of retries on rate limit errors (default: 5)", + ) + parser.add_argument( + "--retry-delay", + type=float, + default=60.0, + help="Initial delay in seconds between retries (uses exponential backoff, default: 60.0)", + ) + parser.add_argument( + "--request-delay", + type=float, + default=2.0, + help="Delay in seconds between consecutive API requests to avoid rate limits (default: 2.0)", + ) args = parser.parse_args() - # Get API key from args or environment + # Get API key from args or environment (support both TRANSLATION_API_KEY and GITHUB_TOKEN) api_key = args.api_key or os.getenv("TRANSLATION_API_KEY") + if not api_key and args.api_provider == "github": + api_key = os.getenv("GITHUB_TOKEN") if not api_key: + env_var = ( + "GITHUB_TOKEN" if args.api_provider == "github" else "TRANSLATION_API_KEY" + ) logger.error( - "API key is required. Provide via --api-key or TRANSLATION_API_KEY environment variable." + f"API key is required. Provide via --api-key or {env_var} environment variable." ) sys.exit(1) # Setup configuration config = TranslationConfig( - api_provider=args.api_provider, api_key=api_key, create_pr=not args.no_pr + api_provider=args.api_provider, + api_key=api_key, + api_base_url=args.api_base_url, + model_name=args.model, + max_retries=args.max_retries, + retry_delay=args.retry_delay, + request_delay=args.request_delay, + create_pr=not args.no_pr, ) if args.docs_dir: