diff --git a/.github/workflows/detect-changed-versions.yml b/.github/workflows/detect-changed-versions.yml new file mode 100644 index 0000000..28ed34b --- /dev/null +++ b/.github/workflows/detect-changed-versions.yml @@ -0,0 +1,36 @@ +--- +name: Detect Changed Versions + +permissions: + contents: read + statuses: read + +on: + workflow_call: + outputs: + versions: + description: 'Changed version folders' + value: ${{ jobs.detect-changed-versions.outputs.versions }} + +jobs: + detect-changed-versions: + name: Detect Changed Versions + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.list-folders.outputs.versions }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed version folders + id: list-folders + run: | + git fetch origin main + BASE_SHA=$(git merge-base HEAD origin/main) + + CHANGED=$(git diff --name-only "$BASE_SHA"...HEAD | grep '^routes/v[0-9]\+/.*\.py$' | grep -v 'test' || true) + VERSIONS=$(echo "$CHANGED" | sed -n 's|^routes/\(v[0-9]\+\)/.*|\1|p' | sort -u | tr '\n' ' ') + echo "versions=$VERSIONS" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/devcontainer-verification.yml b/.github/workflows/devcontainer-verification.yml index 0d45f9f..c0a1948 100644 --- a/.github/workflows/devcontainer-verification.yml +++ b/.github/workflows/devcontainer-verification.yml @@ -1,14 +1,15 @@ +--- name: Scan Devcontainer Image permissions: actions: read + contents: read security-events: write on: push: branches: - main - - master pull_request: null jobs: diff --git a/.github/workflows/enforce-version.yml b/.github/workflows/enforce-version.yml new file mode 100644 index 0000000..e056b10 --- /dev/null +++ b/.github/workflows/enforce-version.yml @@ -0,0 +1,86 @@ +name: Enforce Correct Version Update + +permissions: + contents: read + statuses: write + +on: + pull_request: + branches: + - main + paths: + - '**/*.py' + - '!**/tests/**' + +jobs: + version-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed version folders and main.py + id: detect + run: | + echo "🔍 Checking changed versioned folders and main.py..." + + git fetch origin main + BASE_SHA=$(git merge-base HEAD origin/main) + + # List changed Python files (excluding tests) + CHANGED=$(git diff --name-only "$BASE_SHA"...HEAD | grep '\.py$' | grep -v 'test' || true) + echo "$CHANGED" + + # Extract unique version folders like v1, v2 + VERSIONS=$(echo "$CHANGED" | sed -n 's|^app/routes/\(v[0-9]\+\)/.*|\1|p' | sort -u | tr '\n' ' ') + echo "Detected version folders with changes: $VERSIONS" + echo "VERSIONS=$VERSIONS" >> "$GITHUB_ENV" + + # Check if main.py was modified + MAIN_CHANGED=$(echo "$CHANGED" | grep '^app/main.py$' || true) + echo "MAIN_CHANGED=$MAIN_CHANGED" >> "$GITHUB_ENV" + + - name: Verify version changes and summarize + run: | + echo "📋 Running version change checks..." + + git fetch origin main + BASE_SHA=$(git merge-base HEAD origin/main) + + REPORT="" + EXIT_CODE=0 + + # Check version folders + for v in $VERSIONS; do + echo "➡ Checking app/routes/$v/__init__.py" + VERSION_CHANGED=$(git diff "$BASE_SHA"...HEAD -- "app/routes/$v/__init__.py" | grep '__version__ = ' || true) + + if [ -z "$VERSION_CHANGED" ]; then + REPORT+="🛑 Version $v was modified, but __version__ was not updated in app/routes/$v/__init__.py\n" + EXIT_CODE=1 + else + REPORT+="✅ app/routes/$v/__init__.py includes a version change.\n" + fi + done + + # Check main.py + if [ -n "$MAIN_CHANGED" ]; then + echo "➡ Checking app/main.py" + VERSION_CHANGED=$(git diff "$BASE_SHA"...HEAD -- "app/main.py" | grep '__version__ = ' || true) + + if [ -z "$VERSION_CHANGED" ]; then + REPORT+="🛑 main.py was modified, but __version__ was not updated in app/main.py\n" + EXIT_CODE=1 + else + REPORT+="✅ app/main.py includes a version change.\n" + fi + fi + + echo -e "\n==== Version Check Report ====" + echo -e "$REPORT" + echo "================================" + + exit $EXIT_CODE diff --git a/.github/workflows/enforce_correct_version_update.yml b/.github/workflows/enforce_correct_version_update.yml deleted file mode 100644 index a8f5385..0000000 --- a/.github/workflows/enforce_correct_version_update.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Enforce Correct Version Update - -on: - pull_request: - branches: - - main - paths: - - '**/*.py' - paths-ignore: - - '**/tests/**' - -jobs: - version-check: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Detect changed version folders - id: detect - run: | - echo "🔍 Checking changed versioned folders..." - CHANGED=$(git diff --name-only origin/main HEAD | grep '^routes/v[0-9]\+/.*\.py$' | grep -v 'test' || true) - - echo "Changed files:" - echo "$CHANGED" - - # Extract unique version folders like v1, v2 - VERSIONS=$(echo "$CHANGED" | sed -n 's|^routes/\(v[0-9]\+\)/.*|\1|p' | sort -u) - echo "Detected version folders with changes: $VERSIONS" - echo "VERSIONS=$VERSIONS" >> $GITHUB_ENV - - - name: Verify __version__ change per version folder - if: env.VERSIONS != '' - run: | - echo "🔍 Checking for __version__ changes in modified version folders..." - - for v in $VERSIONS; do - echo "➡ Checking $v" - - VERSION_CHANGED=$(git diff origin/main HEAD -- routes/$v/__init__.py | grep '__version__ = ' || true) - - if [ -z "$VERSION_CHANGED" ]; then - echo "🛑 Version $v was modified, but __version__ was not updated in routes/$v/__init__.py" - exit 1 - else - echo "✅ routes/$v/__init__.py includes a version change." - fi - done diff --git a/.github/workflows/get-pr-labels.yml b/.github/workflows/get-pr-labels.yml new file mode 100644 index 0000000..3cb4d49 --- /dev/null +++ b/.github/workflows/get-pr-labels.yml @@ -0,0 +1,35 @@ +--- +name: Get PR Labels + +permissions: + contents: read + statuses: read + pull-requests: read + +on: + workflow_call: + outputs: + labels: + description: 'Labels on the pull request' + value: ${{ jobs.get-labels.outputs.labels }} + +jobs: + get-labels: + name: Get PR Labels + runs-on: ubuntu-latest + outputs: + labels: ${{ steps.labels.outputs.labels }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get PR labels + id: labels + run: | + LABELS=$(gh pr view "${{ github.event.pull_request.number }}" --json labels --jq '[.labels[].name] | join(" ")') + echo "labels=$LABELS" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/suggest-version-bump.yml b/.github/workflows/suggest-version-bump.yml new file mode 100644 index 0000000..10ae9e0 --- /dev/null +++ b/.github/workflows/suggest-version-bump.yml @@ -0,0 +1,47 @@ +name: Suggest Version Bump + +permissions: + contents: read + statuses: read + pull-requests: read + +on: + pull_request: + branches: [main] + paths: + - '**/*.py' + - '!**/tests/**' + +jobs: + suggest: + uses: ./.github/workflows/detect-changed-versions.yml + labels: + uses: ./.github/workflows/get-pr-labels.yml + + analyze: + name: Analyze Changes and Suggest Bumps + needs: + - suggest + - labels + runs-on: ubuntu-latest + + steps: + - name: Determine bump type + id: bump + run: | + LABELS="${{ needs.labels.outputs.labels }}" + BUMP="patch" + echo "$LABELS" | grep -q 'type: feature' && BUMP="minor" + echo "$LABELS" | grep -q 'type: security' && BUMP="minor" + # ... other types + echo "bump=$BUMP" >> "$GITHUB_OUTPUT" + + - name: Report summary + if: needs.suggest.outputs.versions != '' + run: | + { + echo "### 🚀 Suggested Version Bumps" + for v in ${{ needs.suggest.outputs.versions }}; do + echo "- \`routes/$v/\`: **${{ steps.bump.outputs.bump }}**" + done + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/suggest_version_bump.yml b/.github/workflows/suggest_version_bump.yml deleted file mode 100644 index 157fb8e..0000000 --- a/.github/workflows/suggest_version_bump.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Suggest Version Bump - -on: - pull_request: - types: - - opened - - labeled - - unlabeled - - synchronize - paths: - - '**/*.py' - paths-ignore: - - '**/tests/**' - -jobs: - suggest-bump: - runs-on: ubuntu-latest - steps: - - name: Check out PR code - uses: actions/checkout@v4 - - - name: Determine affected API versions - id: changed - run: | - CHANGED_FOLDERS=$(git diff --name-only origin/main...HEAD | grep "^routes/" | cut -d/ -f2 | sort -u) - echo "📁 Changed folders detected: $CHANGED_FOLDERS" - echo "folders=$CHANGED_FOLDERS" >> $GITHUB_OUTPUT - - - name: Get PR labels - id: pr_labels - run: | - LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name') - echo "labels=$LABELS" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ github.token }} - - - name: Determine bump type - id: bump - run: | - LABELS="${{ steps.pr_labels.outputs.labels }}" - BUMP="patch" - echo "$LABELS" | grep -q 'type: feature' && BUMP="minor" - echo "$LABELS" | grep -q 'type: security' && BUMP="minor" - echo "$LABELS" | grep -q 'type: refactor' && BUMP="patch" - echo "$LABELS" | grep -q 'type: fix' && BUMP="patch" - echo "$LABELS" | grep -q 'type: optimization' && BUMP="patch" - echo "bump=$BUMP" >> $GITHUB_OUTPUT - - echo "📁 Detected change in: ${{ steps.changed.outputs.folders }}" - for folder in ${{ steps.changed.outputs.folders }}; do - echo "📌 Suggest bump for routes/$folder/: $BUMP" - done diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index db2e48a..a945641 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -10,7 +10,6 @@ on: push: branches: - main - - master pull_request: null jobs: @@ -24,9 +23,11 @@ jobs: with: fetch-depth: 0 - - name: Super-linter + - name: Run Super Linter uses: super-linter/super-linter@v7 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_ALL_CODEBASE: false - FILTER_REGEX_EXCLUDE: "(.devcontainer/Dockerfile|.github/pull_request_template.md|.github/ISSUE_TEMPLATE/*.md)" + FILTER_REGEX_EXCLUDE: '(.devcontainer/Dockerfile|.github/pull_request_template.md|.github/ISSUE_TEMPLATE/*.md)' + VALIDATE_PYTHON_ISORT: false + VALIDATE_PYTHON_MYPY: false diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..b274b07 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,54 @@ +--- +name: Run Unit Tests + +permissions: + contents: read + +on: + push: + branches: + - main + pull_request: null + +jobs: + test: + name: Pytest on ${{ matrix.os }} with Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.13', '3.x'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + - name: Install OpenSSL (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libssl-dev + + # MacOS does not require OpenSSL installation as it is pre-installed + + - name: Install OpenSSL (Windows) + if: runner.os == 'Windows' + run: choco install openssl.light --no-progress + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install pytest pytest-cov + + - name: Run Pytest (Linux/macOS) + if: runner.os != 'Windows' + run: python -m pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + + - name: Run Pytest (Windows) + if: runner.os == 'Windows' + run: python -m pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html diff --git a/.gitignore b/.gitignore index 4c49bd7..1cc48e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,189 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.pyc + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments .env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# 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 +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the enitre vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ed7278..000bbd4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,31 +10,18 @@ "**/.pytest_cache": true }, "[python]": { - "editor.rulers": [ - 88 - ], + "editor.rulers": [88], "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } }, - "isort.args": [ - "--profile", - "black" - ], + "isort.args": ["--profile", "black"], "triggerTaskOnSave.tasks": { - "Run on test file": [ - "tests/**/test_*.py" - ], - "Run all tests": [ - "!tests/**", - "**/*.py" - ] + "Run on test file": ["tests/**/test_*.py"], + "Run all tests": ["app/**/*.py", "!tests/**"] }, - "python.testing.pytestArgs": [ - "tests" - ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c3c6322..4eaebd1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,10 +5,7 @@ "label": "Run all tests", "type": "shell", "command": "python3 -m pytest tests", - "group": { - "kind": "test", - "isDefault": true - }, + "group": "build", "problemMatcher": [], "runOptions": { "runOn": "default" @@ -38,6 +35,23 @@ "panel": "dedicated", "close": true } + }, + { + "label": "Package app", + "type": "shell", + "command": "python setup.py egg_info --egg-base .", + "problemMatcher": [], + "runOptions": { + "runOn": "default" + }, + "presentation": { + "panel": "dedicated", + "close": true + }, + "group": { + "kind": "build", + "isDefault": true + } } ] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5e4859d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.13-alpine3.21 + +# Create a user to run the app securely +WORKDIR /app + +# Install dependencies +RUN apt-get update && apt-get install -y --no-install-recommends curl openssl \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Copy package and dependencies +COPY requirements.txt . +COPY app/* ./ + +# Install app dependencies as the apiuser +RUN pip install --no-cache-dir . + +# Use non-root user +USER apiuser + +# Expose the FastAPI port +EXPOSE 8000 + +# Add healthcheck for the container +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl --fail http://localhost:8000/ || exit 1 + +CMD ["python3", "main.py"] diff --git a/LICENSE b/LICENSE index 2a88561..aec0d6a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2025 ChocoMax Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 88ab43a..0000000 Binary files a/__pycache__/main.cpython-312.pyc and /dev/null differ diff --git a/main.py b/app/main.py similarity index 52% rename from main.py rename to app/main.py index d72e0d7..11973fb 100644 --- a/main.py +++ b/app/main.py @@ -1,9 +1,15 @@ -from fastapi import FastAPI +""" +This is the main entry point for the API application. +It sets up the FastAPI application, includes the home router, and mounts versioned APIs. +""" +from fastapi import FastAPI from routes.home import router as home_router from routes.v1 import api as v1 from routes.v2 import api as v2 +__version__ = "0.1.0" + app = FastAPI(title="ChocoMax Shop API") # Home (non-versioned) @@ -13,7 +19,16 @@ app.mount("/api/v1", v1) app.mount("/api/v2", v2) -if __name__ == "__main__": + +def main(): + """ + Main function to run the FastAPI application. + This is typically used when running the app with a command like `python main.py`. + """ import uvicorn uvicorn.run("main:app", reload=True) + + +if __name__ == "__main__": + main() diff --git a/routes/__init__.py b/app/routes/__init__.py similarity index 100% rename from routes/__init__.py rename to app/routes/__init__.py diff --git a/app/routes/home.py b/app/routes/home.py new file mode 100644 index 0000000..05ac404 --- /dev/null +++ b/app/routes/home.py @@ -0,0 +1,14 @@ +""" +This is the main entry point for the API. +It sets up the home router and defines the root endpoint. +""" + +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/", tags=["Home"]) +def read_root(): + """Root endpoint that returns a welcome message.""" + return {"message": "Welcome to the ChocoMax Shop API!"} diff --git a/routes/v1/__init__.py b/app/routes/v1/__init__.py similarity index 71% rename from routes/v1/__init__.py rename to app/routes/v1/__init__.py index 82b9bc5..d025933 100644 --- a/routes/v1/__init__.py +++ b/app/routes/v1/__init__.py @@ -1,4 +1,6 @@ -# routes/v1/__init__.py +""" +Base module for the API version 1 routes. +""" from fastapi import FastAPI @@ -7,7 +9,7 @@ __version__ = "1.0.0" -api = FastAPI(title=f"ChocoMax Shop API", version=__version__) +api = FastAPI(title="ChocoMax Shop API", version=__version__) api.include_router(product_router, prefix="/products", tags=["Products"]) api.include_router(order_router, prefix="/orders", tags=["Orders"]) diff --git a/routes/v1/orders.py b/app/routes/v1/orders.py similarity index 92% rename from routes/v1/orders.py rename to app/routes/v1/orders.py index ee93497..dab208d 100644 --- a/routes/v1/orders.py +++ b/app/routes/v1/orders.py @@ -1,3 +1,7 @@ +""" +This module defines the API routes for order management. +""" + from fastapi import APIRouter router = APIRouter() @@ -43,4 +47,5 @@ @router.get("/") def get_orders(): + """Retrieve all orders.""" return orders diff --git a/routes/v1/products.py b/app/routes/v1/products.py similarity index 92% rename from routes/v1/products.py rename to app/routes/v1/products.py index 6eb70ee..f323509 100644 --- a/routes/v1/products.py +++ b/app/routes/v1/products.py @@ -1,3 +1,7 @@ +""" +This module defines the API routes for managing products. +""" + from fastapi import APIRouter router = APIRouter() @@ -62,4 +66,5 @@ @router.get("/") def get_products(): + """Retrieve a list of all products.""" return products diff --git a/app/routes/v2/__init__.py b/app/routes/v2/__init__.py new file mode 100644 index 0000000..ba29719 --- /dev/null +++ b/app/routes/v2/__init__.py @@ -0,0 +1,9 @@ +""" +Base module for the API version 2 routes. +""" + +from fastapi import FastAPI + +__version__ = "2.0.0" + +api = FastAPI(title="ChocoMax Shop API", version=__version__) diff --git a/utility/test.py b/app/utility/test.py similarity index 100% rename from utility/test.py rename to app/utility/test.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8bd6701 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +pythonpath = ["app"] diff --git a/routes/__pycache__/__init__.cpython-312.pyc b/routes/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 14518c0..0000000 Binary files a/routes/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/routes/__pycache__/home.cpython-312.pyc b/routes/__pycache__/home.cpython-312.pyc deleted file mode 100644 index 1966b51..0000000 Binary files a/routes/__pycache__/home.cpython-312.pyc and /dev/null differ diff --git a/routes/home.py b/routes/home.py deleted file mode 100644 index 1962be8..0000000 --- a/routes/home.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.get("/", tags=["Home"]) -def read_root(): - return {"message": "Welcome to the ChocoMax Shop API"} diff --git a/routes/v1/__pycache__/__init__.cpython-312.pyc b/routes/v1/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d976ab1..0000000 Binary files a/routes/v1/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/routes/v1/__pycache__/orders.cpython-312.pyc b/routes/v1/__pycache__/orders.cpython-312.pyc deleted file mode 100644 index 0022e56..0000000 Binary files a/routes/v1/__pycache__/orders.cpython-312.pyc and /dev/null differ diff --git a/routes/v1/__pycache__/products.cpython-312.pyc b/routes/v1/__pycache__/products.cpython-312.pyc deleted file mode 100644 index d7a73c9..0000000 Binary files a/routes/v1/__pycache__/products.cpython-312.pyc and /dev/null differ diff --git a/routes/v2/__init__.py b/routes/v2/__init__.py deleted file mode 100644 index 211c0ed..0000000 --- a/routes/v2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# routes/v1/__init__.py - -from fastapi import FastAPI - -__version__ = "2.0.0" - -api = FastAPI(title=f"ChocoMax Shop API", version=__version__) diff --git a/routes/v2/__pycache__/__init__.cpython-312.pyc b/routes/v2/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e27ff23..0000000 Binary files a/routes/v2/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/routes/v2/__pycache__/orders.cpython-312.pyc b/routes/v2/__pycache__/orders.cpython-312.pyc deleted file mode 100644 index ea0cb0e..0000000 Binary files a/routes/v2/__pycache__/orders.cpython-312.pyc and /dev/null differ diff --git a/routes/v2/__pycache__/products.cpython-312.pyc b/routes/v2/__pycache__/products.cpython-312.pyc deleted file mode 100644 index c6c87d4..0000000 Binary files a/routes/v2/__pycache__/products.cpython-312.pyc and /dev/null differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..28b57c1 --- /dev/null +++ b/setup.py @@ -0,0 +1,58 @@ +""" +Package setup script for the API. +This script is used to package the API application for distribution. +It reads the Python version from the Dockerfile and the application version from the main module. +It also reads the requirements from a requirements.txt file and sets up the package metadata. +""" + +from setuptools import find_packages, setup + + +# Read the Python version from Dockerfile +def get_python_version(): + """Retrieve the Python version from the Dockerfile.""" + with open("Dockerfile", encoding="utf-8") as f: + for line in f: + if line.startswith("FROM python:"): + return line.split(":")[1].split("-")[0].strip() + raise RuntimeError("Version not found.") + + +# Read the app version from the main module +def get_app_version(): + """Retrieve the application version from the main module.""" + with open("app/main.py", encoding="utf-8") as f: + for line in f: + if line.startswith("__version__"): + return line.split("=")[1].strip().strip('"').strip("'") + raise RuntimeError("App version not found in app.main module.") + + +def get_requirements(): + """Retrieve the requirements from the requirements.txt file.""" + with open("requirements.txt", encoding="utf-8") as f: + return [line.strip() for line in f if line.strip() and not line.startswith("#")] + + +setup( + name="chocomax-api", + version=get_app_version(), + url="https://github.com/TheChocoMax/API", + description="ChocoMax API", + author="Vianpyro", + packages=find_packages(where="app", exclude=["tests*"]), + package_dir={"": "app"}, + include_package_data=True, + install_requires=get_requirements(), + python_requires=f">={get_python_version()}", + classifiers=[ + "Programming Language :: Python :: 3", + "Framework :: FastAPI", + "License :: OSI Approved :: MIT License", + ], + entry_points={ + "console_scripts": [ + "chocomax-api=main:main", + ], + }, +) diff --git a/tests/__pycache__/test_home.cpython-312-pytest-8.3.5.pyc b/tests/__pycache__/test_home.cpython-312-pytest-8.3.5.pyc deleted file mode 100644 index 9beef8d..0000000 Binary files a/tests/__pycache__/test_home.cpython-312-pytest-8.3.5.pyc and /dev/null differ diff --git a/tests/test_home.py b/tests/test_home.py index ac0058a..75fbedc 100644 --- a/tests/test_home.py +++ b/tests/test_home.py @@ -1,11 +1,16 @@ +""" +Test the home endpoint of the API. +""" + from fastapi.testclient import TestClient -from main import app +from app.main import app client = TestClient(app) def test_home(): + """Test the home endpoint.""" response = client.get("/") assert response.status_code == 200 - assert response.json() == {"message": "Welcome to the ChocoMax Shop API"} + assert response.json() == {"message": "Welcome to the ChocoMax Shop API!"}