major: added initial version
This commit is contained in:
121
.github/workflows/ci.yml
vendored
Normal file
121
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
name: Lint, Test, Tag, Build and Deploy DEV
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- "**/.github/**"
|
||||||
|
- "**/.gitignore"
|
||||||
|
- "**/.vscode/**"
|
||||||
|
- "**/README.md"
|
||||||
|
- "**/CHANGELOG.md"
|
||||||
|
- "**/docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: psf/black@stable
|
||||||
|
with:
|
||||||
|
options: "-l 100 --exclude '/.venv/|alembic/|/__init__.py'"
|
||||||
|
- uses: creyD/autoflake_action@master
|
||||||
|
with:
|
||||||
|
no_commit: True
|
||||||
|
options: --in-place --remove-all-unused-imports -r --exclude **/__init__.py,**/db/models.py,
|
||||||
|
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: Adjusted files for isort & autopep
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.12
|
||||||
|
cache: 'pip' # caching pip dependencies
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
- name: Run tests
|
||||||
|
run: pytest
|
||||||
|
|
||||||
|
tag:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # for the tags
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.git_version.outputs.version }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: setup git
|
||||||
|
run: |
|
||||||
|
git config --local user.email "15138480+creyD@users.noreply.github.com"
|
||||||
|
git config --local user.name "creyD"
|
||||||
|
|
||||||
|
- name: Git Version
|
||||||
|
uses: PaulHatch/semantic-version@v5.4.0
|
||||||
|
id: git_version
|
||||||
|
with:
|
||||||
|
tag_prefix: ""
|
||||||
|
major_pattern: "breaking:"
|
||||||
|
minor_pattern: "feat:"
|
||||||
|
enable_prerelease_mode: false
|
||||||
|
version_format: "${major}.${minor}.${patch}-rc${increment}"
|
||||||
|
|
||||||
|
- name: Create Tag
|
||||||
|
run: git tag ${{ steps.git_version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Push tag
|
||||||
|
run: git push origin ${{ steps.git_version.outputs.version }}
|
||||||
|
|
||||||
|
build_and_push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions: write-all
|
||||||
|
needs: tag
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ github.ref_name }}
|
||||||
|
tags: latest
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ needs.tag.outputs.version }}-${{ github.ref_name }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
28
.gitignore
vendored
28
.gitignore
vendored
@@ -55,16 +55,6 @@ cover/
|
|||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
.scrapy
|
.scrapy
|
||||||
|
|
||||||
@@ -77,6 +67,7 @@ target/
|
|||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
.virtual_documents
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
profile_default/
|
profile_default/
|
||||||
@@ -106,10 +97,8 @@ ipython_config.py
|
|||||||
#pdm.lock
|
#pdm.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
# in version control.
|
# in version control.
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
@@ -155,8 +144,11 @@ dmypy.json
|
|||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
.idea/
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# MacOS
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
.DS_Store
|
||||||
#.idea/
|
|
||||||
|
*.log
|
||||||
|
files/
|
||||||
|
DOCKER/
|
||||||
|
|||||||
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "uvicorn",
|
||||||
|
"args": ["app.main:app", "--reload", "--port", "8000"],
|
||||||
|
"jinja": true,
|
||||||
|
"justMyCode": true,
|
||||||
|
"env": {
|
||||||
|
"PYDEVD_DISABLE_FILE_VALIDATION": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Migrate",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "alembic",
|
||||||
|
"args": ["upgrade", "head"],
|
||||||
|
"jinja": true,
|
||||||
|
"justMyCode": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Make Migrations",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "alembic",
|
||||||
|
"args": ["revision", "--autogenerate"],
|
||||||
|
"jinja": true,
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
42
.vscode/settings.json
vendored
Normal file
42
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python3",
|
||||||
|
"python.globalModuleInstallation": false,
|
||||||
|
"python.terminal.activateEnvironment": true,
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"[python]": {
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
|
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||||
|
},
|
||||||
|
"black-formatter.args": ["--line-length", "100"],
|
||||||
|
"isort.args": ["--profile", "black"],
|
||||||
|
// Editor General
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"editor.fontSize": 15,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.rulers": [100],
|
||||||
|
"editor.minimap.enabled": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.pytest_cache": true,
|
||||||
|
"**/.venv": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/db.sqlite3": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/*.pyc": true,
|
||||||
|
"**/__pycache__/": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.venv": true,
|
||||||
|
"**/tmp": true,
|
||||||
|
"htmlcov/*": true,
|
||||||
|
"docs/*": true,
|
||||||
|
".venv/*": true
|
||||||
|
},
|
||||||
|
"python.testing.pytestArgs": ["app"],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true
|
||||||
|
}
|
||||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
ARG VERSION=unkown
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Python setup
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
ENV VERSION=${VERSION}
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
||||||
|
RUN pip install 'uvicorn[standard]'
|
||||||
|
|
||||||
|
ENV ENV=DEV
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|
||||||
|
# Install curl
|
||||||
|
RUN apt-get update && apt-get install -y curl && apt-get clean
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --retries=5 \
|
||||||
|
CMD curl --fail http://localhost:8000/openapi.json || exit 1
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
# pong
|
# pong
|
||||||
|
|
||||||
A really simple FastAPI service to return the request with code and content specified in the parameters.
|
A really simple FastAPI service to return the request with code and content specified in the parameters.
|
||||||
|
|||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
55
app/main.py
Normal file
55
app/main.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi import Response
|
||||||
|
from creyPY.fastapi.app import generate_unique_id
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
ENV = os.getenv("ENV", "local").lower()
|
||||||
|
VERSION = os.getenv("VERSION", "Alpha")
|
||||||
|
|
||||||
|
# App Setup
|
||||||
|
app = FastAPI(
|
||||||
|
title="ServerCrow Pong API",
|
||||||
|
description="A really simple FastAPI service to return the request with code and content specified in the parameters. No logging, no nothing.",
|
||||||
|
version=VERSION,
|
||||||
|
docs_url="/",
|
||||||
|
redoc_url=None,
|
||||||
|
debug=ENV != "prod",
|
||||||
|
swagger_ui_parameters={
|
||||||
|
"docExpansion": "list",
|
||||||
|
"displayOperationId": True,
|
||||||
|
"defaultModelsExpandDepth": 5,
|
||||||
|
"defaultModelExpandDepth": 5,
|
||||||
|
"filter": True,
|
||||||
|
"displayRequestDuration": True,
|
||||||
|
"defaultModelRendering": "model",
|
||||||
|
"persistAuthorization": True,
|
||||||
|
},
|
||||||
|
generate_unique_id_function=generate_unique_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# CORS Setup
|
||||||
|
origins = ["http://localhost:5173", "https://pong.servercrow.com"]
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# App Routers
|
||||||
|
router = APIRouter(prefix="/pong", tags=["public"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", operation_id="get_pong")
|
||||||
|
async def get_status(code: int, response_text: str = "OK") -> Response:
|
||||||
|
"""Get the ping to your pong. Returns the code that is specified and a response if provided."""
|
||||||
|
return Response(status_code=code, content=response_text)
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(router)
|
||||||
42
app/test_main.py
Normal file
42
app/test_main.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from creyPY.fastapi.testing import GenericClient
|
||||||
|
from .main import app
|
||||||
|
|
||||||
|
|
||||||
|
class TestAPI:
|
||||||
|
def setup_class(self):
|
||||||
|
self.c = GenericClient(app)
|
||||||
|
|
||||||
|
def test_swagger_gen(self):
|
||||||
|
re = self.c.get("/openapi.json")
|
||||||
|
assert re["info"]["title"] == "ServerCrow Pong API"
|
||||||
|
|
||||||
|
def test_health_check(self):
|
||||||
|
self.c.get("/", parse_json=False)
|
||||||
|
|
||||||
|
def test_get_pong(self):
|
||||||
|
re = self.c.get("/pong/?code=200&response_text=OK", parse_json=False)
|
||||||
|
assert re == b"OK"
|
||||||
|
|
||||||
|
def test_get_pong_404(self):
|
||||||
|
re = self.c.get("/pong/?code=404&response_text=Not Found", parse_json=False, r_code=404)
|
||||||
|
assert re == b"Not Found"
|
||||||
|
|
||||||
|
def test_get_pong_503(self):
|
||||||
|
re = self.c.get(
|
||||||
|
"/pong/?code=503&response_text=Service Unavailable", parse_json=False, r_code=503
|
||||||
|
)
|
||||||
|
assert re == b"Service Unavailable"
|
||||||
|
|
||||||
|
def test_get_pong_500(self):
|
||||||
|
re = self.c.get(
|
||||||
|
"/pong/?code=500&response_text=Internal Server Error", parse_json=False, r_code=500
|
||||||
|
)
|
||||||
|
assert re == b"Internal Server Error"
|
||||||
|
|
||||||
|
def test_get_pong_400(self):
|
||||||
|
re = self.c.get("/pong/?code=400&response_text=Bad Request", parse_json=False, r_code=400)
|
||||||
|
assert re == b"Bad Request"
|
||||||
|
|
||||||
|
def test_get_pong_401(self):
|
||||||
|
re = self.c.get("/pong/?code=401&response_text=Unauthorized", parse_json=False, r_code=401)
|
||||||
|
assert re == b"Unauthorized"
|
||||||
5
pytest.ini
Normal file
5
pytest.ini
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[pytest]
|
||||||
|
filterwarnings =
|
||||||
|
default:::app.*
|
||||||
|
ignore
|
||||||
|
addopts = -sl
|
||||||
15
requirements.txt
Normal file
15
requirements.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
certifi==2023.11.17 # Testing
|
||||||
|
iniconfig==2.0.0 # PyTest Testing
|
||||||
|
packaging==23.2 # PyTest Testing
|
||||||
|
pluggy==1.3.0 # PyTest Testing
|
||||||
|
pytest==7.4.3 # PyTest Testing
|
||||||
|
|
||||||
|
click==8.1.7 # Uvicorn
|
||||||
|
httptools==0.6.1 # Uvicorn
|
||||||
|
pyyaml==6.0.1 # Uvicorn
|
||||||
|
uvicorn==0.27.0.post1 # Uvicorn
|
||||||
|
uvloop==0.19.0 # Uvicorn
|
||||||
|
watchfiles==0.21.0 # Uvicorn
|
||||||
|
websockets==12.0 # Uvicorn
|
||||||
|
|
||||||
|
creyPY==1.3.0 # My own package
|
||||||
Reference in New Issue
Block a user