diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..36af9e4
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,93 @@
+#
+# How to build this
+
+name: Build
+
+#
+# Operational Variables
+
+env:
+ MAJOR: 0
+ MINOR: 0
+ PYTHON_VERSION: 3.13.0
+
+#
+# Establish when the workflow is run
+# We do build on every push except when we push onto main (which ought to be subject to branch protection)
+# We do build whenever a PR onto main is closed (see on) and the code is actually merged (see release job if)
+# Why is that okay?
+# Since we're making a PR, we know from the previous workflow run on push that the repo is okay and the PR
+# shows that to us. A PR itself doesn't cause a build, except when it is closed and the changes were merged.
+
+on:
+ push:
+ branches-ignore:
+ - main
+ pull_request_target:
+ branches:
+ - main
+ types:
+ - closed
+
+#
+# Workflow
+
+jobs:
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Checkout out our code
+ uses: actions/checkout@v3
+
+ - name: Calculate Build Context
+ run: |
+ MRMAT_VERSION="${MAJOR}.${MINOR}.${GITHUB_RUN_NUMBER}"
+ if [ "$GITHUB_EVENT_NAME" == 'pull_request_target' && GITHUB_BASE_REF == 'main']; then
+ MRMAT_IS_RELEASE=true
+ echo "::warning ::Building release ${MRMAT_VERSION}"
+ echo "MRMAT_IS_RELEASE=true" >> $GITHUB_ENV
+ else
+ MRMAT_VERSION="${MRMAT_VERSION}.dev0"
+ echo "::warning ::Building version ${MRMAT_VERSION}"
+ fi
+ echo "MRMAT_VERSION=${MRMAT_VERSION}" >> $GITHUB_ENV
+
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Establish a cache for dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.local
+ ~/.cache/pip
+ key: ${{ runner.os }}
+
+ - name: Build
+ run: |
+ export PYTHONUSERBASE=${HOME}/.local
+ pip install --user -r requirements.txt -r requirements.dev.txt
+ PYTHONPATH=${GITHUB_WORKSPACE}/src pytest
+ PYTHONPATH=${GITHUB_WORKSPACE}/src python -m build --wheel -n
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: ${{ always() }}
+ with:
+ name: Test and Coverage
+ path: |
+ build/junit.xml
+ build/coverage.xml
+
+ - name: Conditional Release
+ uses: marvinpinto/action-automatic-releases@latest
+ if: (github.event.pull_request.merged == true && github.base_ref == 'main')
+ with:
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ automatic_release_tag: "${{ env.MRMAT_VERSION }}"
+ prerelease: false
+ title: "Release ${{ env.MRMAT_VERSION }}"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ba552da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,250 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/jetbrains,python
+# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains,python
+
+### JetBrains ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### JetBrains Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+wheels/
+pip-wheel-metadata/
+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/
+pytestdebug.log
+
+# 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/
+doc/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.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
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pythonenv*
+
+# 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/
+
+# profiling data
+.prof
+
+# End of https://www.toptal.com/developers/gitignore/api/jetbrains,python
+
+
+.coverage
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/copyright/MIT.xml b/.idea/copyright/MIT.xml
new file mode 100644
index 0000000..4e0ea94
--- /dev/null
+++ b/.idea/copyright/MIT.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..c3ba54a
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..22e22e1
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/build/test.db
+
+
+
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..064f873
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..be83902
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e69dc1d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/mrmat-python-api-fastapi.iml b/.idea/mrmat-python-api-fastapi.iml
new file mode 100644
index 0000000..24ec1fd
--- /dev/null
+++ b/.idea/mrmat-python-api-fastapi.iml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/pylint.xml b/.idea/pylint.xml
new file mode 100644
index 0000000..86e7be6
--- /dev/null
+++ b/.idea/pylint.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/build.xml b/.idea/runConfigurations/build.xml
new file mode 100644
index 0000000..b921569
--- /dev/null
+++ b/.idea/runConfigurations/build.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/lint.xml b/.idea/runConfigurations/lint.xml
new file mode 100644
index 0000000..58e3152
--- /dev/null
+++ b/.idea/runConfigurations/lint.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/pytest_in_tests.xml b/.idea/runConfigurations/pytest_in_tests.xml
new file mode 100644
index 0000000..e8629e0
--- /dev/null
+++ b/.idea/runConfigurations/pytest_in_tests.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/run.xml b/.idea/runConfigurations/run.xml
new file mode 100644
index 0000000..028572a
--- /dev/null
+++ b/.idea/runConfigurations/run.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..b762af0
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..23353d7
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+imfeldma+02_q@gmail.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..be96b11
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing to this repository
+
+This is reasonably small for you to just fork and raise a PR. Have fun.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index f2f1fd6..263a877 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022 MrMatOrg
+Copyright (c) 2021 Mathieu Imfeld
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/Makefile b/Makefile
new file mode 100644
index 0000000..7578cfa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,46 @@
+#
+# Convenience Makefile
+# Useful reference: https://makefiletutorial.com
+
+GIT_SHA := $(shell git rev-parse --short HEAD)
+VERSION ?= 0.0.0-dev0.${GIT_SHA}
+PYTHON_VERSION := $(shell echo "${VERSION}" | sed -e 's/-dev0\./-dev0+/')
+
+PYTHON_SOURCES := $(shell find src/mrmat_python_api_fastapi -name '*.py')
+PYTHON_TARGET := dist/mrmat_python_api_fastapi-${PYTHON_VERSION}-py3-none-any.whl
+CONTAINER_SOURCES := $(shell find var/container)
+HELM_SOURCES := $(shell find var/helm)
+HELM_TARGET := dist/mrmat-python-api-fastapi-$(VERSION).tgz
+
+all: python container helm
+python: $(PYTHON_TARGET)
+helm: $(HELM_TARGET)
+
+$(PYTHON_TARGET): $(PYTHON_SOURCES)
+ MRMAT_VERSION="${PYTHON_VERSION}" python -mbuild -n --wheel
+
+$(HELM_TARGET): $(HELM_SOURCES) container
+ helm package \
+ --app-version "$(VERSION)" \
+ --version $(VERSION) \
+ --destination dist/ \
+ var/helm
+
+container: $(PYTHON_TARGET) $(CONTAINER_SOURCES)
+ docker build \
+ -f var/container/Dockerfile \
+ -t localhost:5001/mrmat-python-api-fastapi:$(VERSION) \
+ --build-arg MRMAT_VERSION=$(VERSION) \
+ .
+ docker push localhost:5001/mrmat-python-api-fastapi:$(VERSION)
+
+helm-install: $(HELM_TARGET)
+ helm upgrade \
+ mrmat-python-api-fastapi \
+ ${HELM_TARGET} \
+ --install \
+ --create-namespace \
+ --namespace mrmat-python-api-fastapi
+
+clean:
+ rm -rf build dist
diff --git a/README.md b/README.md
index 473dfaa..2e615c5 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-# mrmat-python-api-fastapi
+# MrMat :: Python :: API :: FastAPI
+
+[](https://github.com/MrMatOrg/mrmat-python-api-fastapi/actions/workflows/build.yml)
+
+
Boilerplate (and playground) for a code-first Python FastAPI API, with all the bells and whistles we've come to expect
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..5d4aaf9
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+I'd be surprised if you'd want support for this. But be my guest raising an issue...
+
+| Version | Supported |
+|---------|--------------------|
+| all | :white_check_mark: |
+
+## Reporting a Vulnerability
+
+Raise an issue straight on this repository.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..a6288cb
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,60 @@
+[build-system]
+requires = [
+ 'setuptools==76.0.0',
+ 'wheel==0.45.1'
+]
+build-backend = 'setuptools.build_meta'
+
+[project]
+name = "mrmat-python-api-fastapi"
+description = "A Python API using FastAPI"
+urls = { "Sources" = "https://github.com/MrMatAP/mrmat-python-api-fastapi.git" }
+keywords = ["api", "python", "fastapi"]
+readme = "README.md"
+license = { text = "MIT" }
+authors = [
+ { "name" = "Mathieu Imfeld", "email" = "imfeldma+9jqerw@gmail.com" }
+]
+maintainers = [
+ { "name" = "Mathieu Imfeld", "email" = "imfeldma+9jqerw@gmail.com" }
+]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "License :: OSI Approved :: MIT",
+ "Programming Language :: Python :: 3.12"
+]
+requires-python = ">=3.12"
+dynamic = ["version", "dependencies", "optional-dependencies"]
+
+[tool.setuptools.dynamic]
+version = { attr = "ci.version"}
+dependencies = {file = ["requirements.txt"]}
+optional-dependencies = { dev = {file = ["requirements.dev.txt"] } }
+
+[tool.setuptools.packages.find]
+where = ["src"]
+include = ["mrmat_python_api_fastapi*"]
+namespaces = true
+
+[tool.setuptools.package-data]
+"*" = [".mo", "*.yml", "*.yaml", "*.md", "inventory", "*.j2", "*.html", "*.ico", "*.css", "*.js", "*.svg", "*.woff", "*.eot", "*.ttf"]
+
+[project.scripts]
+mrmat-python-api-fastapi = "mrmat_python_api_fastapi.app:run"
+
+[tool.mypy]
+plugins = [ 'pydantic.mypy' ]
+
+[tool.pydantic-mypy]
+init_forbid_extra = true
+init_typed = true
+warn_required_dynamic_aliases = true
+
+[tool.pytest.ini_options]
+testpaths = 'tests'
+addopts = '--cov=mrmat_python_api_fastapi --cov-report=term --cov-report=xml:build/coverage.xml --junit-xml=build/junit.xml'
+junit_family = 'xunit2'
+log_cli = 1
+log_cli_level = 'INFO'
+log_cli_format = '%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)'
+log_cli_date_format = '%Y-%m-%d %H:%M:%S'
diff --git a/requirements.dev.txt b/requirements.dev.txt
new file mode 100644
index 0000000..16d0fe0
--- /dev/null
+++ b/requirements.dev.txt
@@ -0,0 +1,15 @@
+#
+# This file is for convenience only, to quickly set up what is necessary to develop in some IDE
+# The real runtime requirements are held in pyproject.toml. BE SURE to update both files if necessary.
+
+# Build/Test requirements
+
+setuptools==76.0.0
+build==1.2.2.post1 # MIT
+wheel==0.45.1 # MIT
+
+pytest==8.3.5 # MIT
+pytest-cov==6.0.0 # MIT
+mypy==1.15.0 # MIT
+types-PyYAML==6.0.12.20241230 # Apache 2.0
+httpx==0.28.1 # BSD-3-Clause
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..7e66d3c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+#
+# Runtime requirements
+
+fastapi==0.115.11 # MIT
+sqlalchemy[asyncio]==2.0.40 # MIT
+uvicorn==0.34.0 # BSD 3-Clause
+pydantic==2.10.6 # MIT
diff --git a/src/ci/__init__.py b/src/ci/__init__.py
new file mode 100644
index 0000000..63ed8b0
--- /dev/null
+++ b/src/ci/__init__.py
@@ -0,0 +1,30 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+Build-time only module to determine the version of this project from CI and if
+not provided a reasonable development-time default.
+"""
+
+import os
+
+version = os.environ.get('MRMAT_VERSION', '0.0.0.dev0')
diff --git a/src/mrmat_python_api_fastapi/__init__.py b/src/mrmat_python_api_fastapi/__init__.py
new file mode 100644
index 0000000..32dea2a
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/__init__.py
@@ -0,0 +1,33 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from pydantic import BaseModel
+
+from .config import Config
+app_config = Config.from_json_file()
+from sqlalchemy.orm import DeclarativeBase
+
+class ORMBase(DeclarativeBase):
+ pass
+
+
+class SchemaBase(BaseModel):
+ model_config = {'from_attributes': True}
diff --git a/src/mrmat_python_api_fastapi/apis/__init__.py b/src/mrmat_python_api_fastapi/apis/__init__.py
new file mode 100644
index 0000000..ea100a6
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/__init__.py
@@ -0,0 +1,31 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from pydantic import BaseModel
+
+
+class StatusSchema(BaseModel):
+ """
+ A generic message class
+ """
+ code: int
+ msg: str
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/__init__.py b/src/mrmat_python_api_fastapi/apis/greeting/__init__.py
new file mode 100644
index 0000000..5ba3d52
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/__init__.py
@@ -0,0 +1,25 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .v1 import api_greeting_v1
+from .v2 import api_greeting_v2
+from .v3 import api_greeting_v3
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py b/src/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py
new file mode 100644
index 0000000..bf5e696
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .model import GreetingV1Output # noqa: F401
+from .api import router as api_greeting_v1 # noqa: F401
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v1/api.py b/src/mrmat_python_api_fastapi/apis/greeting/v1/api.py
new file mode 100644
index 0000000..565971d
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v1/api.py
@@ -0,0 +1,41 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from fastapi import APIRouter
+from mrmat_python_api_fastapi.apis.greeting.v1 import GreetingV1Output
+
+router = APIRouter()
+
+
+@router.get('/',
+ response_model=GreetingV1Output,
+ name='get_greeting',
+ description='Get a generic greeting since this version of the greeting API does not have a means to '
+ 'determine who you are',
+ response_description='A JSON-encoded Hello World message')
+async def get_greeting():
+ """
+ Return a Hello World message
+ Returns:
+ A Hello World message
+ """
+ return GreetingV1Output()
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v1/model.py b/src/mrmat_python_api_fastapi/apis/greeting/v1/model.py
new file mode 100644
index 0000000..34c1687
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v1/model.py
@@ -0,0 +1,27 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from pydantic import BaseModel, Field
+
+
+class GreetingV1Output(BaseModel):
+ message: str = Field(default='Hello World', title='A generic greeting message')
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v2/__init__.py b/src/mrmat_python_api_fastapi/apis/greeting/v2/__init__.py
new file mode 100644
index 0000000..561c454
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v2/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .model import GreetingV2Output # noqa: F401
+from .api import router as api_greeting_v2 # noqa: F401
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v2/api.py b/src/mrmat_python_api_fastapi/apis/greeting/v2/api.py
new file mode 100644
index 0000000..2f92a2a
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v2/api.py
@@ -0,0 +1,40 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+Blueprint for the Greeting API in V2
+"""
+
+from typing import Optional
+from fastapi import APIRouter
+from mrmat_python_api_fastapi.apis.greeting.v2 import GreetingV2Output
+
+router = APIRouter()
+
+
+@router.get('/',
+ response_model=GreetingV2Output,
+ name='get_greeting_v2',
+ description='Get a greeting for a given name',
+ response_description='A JSON-encoded greeting for the provided name')
+async def get_greeting(name: Optional[str] = 'Stranger'):
+ return GreetingV2Output(message=f'Hello {name}')
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v2/model.py b/src/mrmat_python_api_fastapi/apis/greeting/v2/model.py
new file mode 100644
index 0000000..d49c247
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v2/model.py
@@ -0,0 +1,27 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from pydantic import BaseModel, Field
+
+
+class GreetingV2Output(BaseModel):
+ message: str = Field(description='A greeting message')
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v3/__init__.py b/src/mrmat_python_api_fastapi/apis/greeting/v3/__init__.py
new file mode 100644
index 0000000..10a0a2c
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v3/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .model import GreetingV3Output # noqa: F401
+from .api import router as api_greeting_v3 # noqa: F401
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v3/api.py b/src/mrmat_python_api_fastapi/apis/greeting/v3/api.py
new file mode 100644
index 0000000..50a0b89
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v3/api.py
@@ -0,0 +1,35 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from fastapi import APIRouter
+from mrmat_python_api_fastapi.apis.greeting.v3 import GreetingV3Output
+
+router = APIRouter()
+
+
+@router.get('/',
+ name='get_greeting_v3',
+ summary='Get a greeting for the authenticated name',
+ description='This version of the greeting API knows who you are',
+ response_model=GreetingV3Output)
+async def get_greeting():
+ return GreetingV3Output(message='Hello foo')
diff --git a/src/mrmat_python_api_fastapi/apis/greeting/v3/model.py b/src/mrmat_python_api_fastapi/apis/greeting/v3/model.py
new file mode 100644
index 0000000..ab05a30
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/greeting/v3/model.py
@@ -0,0 +1,31 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+Greeting API v3 Model
+"""
+
+from pydantic import BaseModel, Field
+
+
+class GreetingV3Output(BaseModel):
+ message: str = Field('A greeting message')
diff --git a/src/mrmat_python_api_fastapi/apis/healthz/__init__.py b/src/mrmat_python_api_fastapi/apis/healthz/__init__.py
new file mode 100644
index 0000000..6ce9ee0
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/healthz/__init__.py
@@ -0,0 +1,23 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .api import router as api_healthz # noqa: F401
diff --git a/src/mrmat_python_api_fastapi/apis/healthz/api.py b/src/mrmat_python_api_fastapi/apis/healthz/api.py
new file mode 100644
index 0000000..17d10db
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/healthz/api.py
@@ -0,0 +1,60 @@
+# MIT License
+#
+# Copyright (c) 2022 MrMat
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+Blueprint for the Healthz API
+"""
+
+from fastapi import APIRouter
+from pydantic import BaseModel
+
+router = APIRouter()
+
+class HealthzSchema(BaseModel):
+ status: str
+
+class LivenessSchema(BaseModel):
+ status: str
+
+class ReadinessSchema(BaseModel):
+ status: str
+
+@router.get('/',
+ name='Get application health',
+ description='Get an indication of application health',
+ response_model=HealthzSchema)
+async def healthz() -> HealthzSchema:
+ return HealthzSchema(status='OK')
+
+@router.get('/liveness/',
+ name='Get application liveness',
+ description='Get an indication of application liveness',
+ response_model=LivenessSchema)
+async def liveness() -> LivenessSchema:
+ return LivenessSchema(status='OK')
+
+@router.get('/readiness/',
+ name='Get application readiness',
+ description='Get an indication of application readiness',
+ response_model=ReadinessSchema)
+async def readiness() -> ReadinessSchema:
+ return ReadinessSchema(status='OK')
diff --git a/src/mrmat_python_api_fastapi/apis/platform/__init__.py b/src/mrmat_python_api_fastapi/apis/platform/__init__.py
new file mode 100644
index 0000000..efc80fa
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/platform/__init__.py
@@ -0,0 +1,23 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .v1 import api_platform_v1
diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py b/src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py
new file mode 100644
index 0000000..6516d40
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py
@@ -0,0 +1,31 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .api import router as api_platform_v1
+from .schema import (
+ ResourceSchema,
+ ResourceInputSchema,
+ ResourceListSchema,
+ OwnerSchema,
+ OwnerInputSchema,
+ OwnerListSchema
+)
\ No newline at end of file
diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/api.py b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py
new file mode 100644
index 0000000..3a54c46
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py
@@ -0,0 +1,287 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from fastapi import APIRouter, Depends, status, HTTPException
+from fastapi.responses import Response
+from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.orm import Session
+
+from mrmat_python_api_fastapi.db import get_db
+from mrmat_python_api_fastapi.apis import StatusSchema
+from .db import Resource, Owner
+from .schema import (
+ ResourceSchema,
+ ResourceInputSchema,
+ ResourceListSchema,
+ OwnerSchema,
+ OwnerInputSchema,
+ OwnerListSchema
+)
+
+router = APIRouter()
+
+
+@router.get('/resources',
+ name='list_resources',
+ summary='List all known resources',
+ description='Returns all currently known resources and their metadata',
+ response_model=ResourceListSchema)
+async def get_resources(session: Session = Depends(get_db)) -> ResourceListSchema:
+ try:
+ resources = session.query(Resource).all()
+ return ResourceListSchema(resources=[ResourceSchema(uid=r.uid,
+ name=r.name,
+ owner_uid=r.owner_uid)
+ for r in resources])
+ except SQLAlchemyError as e:
+ # Handle the error appropriately, maybe raise an HTTPException
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.get('/resources/{uid}',
+ name='get_resource',
+ summary='Get a single resource',
+ description='Return a single resource identified by its resource id',
+ responses={
+ status.HTTP_404_NOT_FOUND: {
+ 'description': 'The resource was not found',
+ 'model': StatusSchema
+ },
+ status.HTTP_200_OK: {
+ 'description': 'The requested resource',
+ 'model': ResourceSchema
+ }
+ })
+async def get_resource(uid: str,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ resource = session.get(Resource, uid)
+ if not resource:
+ response.status_code = 404
+ return StatusSchema(code=404, msg='The resource was not found')
+ return resource
+ except SQLAlchemyError as e:
+ # Handle the error appropriately, maybe raise an HTTPException
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+@router.post('/resources',
+ name='create_resource',
+ summary='Create a resource',
+ description='Create a resource owned by the authenticated user',
+ response_model=ResourceSchema)
+async def create_resource(data: ResourceInputSchema,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ resource = Resource(name=data.name, owner_uid=data.owner_uid)
+ session.add(resource)
+ session.commit()
+ response.status_code = 201
+ return resource
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.put('/resources/{uid}',
+ name='modify_resource',
+ summary='Modify a resource',
+ description='Modify a resource by its resource id',
+ responses={
+ status.HTTP_200_OK: {
+ 'description': 'The resource was modified',
+ 'model': ResourceSchema
+ },
+ status.HTTP_404_NOT_FOUND: {
+ 'description': 'The resource was not found',
+ 'model': StatusSchema
+ }
+ },
+ response_model=ResourceSchema)
+async def modify_resource(uid: str,
+ data: ResourceInputSchema,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ resource = session.get(Resource, uid)
+ if not resource:
+ response.status_code = 404
+ return StatusSchema(code=404, msg='Not found')
+ resource.name = data.name
+ session.add(resource)
+ session.commit()
+ return resource
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.delete('/resources/{uid}',
+ name='remove_resource',
+ summary='Remove a resource',
+ description='Remove a resource by its resource id',
+ status_code=204,
+ responses={
+ status.HTTP_204_NO_CONTENT: {
+ 'description': 'The resource was removed'
+ },
+ status.HTTP_410_GONE: {
+ 'description': 'The resource was already gone',
+ 'model': StatusSchema
+ }
+ })
+async def remove_resource(uid: str,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ resource = session.get(Resource, uid)
+ if not resource:
+ response.status_code = 410
+ return StatusSchema(code=410, msg='The resource was already gone')
+ session.delete(resource)
+ session.commit()
+ response.status_code = 204
+ return {}
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+
+@router.get('/owners',
+ name='list_owners',
+ summary='List all known owners',
+ description='Returns all currently known owners and their metadata',
+ response_model=OwnerListSchema)
+async def get_owners(session: Session = Depends(get_db)):
+ try:
+ owners = session.query(Owner).all()
+ return OwnerListSchema(owners=[OwnerSchema(uid=o.uid, name=o.name) for o in owners])
+ except SQLAlchemyError as e:
+ # Handle the error appropriately, maybe raise an HTTPException
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.get('/owners/{uid}',
+ name='get_owner',
+ summary='Get a single owner',
+ description='Return a single owner identified by its owner id',
+ responses={
+ status.HTTP_404_NOT_FOUND: {
+ 'description': 'The owner was not found',
+ 'model': StatusSchema
+ },
+ status.HTTP_200_OK: {
+ 'description': 'The requested owner',
+ 'model': OwnerSchema
+ }
+ },
+ response_model=OwnerSchema)
+async def get_owner(uid: str,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ owner = session.get(Owner, uid)
+ if not owner:
+ response.status_code = 404
+ return StatusSchema(code=404, msg='The owner was not found')
+ return owner
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.post('/owners',
+ name='create_owner',
+ summary='Create a owner',
+ description='Create a owner',
+ response_model=OwnerSchema)
+async def create_owner(data: OwnerInputSchema,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ owner = Owner(name=data.name, client_id='TODO')
+ session.add(owner)
+ session.commit()
+ response.status_code = 201
+ return owner
+ except SQLAlchemyError as e:
+ # Handle the error appropriately, maybe raise an HTTPException
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.put('/owners/{uid}',
+ name='modify_owner',
+ summary='Modify a owner',
+ description='Modify a owner by its owner id',
+ responses={
+ status.HTTP_200_OK: {
+ 'description': 'The owner was modified',
+ 'model': OwnerSchema
+ },
+ status.HTTP_404_NOT_FOUND: {
+ 'description': 'The owner was not found',
+ 'model': StatusSchema
+ }
+ },
+ response_model=OwnerSchema)
+async def modify_owner(uid: str,
+ data: OwnerInputSchema,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ owner = session.get(Owner, uid)
+ if not owner:
+ response.status_code = 404
+ return StatusSchema(code=404, msg='The owner was not found')
+ owner.name = data.name
+ session.add(owner)
+ session.commit()
+ return owner
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
+
+
+@router.delete('/owners/{uid}',
+ name='remove_owner',
+ summary='Remove a owner',
+ description='Remove a owner by its owner id',
+ status_code=204,
+ responses={
+ status.HTTP_204_NO_CONTENT: {
+ 'description': 'The owner was removed'
+ },
+ status.HTTP_410_GONE: {
+ 'description': 'The owner was already gone',
+ 'model': StatusSchema
+ }
+ })
+async def remove_owner(uid: str,
+ response: Response,
+ session: Session = Depends(get_db)):
+ try:
+ owner = session.get(Owner, uid)
+ if not owner:
+ response.status_code = status.HTTP_410_GONE
+ return
+ session.delete(owner)
+ session.commit()
+ response.status_code = status.HTTP_204_NO_CONTENT
+ except SQLAlchemyError as e:
+ raise HTTPException(status_code=500, detail="A database error occurred") from e
diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/db.py b/src/mrmat_python_api_fastapi/apis/platform/v1/db.py
new file mode 100644
index 0000000..fb90a02
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/platform/v1/db.py
@@ -0,0 +1,48 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import uuid
+from sqlalchemy import ForeignKey, String, UniqueConstraint, UUID
+from sqlalchemy.orm import relationship, Mapped, mapped_column
+
+from mrmat_python_api_fastapi import ORMBase
+
+
+class Owner(ORMBase):
+ __tablename__ = 'owners'
+ __schema__ = 'mrmat-python-api-fastapi'
+ uid: Mapped[str] = mapped_column(String, primary_key=True, default=str(uuid.uuid4()))
+
+ client_id: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
+ name: Mapped[str] = mapped_column(String(255), nullable=False)
+ resources: Mapped[list["Resource"]] = relationship('Resource', back_populates='owner')
+
+
+class Resource(ORMBase):
+ __tablename__ = 'resources'
+ __schema__ = 'mrmat-python-api-fastapi'
+ uid: Mapped[str] = mapped_column(String, primary_key=True, default=str(uuid.uuid4()))
+ owner_uid: Mapped[str] = mapped_column(String, ForeignKey('owners.uid'), nullable=False)
+ name: Mapped[str] = mapped_column(String(255), nullable=False)
+
+ owner: Mapped["Owner"] = relationship('Owner', back_populates='resources')
+ __table_args__ = (UniqueConstraint('owner_uid', 'name', name='no_duplicate_names_per_owner'),)
diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py
new file mode 100644
index 0000000..553372c
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py
@@ -0,0 +1,45 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import typing
+
+from mrmat_python_api_fastapi import SchemaBase
+
+
+class OwnerInputSchema(SchemaBase):
+ name: str
+
+class OwnerSchema(OwnerInputSchema):
+ uid: str
+
+class OwnerListSchema(SchemaBase):
+ owners: typing.Sequence[OwnerSchema]
+
+class ResourceInputSchema(SchemaBase):
+ name: str
+ owner_uid: str
+
+class ResourceSchema(ResourceInputSchema):
+ uid: str
+
+class ResourceListSchema(SchemaBase):
+ resources: typing.Sequence[ResourceSchema]
diff --git a/src/mrmat_python_api_fastapi/app.py b/src/mrmat_python_api_fastapi/app.py
new file mode 100644
index 0000000..0f00470
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/app.py
@@ -0,0 +1,49 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from fastapi import FastAPI
+
+from mrmat_python_api_fastapi.apis.healthz import api_healthz
+from mrmat_python_api_fastapi.apis.greeting import api_greeting_v1, api_greeting_v2, api_greeting_v3
+from mrmat_python_api_fastapi.apis.platform import api_platform_v1
+
+app = FastAPI(title='MrMat :: Python :: API :: FastAPI')
+app.include_router(api_healthz, prefix='/api/healthz', tags=['health'])
+app.include_router(api_greeting_v1, prefix='/api/greeting/v1', tags=['greeting'])
+app.include_router(api_greeting_v2, prefix='/api/greeting/v2', tags=['greeting'])
+app.include_router(api_greeting_v3, prefix='/api/greeting/v3', tags=['greeting'])
+app.include_router(api_platform_v1, prefix='/api/platform/v1', tags=['platform'])
+
+
+@app.get('/')
+def index():
+ return {'Hello': 'World'}
+
+def run() -> int:
+ """
+ This is the main entry point for the application when running via the CLI wrapper
+ Returns:
+ - int: The exit code
+ """
+ import uvicorn
+ uvicorn.run(app, host='0.0.0.0', port=8000)
+ return 0
diff --git a/src/mrmat_python_api_fastapi/config.py b/src/mrmat_python_api_fastapi/config.py
new file mode 100644
index 0000000..a7b2691
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/config.py
@@ -0,0 +1,43 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import json
+import secrets
+
+
+class Config:
+ """
+ A class to deal with application configuration
+ """
+ secret_key: str = secrets.token_urlsafe(16)
+ db_url: str = 'sqlite://'
+
+ @staticmethod
+ def from_json_file(file: str | None = os.getenv('APP_CONFIG')):
+ runtime_config = Config()
+ if file and os.path.exists(file):
+ with open(file, 'r', encoding='UTF-8') as c:
+ file_config = json.load(c)
+ runtime_config.secret_key = file_config.get_owner('secret_key', secrets.token_urlsafe(16))
+ runtime_config.db_url = file_config.get_owner('db_url', 'sqlite://')
+ return runtime_config
diff --git a/src/mrmat_python_api_fastapi/db.py b/src/mrmat_python_api_fastapi/db.py
new file mode 100644
index 0000000..4ae9335
--- /dev/null
+++ b/src/mrmat_python_api_fastapi/db.py
@@ -0,0 +1,39 @@
+# MIT License
+#
+# Copyright (c) 2022 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from functools import lru_cache
+
+from sqlalchemy import create_engine
+from sqlalchemy.orm import Session, sessionmaker
+
+from mrmat_python_api_fastapi import app_config, ORMBase
+
+
+@lru_cache
+def get_db() -> Session:
+ if app_config.db_url.startswith('sqlite'):
+ engine = create_engine(url=app_config.db_url, connect_args={'check_same_thread': False})
+ else:
+ engine = create_engine(url=app_config.db_url)
+ session_local = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+ ORMBase.metadata.create_all(bind=engine)
+ return session_local()
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..786c910
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,50 @@
+# MIT License
+#
+# Copyright (c) 2025 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import pathlib
+import pytest
+import fastapi.testclient
+
+from mrmat_python_api_fastapi import app_config, ORMBase
+from mrmat_python_api_fastapi.app import app
+from mrmat_python_api_fastapi.db import get_db
+
+@pytest.fixture(scope='session')
+def build_path():
+ build = pathlib.Path(__file__).parent.parent.resolve() / 'build'
+ build.mkdir(exist_ok=True)
+ return build
+
+@pytest.fixture(scope='session')
+def test_db_path(build_path) -> pathlib.Path:
+ test_db_path = build_path / "test.db"
+ if test_db_path.exists():
+ test_db_path.unlink()
+ return test_db_path
+
+@pytest.fixture(scope='session')
+def client(test_db_path) -> fastapi.testclient.TestClient:
+ app_config.db_url = f'sqlite:///{test_db_path}'
+ session = get_db()
+ with session.begin():
+ ORMBase.metadata.create_all(session.bind)
+ return fastapi.testclient.TestClient(app)
diff --git a/tests/test_greeting.py b/tests/test_greeting.py
new file mode 100644
index 0000000..fdc667c
--- /dev/null
+++ b/tests/test_greeting.py
@@ -0,0 +1,43 @@
+# MIT License
+#
+# Copyright (c) 2025 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import pytest
+import fastapi.testclient
+
+from mrmat_python_api_fastapi.apis.greeting.v1 import GreetingV1Output
+from mrmat_python_api_fastapi.apis.greeting.v2 import GreetingV2Output
+
+def test_greeting_v1(client: fastapi.testclient.TestClient):
+ response = client.get("/api/greeting/v1")
+ assert response.status_code == 200
+ assert GreetingV1Output.model_validate(response.json(), strict=True).message == 'Hello World'
+
+def test_greeting_v2(client: fastapi.testclient.TestClient):
+ response = client.get("/api/greeting/v2")
+ assert response.status_code == 200
+ assert GreetingV2Output.model_validate(response.json(), strict=True).message == 'Hello Stranger'
+
+@pytest.mark.parametrize('name', ['MrMat', 'Chris', 'Mihal', 'Alexandre', 'Jerome'])
+def test_greeting_v2_custom(client: fastapi.testclient.TestClient, name: str):
+ response = client.get("/api/greeting/v2", params=dict(name=name))
+ assert response.status_code == 200
+ assert GreetingV2Output.model_validate(response.json(), strict=True).message == f'Hello {name}'
diff --git a/tests/test_healthz.py b/tests/test_healthz.py
new file mode 100644
index 0000000..d8779f9
--- /dev/null
+++ b/tests/test_healthz.py
@@ -0,0 +1,40 @@
+# MIT License
+#
+# Copyright (c) 2025 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import fastapi.testclient
+
+from mrmat_python_api_fastapi.apis.healthz.api import HealthzSchema, LivenessSchema, ReadinessSchema
+
+def test_healthz(client: fastapi.testclient.TestClient):
+ response = client.get("/api/healthz")
+ assert response.status_code == 200
+ assert HealthzSchema.model_validate(response.json(), strict=True)
+
+def test_liveness(client: fastapi.testclient.TestClient):
+ response = client.get("/api/healthz/liveness")
+ assert response.status_code == 200
+ assert LivenessSchema.model_validate(response.json(), strict=True)
+
+def test_readiness(client: fastapi.testclient.TestClient):
+ response = client.get("/api/healthz/readiness")
+ assert response.status_code == 200
+ assert ReadinessSchema.model_validate(response.json(), strict=True)
diff --git a/tests/test_platform.py b/tests/test_platform.py
new file mode 100644
index 0000000..bf5538c
--- /dev/null
+++ b/tests/test_platform.py
@@ -0,0 +1,101 @@
+# MIT License
+#
+# Copyright (c) 2025 Mathieu Imfeld
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import fastapi.testclient
+
+from mrmat_python_api_fastapi.apis.platform.v1 import (
+ ResourceSchema,
+ ResourceInputSchema,
+ ResourceListSchema,
+ OwnerSchema,
+ OwnerInputSchema,
+ OwnerListSchema
+)
+
+def test_platform_v1(client: fastapi.testclient.TestClient):
+ response = client.get('/api/platform/v1/owners')
+ assert response.status_code == 200
+ assert len(OwnerListSchema.model_validate(response.json(), strict=True).owners) == 0
+
+ owner = OwnerInputSchema(name='test-owner')
+ response = client.post('/api/platform/v1/owners', json=owner.model_dump(mode='json'))
+ assert response.status_code == 201
+ owner_created = OwnerSchema.model_validate(response.json(), strict=True)
+ assert owner_created.uid is not None
+
+ response = client.get(f'/api/platform/v1/owners/{owner_created.uid}')
+ assert response.status_code == 200
+ owner_retrieved = OwnerSchema.model_validate(response.json(), strict=True)
+ assert owner_created == owner_retrieved
+
+ response = client.get('/api/platform/v1/resources')
+ assert response.status_code == 200
+ assert len(ResourceListSchema.model_validate(response.json(), strict=True).resources) == 0
+
+ resource = ResourceInputSchema(name='test-resource', owner_uid=owner_created.uid)
+ response = client.post('/api/platform/v1/resources', json=resource.model_dump(mode='json'))
+ assert response.status_code == 201
+ resource_created = ResourceSchema.model_validate(response.json(), strict=True)
+ assert resource_created.uid is not None
+ assert resource_created.owner_uid == owner_created.uid
+
+ response = client.get(f'/api/platform/v1/resources/{resource_created.uid}')
+ assert response.status_code == 200
+ resource_retrieved = ResourceSchema.model_validate(response.json(), strict=True)
+ assert resource_created == resource_retrieved
+
+ response = client.get('/api/platform/v1/owners')
+ assert response.status_code == 200
+ assert len(OwnerListSchema.model_validate(response.json(), strict=True).owners) == 1
+
+ response = client.get('/api/platform/v1/resources')
+ assert response.status_code == 200
+ assert len(ResourceListSchema.model_validate(response.json(), strict=True).resources) == 1
+
+ owner = OwnerInputSchema(name='modified-owner')
+ response = client.put(f'/api/platform/v1/owners/{owner_created.uid}', json=owner.model_dump(mode='json'))
+ assert response.status_code == 200
+ owner_updated = OwnerSchema.model_validate(response.json(), strict=True)
+ assert owner_updated.uid == owner_created.uid
+ assert owner_updated.name == 'modified-owner'
+
+ response = client.get(f'/api/platform/v1/resources/{resource_created.uid}')
+ assert response.status_code == 200
+ resource_retrieved = ResourceSchema.model_validate(response.json(), strict=True)
+ assert resource_retrieved.owner_uid == owner_updated.uid
+
+ resource = ResourceInputSchema(name='modified-resource', owner_uid=owner_updated.uid)
+ response = client.put(f'/api/platform/v1/resources/{resource_created.uid}', json=resource.model_dump(mode='json'))
+ assert response.status_code == 200
+ resource_updated = ResourceSchema.model_validate(response.json(), strict=True)
+ assert resource_updated.uid == resource_created.uid
+ assert resource_updated.owner_uid == owner_updated.uid
+
+ response = client.delete(f'/api/platform/v1/resources/{resource_created.uid}')
+ assert response.status_code == 204
+ response = client.delete(f'/api/platform/v1/resources/{resource_created.uid}')
+ assert response.status_code == 410
+
+ response = client.delete(f'/api/platform/v1/owners/{owner_created.uid}')
+ assert response.status_code == 204
+ response = client.delete(f'/api/platform/v1/owners/{owner_created.uid}')
+ assert response.status_code == 410
diff --git a/var/container/Dockerfile b/var/container/Dockerfile
new file mode 100644
index 0000000..6a64184
--- /dev/null
+++ b/var/container/Dockerfile
@@ -0,0 +1,17 @@
+FROM python:3.12-alpine AS build
+ARG MRMAT_VERSION="0.0.0.dev0"
+ADD dist/mrmat_python_api_fastapi-*-py3-none-any.whl /
+RUN pip install --user /mrmat_python_api_fastapi-*-py3-none-any.whl
+
+FROM python:3.12-alpine
+ARG MRMAT_VERSION="0.0.0.dev0"
+LABEL VERSION=$MRMAT_VERSION
+RUN addgroup -g 1000 app && \
+ adduser -g 'App User' -u 1000 -G app -D app
+COPY --from=build /root/.local /home/app/.local
+RUN chown -R 1000:1000 /home/app/.local
+
+USER app:app
+EXPOSE 8000
+#CMD ["/home/app/.local/bin/uvicorn", "--host", "0.0.0.0", "--port", "8000", "mrmat_python_api_fastapi.app:app"]
+CMD ["/home/app/.local/bin/mrmat-python-api-fastapi"]
diff --git a/var/helm/.helmignore b/var/helm/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/var/helm/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/var/helm/Chart.yaml b/var/helm/Chart.yaml
new file mode 100644
index 0000000..d91f08e
--- /dev/null
+++ b/var/helm/Chart.yaml
@@ -0,0 +1,15 @@
+apiVersion: v2
+name: mrmat-python-api-fastapi
+description: A Helm chart for MrMat Python API FastAPI
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: "0.0.0"
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.0.0.dev0"
diff --git a/var/helm/templates/NOTES.txt b/var/helm/templates/NOTES.txt
new file mode 100644
index 0000000..f029577
--- /dev/null
+++ b/var/helm/templates/NOTES.txt
@@ -0,0 +1 @@
+# MrMat :: Python :: API :: FastAPI installed
diff --git a/var/helm/templates/deployment.yaml b/var/helm/templates/deployment.yaml
new file mode 100644
index 0000000..c20f14c
--- /dev/null
+++ b/var/helm/templates/deployment.yaml
@@ -0,0 +1,56 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: mrmat-python-api-fastapi
+ labels:
+ app.kubernetes.io/name: mrmat-python-api-fastapi
+ app.kubernetes.io/part-of: mrmat-python-api-fastapi
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
+ template:
+ metadata:
+ labels:
+ sidecar.istio.io/inject: "true"
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
+ app.kubernetes.io/name: mrmat-python-api-fastapi
+ app.kubernetes.io/part-of: mrmat-python-api-fastapi
+ spec:
+ serviceAccountName: {{ .Values.serviceAccount.name }}
+ containers:
+ - name: mrmat-python-api-fastapi
+ image: {{ .Values.pod.repository }}:{{ .Chart.AppVersion }}
+ imagePullPolicy: {{ .Values.pod.imagePullPolicy }}
+ ports:
+ - name: http
+ containerPort: {{ .Values.pod.port }}
+ protocol: TCP
+ securityContext:
+ capabilities:
+ drop:
+ - ALL
+ readOnlyRootFilesystem: true
+ runAsNonRoot: true
+ runAsUser: 1000
+ livenessProbe:
+ httpGet:
+ path: /api/healthz/liveness/
+ port: {{ .Values.pod.port }}
+ readinessProbe:
+ httpGet:
+ path: /api/healthz/readiness/
+ port: {{ .Values.pod.port }}
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - topologyKey: "kubernetes.io/hostname"
+ labelSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values:
+ - {{ .Values.pod.name }}
diff --git a/var/helm/templates/httproute.yaml b/var/helm/templates/httproute.yaml
new file mode 100644
index 0000000..7c76989
--- /dev/null
+++ b/var/helm/templates/httproute.yaml
@@ -0,0 +1,34 @@
+{{- if .Values.httproute.enabled -}}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ .Values.httproute.name }}
+ labels:
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
+ app.kubernetes.io/name: {{ .Values.httproute.name }}
+ app.kubernetes.io/part-of: mrmat-python-api-fastapi
+spec:
+ hostnames:
+ {{- range .Values.httproute.hostnames }}
+ - {{ . | quote }}
+ {{- end }}
+ - mrmat-python-api-fastapi.local
+ parentRefs:
+ {{- range .Values.httproute.parents }}
+ - group: gateway.networking.k8s.io
+ kind: Gateway
+ name: {{ .name }}
+ namespace: {{ .namespace }}
+ {{- end }}
+ rules:
+ - backendRefs:
+ - kind: Service
+ name: {{ .Values.service.name }}
+ port: {{ .Values.service.port }}
+ weight: 1
+ matches:
+ - path:
+ type: PathPrefix
+ value: /
+{{- end -}}
diff --git a/var/helm/templates/ingress.yaml b/var/helm/templates/ingress.yaml
new file mode 100644
index 0000000..5bdb791
--- /dev/null
+++ b/var/helm/templates/ingress.yaml
@@ -0,0 +1,43 @@
+{{- if .Values.ingress.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "helm.fullname" . }}
+ labels:
+ {{- include "helm.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- with .Values.ingress.className }}
+ ingressClassName: {{ . }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls }}
+ - hosts:
+ {{- range .hosts }}
+ - {{ . | quote }}
+ {{- end }}
+ secretName: {{ .secretName }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- with .pathType }}
+ pathType: {{ . }}
+ {{- end }}
+ backend:
+ service:
+ name: {{ include "helm.fullname" $ }}
+ port:
+ number: {{ $.Values.service.port }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/var/helm/templates/service.yaml b/var/helm/templates/service.yaml
new file mode 100644
index 0000000..3f38c96
--- /dev/null
+++ b/var/helm/templates/service.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Values.service.name }}
+ labels:
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
+ app.kubernetes.io/name: {{ .Values.service.name }}
+ app.kubernetes.io/part-of: mrmat-python-api-fastapi
+spec:
+ type: ClusterIP
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: {{ .Values.pod.port }}
+ protocol: TCP
+ name: http
+ selector:
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
diff --git a/var/helm/templates/serviceaccount.yaml b/var/helm/templates/serviceaccount.yaml
new file mode 100644
index 0000000..88b1e7a
--- /dev/null
+++ b/var/helm/templates/serviceaccount.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ .Values.serviceAccount.name }}
+ labels:
+ app: {{ .Values.pod.name }}
+ version: {{ .Chart.AppVersion }}
+ app.kubernetes.io/name: {{ .Values.serviceAccount.name }}
+ app.kubernetes.io/part-of: mrmat-python-api-fastapi
+automountServiceAccountToken: true
diff --git a/var/helm/values.yaml b/var/helm/values.yaml
new file mode 100644
index 0000000..25a7a29
--- /dev/null
+++ b/var/helm/values.yaml
@@ -0,0 +1,46 @@
+#
+# Default values
+
+# This is to override the chart name.
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+ name: mrmat-python-api-fastapi-sa
+
+pod:
+ name: mrmat-python-api-fastapi
+ replicas: 2
+ repository: mrmat-registry:5000/mrmat-python-api-fastapi
+ imagePullPolicy: IfNotPresent
+ port: 8000
+
+service:
+ name: mrmat-python-api-fastapi-svc
+ port: 80
+
+httproute:
+ enabled: true
+ name: mrmat-python-api-fastapi-httproute
+ hostnames:
+ - mrmat-python-api-fastapi.local
+ parents:
+ - name: istio-ingress
+ namespace: edge
+
+ingress:
+ enabled: false
+ name: mrmat-python-api-fastapi-ing
+ className: ""
+ annotations: { }
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: mrmat-python-api-fastapi.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: [ ]
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local