From 18d0d463b6a26fa3755fe17778d1d2c62663589d Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Tue, 15 Mar 2022 19:37:03 +0100 Subject: [PATCH 01/33] Scaffolding --- .github/workflows/build.yml | 95 ++++ .gitignore | 250 ++++++++++ .idea/.gitignore | 8 + .idea/copyright/MIT.xml | 7 + .idea/copyright/profiles_settings.xml | 3 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/markdown.xml | 10 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/mrmat-python-api-fastapi.iml | 12 + .idea/runConfigurations/run.xml | 16 + .idea/vcs.xml | 6 + .pylintrc | 441 ++++++++++++++++++ CODE_OF_CONDUCT.md | 128 +++++ CONTRIBUTING.md | 3 + README.md | 6 +- ci/__init__.py | 53 +++ mrmat_python_api_fastapi/__init__.py | 22 + mrmat_python_api_fastapi/apis/__init__.py | 43 ++ .../apis/greeting/__init__.py | 43 ++ .../apis/greeting/v1/__init__.py | 46 ++ .../apis/greeting/v1/api.py | 63 +++ .../apis/greeting/v1/model.py | 49 ++ .../apis/greeting/v2/__init__.py | 24 + .../apis/greeting/v2/api.py | 39 ++ .../apis/greeting/v2/model.py | 27 ++ .../apis/greeting/v3/__init__.py | 24 + .../apis/greeting/v3/api.py | 35 ++ .../apis/greeting/v3/model.py | 31 ++ .../apis/healthz/__init__.py | 23 + mrmat_python_api_fastapi/apis/healthz/api.py | 37 ++ mrmat_python_api_fastapi/cui.py | 39 ++ pyproject.toml | 3 + pytest.ini | 12 + requirements.txt | 20 + setup.cfg | 30 ++ 36 files changed, 1665 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/copyright/MIT.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/mrmat-python-api-fastapi.iml create mode 100644 .idea/runConfigurations/run.xml create mode 100644 .idea/vcs.xml create mode 100644 .pylintrc create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 ci/__init__.py create mode 100644 mrmat_python_api_fastapi/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v1/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v1/api.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v1/model.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v2/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v2/api.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v2/model.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v3/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v3/api.py create mode 100644 mrmat_python_api_fastapi/apis/greeting/v3/model.py create mode 100644 mrmat_python_api_fastapi/apis/healthz/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/healthz/api.py create mode 100644 mrmat_python_api_fastapi/cui.py create mode 100644 pyproject.toml create mode 100644 pytest.ini create mode 100644 requirements.txt create mode 100644 setup.cfg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2072105 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +# +# How to build this + +name: Build + +# +# Operational Variables + +env: + MAJOR: 0 + MINOR: 0 + PYTHON_VERSION: 3.10.1 + +# +# 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@v2 + + - 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@v2 + with: + path: | + ~/.local + ~/.cache/pip + key: ${{ runner.os }} + + - name: Build + run: | + export PYTHONUSERBASE=${HOME}/.local + pip install --user wheel + pip install --user -r requirements.txt + ${PYTHONUSERBASE}/bin/pylint ${GITHUB_WORKSPACE}/mrmat_python_api_fastapi + PYTHONPATH=${GITHUB_WORKSPACE} python -m pytest --cov=mrmat_python_api_fastapi + PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n + + - name: Upload test results + uses: actions/upload-artifact@v2 + 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..add8f94 --- /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/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..c44665a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ 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..687f643 --- /dev/null +++ b/.idea/mrmat-python-api-fastapi.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/run.xml b/.idea/runConfigurations/run.xml new file mode 100644 index 0000000..e40b81b --- /dev/null +++ b/.idea/runConfigurations/run.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..d735031 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,441 @@ +# This Pylint rcfile contains a best-effort configuration to uphold the +# best-practices and style described in the Google Python style guide: +# https://google.github.io/styleguide/pyguide.html +# +# Its canonical open-source location is: +# https://google.github.io/styleguide/pylintrc + +[MASTER] + +# Files or directories to be skipped. They should be base names, not paths. +ignore=third_party + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Pickle collected data for later comparisons. +persistent=no + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=abstract-method, + apply-builtin, + arguments-differ, + attribute-defined-outside-init, + backtick, + bad-option-value, + basestring-builtin, + buffer-builtin, + c-extension-no-member, + consider-using-enumerate, + cmp-builtin, + cmp-method, + coerce-builtin, + coerce-method, + delslice-method, + div-method, + duplicate-code, + eq-without-hash, + execfile-builtin, + file-builtin, + filter-builtin-not-iterating, + fixme, + getslice-method, + global-statement, + hex-method, + idiv-method, + implicit-str-concat-in-sequence, + import-error, + import-self, + import-star-module-level, + inconsistent-return-statements, + input-builtin, + intern-builtin, + invalid-str-codec, + locally-disabled, + long-builtin, + long-suffix, + map-builtin-not-iterating, + misplaced-comparison-constant, + missing-function-docstring, + metaclass-assignment, + next-method-called, + next-method-defined, + no-absolute-import, + no-else-break, + no-else-continue, + no-else-raise, + no-else-return, + no-init, # added + no-member, + no-name-in-module, + no-self-use, + nonzero-method, + oct-method, + old-division, + old-ne-operator, + old-octal-literal, + old-raise-syntax, + parameter-unpacking, + print-statement, + raising-string, + range-builtin-not-iterating, + raw_input-builtin, + rdiv-method, + reduce-builtin, + relative-import, + reload-builtin, + round-builtin, + setslice-method, + signature-differs, + standarderror-builtin, + suppressed-message, + sys-max-int, + too-few-public-methods, + too-many-ancestors, + too-many-arguments, + too-many-boolean-expressions, + too-many-branches, + too-many-instance-attributes, + too-many-locals, + too-many-nested-blocks, + too-many-public-methods, + too-many-return-statements, + too-many-statements, + trailing-newlines, + unichr-builtin, + unicode-builtin, + unnecessary-pass, + unpacking-in-except, + useless-else-on-loop, + useless-object-inheritance, + useless-suppression, + using-cmp-argument, + wrong-import-order, + xrange-builtin, + zip-builtin-not-iterating, + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". This option is deprecated +# and it will be removed in Pylint 2.0. +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=main,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl + +# Regular expression matching correct function names +function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct constant names +const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Regular expression matching correct attribute names +attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ + +# Regular expression matching correct argument names +argument-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=^_?[A-Z][a-zA-Z0-9]*$ + +# Regular expression matching correct module names +module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ + +# Regular expression matching correct method names +method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=10 + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[FORMAT] + +# Maximum number of characters on a single line (Google has 80). +max-line-length=120 + +# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt +# lines made too long by directives to pytype. + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=(?x)( + ^\s*(\#\ )??$| + ^\s*(from\s+\S+\s+)?import\s+.+$) + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=yes + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check= + +# Maximum number of lines in a module +max-module-lines=99999 + +# String used as indentation unit. The internal Google style guide mandates 2 +# spaces. Google's externaly-published style guide says 4, consistent with +# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google +# projects (like TensorFlow). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=TODO + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=yes + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging,absl.logging,tensorflow.io.logging + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec, + sets + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant, absl + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls, + class_ + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=StandardError, + Exception, + BaseException 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/README.md b/README.md index 473dfaa..231bc43 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ -# mrmat-python-api-fastapi +# MrMat :: Python :: API :: FastAPI + +[![Build](https://github.com/MrMatOrg/mrmat-python-api-fastapi/actions/workflows/build.yml/badge.svg)](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/ci/__init__.py b/ci/__init__.py new file mode 100644 index 0000000..07b8d77 --- /dev/null +++ b/ci/__init__.py @@ -0,0 +1,53 @@ +# 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. + +# MIT License +# +# 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 +# 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/mrmat_python_api_fastapi/__init__.py b/mrmat_python_api_fastapi/__init__.py new file mode 100644 index 0000000..e140f4f --- /dev/null +++ b/mrmat_python_api_fastapi/__init__.py @@ -0,0 +1,22 @@ +# 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. + diff --git a/mrmat_python_api_fastapi/apis/__init__.py b/mrmat_python_api_fastapi/apis/__init__.py new file mode 100644 index 0000000..49abd6b --- /dev/null +++ b/mrmat_python_api_fastapi/apis/__init__.py @@ -0,0 +1,43 @@ +# 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. + +# 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. diff --git a/mrmat_python_api_fastapi/apis/greeting/__init__.py b/mrmat_python_api_fastapi/apis/greeting/__init__.py new file mode 100644 index 0000000..49abd6b --- /dev/null +++ b/mrmat_python_api_fastapi/apis/greeting/__init__.py @@ -0,0 +1,43 @@ +# 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. + +# 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. diff --git a/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py b/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py new file mode 100644 index 0000000..ecc8546 --- /dev/null +++ b/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py @@ -0,0 +1,46 @@ +# 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. + +# 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/mrmat_python_api_fastapi/apis/greeting/v1/api.py b/mrmat_python_api_fastapi/apis/greeting/v1/api.py new file mode 100644 index 0000000..447f14e --- /dev/null +++ b/mrmat_python_api_fastapi/apis/greeting/v1/api.py @@ -0,0 +1,63 @@ +# 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. + +# 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/mrmat_python_api_fastapi/apis/greeting/v1/model.py b/mrmat_python_api_fastapi/apis/greeting/v1/model.py new file mode 100644 index 0000000..9a9a2a5 --- /dev/null +++ b/mrmat_python_api_fastapi/apis/greeting/v1/model.py @@ -0,0 +1,49 @@ +# 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. + +# 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/mrmat_python_api_fastapi/apis/greeting/v2/__init__.py b/mrmat_python_api_fastapi/apis/greeting/v2/__init__.py new file mode 100644 index 0000000..561c454 --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/greeting/v2/api.py b/mrmat_python_api_fastapi/apis/greeting/v2/api.py new file mode 100644 index 0000000..41b0dec --- /dev/null +++ b/mrmat_python_api_fastapi/apis/greeting/v2/api.py @@ -0,0 +1,39 @@ +# 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 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: str | None = 'Stranger'): + return GreetingV2Output(message=f'Hello {name}') diff --git a/mrmat_python_api_fastapi/apis/greeting/v2/model.py b/mrmat_python_api_fastapi/apis/greeting/v2/model.py new file mode 100644 index 0000000..d49c247 --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/greeting/v3/__init__.py b/mrmat_python_api_fastapi/apis/greeting/v3/__init__.py new file mode 100644 index 0000000..10a0a2c --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/greeting/v3/api.py b/mrmat_python_api_fastapi/apis/greeting/v3/api.py new file mode 100644 index 0000000..50a0b89 --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/greeting/v3/model.py b/mrmat_python_api_fastapi/apis/greeting/v3/model.py new file mode 100644 index 0000000..ab05a30 --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/healthz/__init__.py b/mrmat_python_api_fastapi/apis/healthz/__init__.py new file mode 100644 index 0000000..6ce9ee0 --- /dev/null +++ b/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/mrmat_python_api_fastapi/apis/healthz/api.py b/mrmat_python_api_fastapi/apis/healthz/api.py new file mode 100644 index 0000000..4a5f093 --- /dev/null +++ b/mrmat_python_api_fastapi/apis/healthz/api.py @@ -0,0 +1,37 @@ +# 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 + +router = APIRouter() + + +@router.get('/', + name='Assess application health', + description='Get an indication of application health') +async def get(): + return {'code': 200, 'message': 'OK'}, 200 + diff --git a/mrmat_python_api_fastapi/cui.py b/mrmat_python_api_fastapi/cui.py new file mode 100644 index 0000000..c397e8e --- /dev/null +++ b/mrmat_python_api_fastapi/cui.py @@ -0,0 +1,39 @@ +# 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 FastAPI + +from mrmat_python_api_fastapi.apis.healthz import api_healthz +from mrmat_python_api_fastapi.apis.greeting.v1 import api_greeting_v1 +from mrmat_python_api_fastapi.apis.greeting.v2 import api_greeting_v2 +from mrmat_python_api_fastapi.apis.greeting.v3 import api_greeting_v3 + +app = FastAPI(title='MrMat :: Python :: API :: FastAPI') +app.include_router(api_healthz, prefix='/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.get('/') +def read_root(): + return {'Hello': 'World'} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7e01ab2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ['setuptools', 'wheel'] +build-backend = 'setuptools.build_meta' diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..84f79e0 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,12 @@ +# +# If you are debugging your tests using PyCharm then comment out the coverage options +# in addopts + +[pytest] +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.txt b/requirements.txt new file mode 100644 index 0000000..fe5df67 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +# +# IMPORTANT: The requirements that actually count are in setup.cfg. This file is here for convenience so you can +# quickly install both build- and test-requirements from within your development environment. DO MAKE +# SURE to update setup.cfg whenever you make changes here. + +# Build/Test requirements + +build~=0.7.0 # MIT +wheel~=0.37.1 # MIT +pylint~=2.12.2 # GPL-2.0-or-later +pytest~=7.0.1 # MIT +pytest-cov~=3.0.0 # MIT +pyjwt~=2.3.0 # MIT +python-keycloak~=0.27.0 # MIT + +# Runtime requirements + +uvicorn~=0.17.6 # BSD +fastapi~=0.75.0 # MIT +pydantic~=1.9.0 # MIT diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0fef03e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[metadata] +name = mrmat-python-api-fastapi +version = attr: ci.version +author = Mathieu Imfeld +author_email = imfeldma+9jqerw@gmail.com +maintainer = Mathieu Imfeld +maintainer_email = imfeldma+9jqerw@gmail.com +long_description = file: README.md +license = MIT +url = https://github.com/MrMatOrg/mrmat-python-api-fastapi +classifiers = + Development Status :: 3 - Alpha + License :: OSI Approved :: MIT + Programming Language :: Python :: 3.9 + +[options] +packages = find: +install_requires = + uvicorn~=0.17.6 + fastapi~=0.75.0 + pydantic~=1.9.0 + +[options.entry_points] +console_scripts = + mrmat-python-api-fastapi = mrmat_python_api_fastapi.cui:main + mrmat-python-api-fastapi-client = mrmat_python_api_fastapi.client:main + +[options.packages.find] +exclude = + ci From bf54ffe778fb93ea319f38355f5c507d11dca19d Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Tue, 15 Mar 2022 19:38:04 +0100 Subject: [PATCH 02/33] Scaffolding --- .idea/pylint.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .idea/pylint.xml 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 From 789d79dbfa20ec14c5cb0a714e991267936bed3b Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:30:58 +0200 Subject: [PATCH 03/33] Added copyright --- .idea/copyright/MIT.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/copyright/MIT.xml b/.idea/copyright/MIT.xml index add8f94..4e0ea94 100644 --- a/.idea/copyright/MIT.xml +++ b/.idea/copyright/MIT.xml @@ -1,7 +1,7 @@ - \ No newline at end of file From d931c5894ac5fe5ce38a1ef3aaa5995f3e1ab3ed Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:31:24 +0200 Subject: [PATCH 04/33] Added azure webapp publishing --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2072105..fd5e49a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,6 +85,12 @@ jobs: build/junit.xml build/coverage.xml + - name: Publish to Azure + uses: azure/webapps-deploy@v2 + with: + app-name: mrmat-python-api-fastapi + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + - name: Conditional Release uses: marvinpinto/action-automatic-releases@latest if: (github.event.pull_request.merged == true && github.base_ref == 'main') From 28fc5e4c22f24796f642575b8cf6bb90cdc5b365 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:31:35 +0200 Subject: [PATCH 05/33] Set Python 3.10 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0fef03e..d468412 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,7 @@ url = https://github.com/MrMatOrg/mrmat-python-api-fastapi classifiers = Development Status :: 3 - Alpha License :: OSI Approved :: MIT - Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 [options] packages = find: From 47552222dd210e67a2dde0edde98131a64116ce8 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:36:35 +0200 Subject: [PATCH 06/33] Fixed build badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 231bc43..307bb27 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MrMat :: Python :: API :: FastAPI -[![Build](https://github.com/MrMatOrg/mrmat-python-api-fastapi/actions/workflows/build.yml/badge.svg)](https://github. -com/MrMatOrg/mrmat-python-api-fastapi/actions/workflows/build.yml) +[![Build](https://github.com/MrMatOrg/mrmat-python-api-fastapi/actions/workflows/build.yml/badge.svg)](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 From 62a6984aea03780d7963b022975423daf737e961 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:40:56 +0200 Subject: [PATCH 07/33] Ignoring missing module docstrings --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index d735031..fd140d3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -90,6 +90,7 @@ disable=abstract-method, map-builtin-not-iterating, misplaced-comparison-constant, missing-function-docstring, + missing-module-docstring, metaclass-assignment, next-method-called, next-method-defined, From 3b914ee10e576a35f26d5c5997fa0f97851f68bf Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 15:44:40 +0200 Subject: [PATCH 08/33] Added an excuse for a test --- tests/test_excuse.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_excuse.py diff --git a/tests/test_excuse.py b/tests/test_excuse.py new file mode 100644 index 0000000..d797aed --- /dev/null +++ b/tests/test_excuse.py @@ -0,0 +1,24 @@ +# 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. + +def test_excuse(): + assert True From 4224528c3bf00269cbeef8859273132f2d16a54d Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 16:18:35 +0200 Subject: [PATCH 09/33] We just want to install our own wheel --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd5e49a..befd9cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,6 +90,7 @@ jobs: with: app-name: mrmat-python-api-fastapi publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + package: ${GITHUB_WORKSPACE}/dist - name: Conditional Release uses: marvinpinto/action-automatic-releases@latest From d0a23d7a4fb1e7ceb9cfffd9fb494ae198e165b7 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 16:19:51 +0200 Subject: [PATCH 10/33] Does it need to be a folder? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index befd9cc..219fb17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: with: app-name: mrmat-python-api-fastapi publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - package: ${GITHUB_WORKSPACE}/dist + package: dist/ - name: Conditional Release uses: marvinpinto/action-automatic-releases@latest From 6e08395ba737091a37050c22a0b400ee96859abd Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 16:44:54 +0200 Subject: [PATCH 11/33] Some extra settings --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 307bb27..2e615c5 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,4 @@ Boilerplate (and playground) for a code-first Python FastAPI API, with all the bells and whistles we've come to expect + From d17227c1db25de6e9e59c640b0d598203f8beb32 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 16:51:54 +0200 Subject: [PATCH 12/33] Crafting an extra requirements.txt just for Azure --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 219fb17..dca3eef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,6 +75,8 @@ jobs: ${PYTHONUSERBASE}/bin/pylint ${GITHUB_WORKSPACE}/mrmat_python_api_fastapi PYTHONPATH=${GITHUB_WORKSPACE} python -m pytest --cov=mrmat_python_api_fastapi PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n + # Hack to make Azure install our locally produced wheel + echo "./mrmat_python_api_fastapi-${MRMAT_VERSION}.dev0-py3-none-any.whl" > dist/requirements.txt - name: Upload test results uses: actions/upload-artifact@v2 From b2d13c5937cfece594500eadf4b99732d19af662 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 17:11:18 +0200 Subject: [PATCH 13/33] Maybe by fully qualifying the path? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dca3eef..721c77b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: PYTHONPATH=${GITHUB_WORKSPACE} python -m pytest --cov=mrmat_python_api_fastapi PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n # Hack to make Azure install our locally produced wheel - echo "./mrmat_python_api_fastapi-${MRMAT_VERSION}.dev0-py3-none-any.whl" > dist/requirements.txt + echo "/tmp/zipdeploy/extracted/mrmat_python_api_fastapi-${MRMAT_VERSION}.dev0-py3-none-any.whl" > dist/requirements.txt - name: Upload test results uses: actions/upload-artifact@v2 From ec0590c91eec9c6b6eba3aa9c0ea2ce7fa3f83a8 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 17:14:28 +0200 Subject: [PATCH 14/33] Fixed the file path to the wheel --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 721c77b..45f875b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: PYTHONPATH=${GITHUB_WORKSPACE} python -m pytest --cov=mrmat_python_api_fastapi PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n # Hack to make Azure install our locally produced wheel - echo "/tmp/zipdeploy/extracted/mrmat_python_api_fastapi-${MRMAT_VERSION}.dev0-py3-none-any.whl" > dist/requirements.txt + echo "/tmp/zipdeploy/extracted/mrmat_python_api_fastapi-${MRMAT_VERSION}-py3-none-any.whl" > dist/requirements.txt - name: Upload test results uses: actions/upload-artifact@v2 From 87941241f4e02f1845fa01d6f0bbb82085a3f44e Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 24 Apr 2022 17:22:56 +0200 Subject: [PATCH 15/33] Reverting 3.10 features Azure only knows about 3.9 --- mrmat_python_api_fastapi/apis/__init__.py | 22 ------------------- .../apis/greeting/__init__.py | 22 ------------------- .../apis/greeting/v1/__init__.py | 22 ------------------- .../apis/greeting/v1/api.py | 22 ------------------- .../apis/greeting/v1/model.py | 22 ------------------- .../apis/greeting/v2/api.py | 3 ++- setup.cfg | 2 +- 7 files changed, 3 insertions(+), 112 deletions(-) diff --git a/mrmat_python_api_fastapi/apis/__init__.py b/mrmat_python_api_fastapi/apis/__init__.py index 49abd6b..42dad7a 100644 --- a/mrmat_python_api_fastapi/apis/__init__.py +++ b/mrmat_python_api_fastapi/apis/__init__.py @@ -19,25 +19,3 @@ # 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. - -# 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. diff --git a/mrmat_python_api_fastapi/apis/greeting/__init__.py b/mrmat_python_api_fastapi/apis/greeting/__init__.py index 49abd6b..42dad7a 100644 --- a/mrmat_python_api_fastapi/apis/greeting/__init__.py +++ b/mrmat_python_api_fastapi/apis/greeting/__init__.py @@ -19,25 +19,3 @@ # 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. - -# 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. diff --git a/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py b/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py index ecc8546..bf5e696 100644 --- a/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py +++ b/mrmat_python_api_fastapi/apis/greeting/v1/__init__.py @@ -20,27 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# 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/mrmat_python_api_fastapi/apis/greeting/v1/api.py b/mrmat_python_api_fastapi/apis/greeting/v1/api.py index 447f14e..565971d 100644 --- a/mrmat_python_api_fastapi/apis/greeting/v1/api.py +++ b/mrmat_python_api_fastapi/apis/greeting/v1/api.py @@ -20,28 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# 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 diff --git a/mrmat_python_api_fastapi/apis/greeting/v1/model.py b/mrmat_python_api_fastapi/apis/greeting/v1/model.py index 9a9a2a5..34c1687 100644 --- a/mrmat_python_api_fastapi/apis/greeting/v1/model.py +++ b/mrmat_python_api_fastapi/apis/greeting/v1/model.py @@ -20,28 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# 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 diff --git a/mrmat_python_api_fastapi/apis/greeting/v2/api.py b/mrmat_python_api_fastapi/apis/greeting/v2/api.py index 41b0dec..2f92a2a 100644 --- a/mrmat_python_api_fastapi/apis/greeting/v2/api.py +++ b/mrmat_python_api_fastapi/apis/greeting/v2/api.py @@ -24,6 +24,7 @@ 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 @@ -35,5 +36,5 @@ 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: str | None = 'Stranger'): +async def get_greeting(name: Optional[str] = 'Stranger'): return GreetingV2Output(message=f'Hello {name}') diff --git a/setup.cfg b/setup.cfg index d468412..0fef03e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,7 @@ url = https://github.com/MrMatOrg/mrmat-python-api-fastapi classifiers = Development Status :: 3 - Alpha License :: OSI Approved :: MIT - Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.9 [options] packages = find: From 7542ea3670d55c4da67328d27373f11ef5f4b21a Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sat, 30 Apr 2022 11:01:59 +0200 Subject: [PATCH 16/33] Removed duplicate license --- ci/__init__.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ci/__init__.py b/ci/__init__.py index 07b8d77..63ed8b0 100644 --- a/ci/__init__.py +++ b/ci/__init__.py @@ -20,29 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# MIT License -# -# 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 -# 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. From a3dab09d58d5be37e5122de9ceeeb5d30cb16a95 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sat, 30 Apr 2022 11:02:47 +0200 Subject: [PATCH 17/33] Added run configurations --- .idea/runConfigurations/build.xml | 24 ++++++++++++++++++++++++ .idea/runConfigurations/lint.xml | 24 ++++++++++++++++++++++++ .idea/runConfigurations/test.xml | 19 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 .idea/runConfigurations/build.xml create mode 100644 .idea/runConfigurations/lint.xml create mode 100644 .idea/runConfigurations/test.xml diff --git a/.idea/runConfigurations/build.xml b/.idea/runConfigurations/build.xml new file mode 100644 index 0000000..82fef0e --- /dev/null +++ b/.idea/runConfigurations/build.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/lint.xml b/.idea/runConfigurations/lint.xml new file mode 100644 index 0000000..01ae984 --- /dev/null +++ b/.idea/runConfigurations/lint.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/test.xml b/.idea/runConfigurations/test.xml new file mode 100644 index 0000000..314f897 --- /dev/null +++ b/.idea/runConfigurations/test.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file From d5e53012f5ef5a6aad8b13dbffe52acb596cf980 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Sun, 1 May 2022 09:35:05 +0200 Subject: [PATCH 18/33] Added db connectivity & alembic --- .idea/dataSources.xml | 19 +++++ .idea/mrmat-python-api-fastapi.iml | 9 ++- .idea/runConfigurations/db_revision.xml | 24 +++++++ .idea/runConfigurations/db_upgrade__head_.xml | 24 +++++++ .idea/runConfigurations/run.xml | 2 +- .idea/vcs.xml | 5 ++ migrations/README | 1 + migrations/alembic.ini | 47 ++++++++++++ migrations/env.py | 65 +++++++++++++++++ migrations/script.py.mako | 24 +++++++ mrmat_python_api_fastapi/__init__.py | 13 ++++ .../apis/resource/__init__.py | 21 ++++++ .../apis/resource/v1/__init__.py | 23 ++++++ .../apis/resource/v1/api.py | 72 +++++++++++++++++++ .../apis/resource/v1/db.py | 46 ++++++++++++ .../apis/resource/v1/schema.py | 39 ++++++++++ mrmat_python_api_fastapi/{cui.py => app.py} | 19 +++-- mrmat_python_api_fastapi/config.py | 41 +++++++++++ mrmat_python_api_fastapi/db.py | 37 ++++++++++ requirements.txt | 4 ++ setup.cfg | 4 ++ 21 files changed, 530 insertions(+), 9 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/runConfigurations/db_revision.xml create mode 100644 .idea/runConfigurations/db_upgrade__head_.xml create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 mrmat_python_api_fastapi/apis/resource/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/resource/v1/__init__.py create mode 100644 mrmat_python_api_fastapi/apis/resource/v1/api.py create mode 100644 mrmat_python_api_fastapi/apis/resource/v1/db.py create mode 100644 mrmat_python_api_fastapi/apis/resource/v1/schema.py rename mrmat_python_api_fastapi/{cui.py => app.py} (81%) create mode 100644 mrmat_python_api_fastapi/config.py create mode 100644 mrmat_python_api_fastapi/db.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..9af48b5 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,19 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://prometheus.mrmat.org:5432/prometheusdb + $ProjectFileDir$ + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://prometheus.mrmat.org:5432/prometheusdb + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/mrmat-python-api-fastapi.iml b/.idea/mrmat-python-api-fastapi.iml index 687f643..f00f077 100644 --- a/.idea/mrmat-python-api-fastapi.iml +++ b/.idea/mrmat-python-api-fastapi.iml @@ -1,7 +1,14 @@ - + + + + + + + + diff --git a/.idea/runConfigurations/db_revision.xml b/.idea/runConfigurations/db_revision.xml new file mode 100644 index 0000000..88c16dd --- /dev/null +++ b/.idea/runConfigurations/db_revision.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/db_upgrade__head_.xml b/.idea/runConfigurations/db_upgrade__head_.xml new file mode 100644 index 0000000..a3a58fa --- /dev/null +++ b/.idea/runConfigurations/db_upgrade__head_.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/run.xml b/.idea/runConfigurations/run.xml index e40b81b..d23c43f 100644 --- a/.idea/runConfigurations/run.xml +++ b/.idea/runConfigurations/run.xml @@ -1,6 +1,6 @@ - diff --git a/pyproject.toml b/pyproject.toml index 14ba939..78000e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - 'setuptools==75.6.0', + 'setuptools==76.0.0', 'wheel==0.45.1' ] build-backend = 'setuptools.build_meta' diff --git a/requirements.dev.txt b/requirements.dev.txt index a23dfde..925929e 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -4,11 +4,11 @@ # Build/Test requirements -setuptools==75.6.0 -build==1.2.2 # MIT +setuptools==76.0.0 +build==1.2.2.post1 # MIT wheel==0.45.1 # MIT -pytest==8.3.4 # MIT +pytest==8.3.5 # MIT pytest-cov==6.0.0 # MIT -mypy==1.13.0 # MIT -types-PyYAML==6.0.12.20240311 # Apache 2.0 +mypy==1.15.0 # MIT +types-PyYAML==6.0.12.20241230 # Apache 2.0 diff --git a/requirements.txt b/requirements.txt index f75c178..30fab63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # # Runtime requirements -fastapi==0.115.6 # MIT -sqlalchemy[asyncio] # MIT +fastapi==0.115.11 # MIT +sqlalchemy[asyncio]==2.0.39 # MIT uvicorn==0.34.0 # BSD 3-Clause -pydantic==2.10.4 # MIT +pydantic==2.10.6 # MIT From 36030fa14116876eab406eb5a4079f605dfd8196 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 18:26:04 +0200 Subject: [PATCH 26/33] Updated API and testsuite --- .idea/dataSources.xml | 18 +- .idea/misc.xml | 2 +- .idea/mrmat-python-api-fastapi.iml | 2 +- requirements.dev.txt | 1 + requirements.txt | 6 +- .../apis/{resource => platform}/__init__.py | 2 +- .../{resource => platform}/v1/__init__.py | 10 +- .../apis/platform/v1/api.py | 290 ++++++++++++++++++ .../apis/{resource => platform}/v1/db.py | 12 +- .../apis/{resource => platform}/v1/schema.py | 13 +- .../apis/resource/v1/api.py | 173 ----------- src/mrmat_python_api_fastapi/app.py | 4 +- src/mrmat_python_api_fastapi/db.py | 6 +- tests/conftest.py | 50 +++ tests/test_greeting.py | 43 +++ tests/{test_excuse.py => test_healthz.py} | 22 +- tests/test_platform.py | 101 ++++++ 17 files changed, 549 insertions(+), 206 deletions(-) rename src/mrmat_python_api_fastapi/apis/{resource => platform}/__init__.py (97%) rename src/mrmat_python_api_fastapi/apis/{resource => platform}/v1/__init__.py (85%) create mode 100644 src/mrmat_python_api_fastapi/apis/platform/v1/api.py rename src/mrmat_python_api_fastapi/apis/{resource => platform}/v1/db.py (78%) rename src/mrmat_python_api_fastapi/apis/{resource => platform}/v1/schema.py (87%) delete mode 100644 src/mrmat_python_api_fastapi/apis/resource/v1/api.py create mode 100644 tests/conftest.py create mode 100644 tests/test_greeting.py rename tests/{test_excuse.py => test_healthz.py} (57%) create mode 100644 tests/test_platform.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 9af48b5..22e22e1 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,18 +1,14 @@ - - postgresql + + sqlite.xerial true - org.postgresql.Driver - jdbc:postgresql://prometheus.mrmat.org:5432/prometheusdb - $ProjectFileDir$ - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://prometheus.mrmat.org:5432/prometheusdb + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/build/test.db + + + $ProjectFileDir$ diff --git a/.idea/misc.xml b/.idea/misc.xml index 183c6ab..be83902 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/mrmat-python-api-fastapi.iml b/.idea/mrmat-python-api-fastapi.iml index a03acdd..24ec1fd 100644 --- a/.idea/mrmat-python-api-fastapi.iml +++ b/.idea/mrmat-python-api-fastapi.iml @@ -7,7 +7,7 @@ - + diff --git a/requirements.dev.txt b/requirements.dev.txt index 925929e..16d0fe0 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -12,3 +12,4 @@ 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 index 30fab63..7e66d3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,6 @@ # Runtime requirements fastapi==0.115.11 # MIT -sqlalchemy[asyncio]==2.0.39 # MIT -uvicorn==0.34.0 # BSD 3-Clause -pydantic==2.10.6 # MIT +sqlalchemy[asyncio]==2.0.40 # MIT +uvicorn==0.34.0 # BSD 3-Clause +pydantic==2.10.6 # MIT diff --git a/src/mrmat_python_api_fastapi/apis/resource/__init__.py b/src/mrmat_python_api_fastapi/apis/platform/__init__.py similarity index 97% rename from src/mrmat_python_api_fastapi/apis/resource/__init__.py rename to src/mrmat_python_api_fastapi/apis/platform/__init__.py index e958016..efc80fa 100644 --- a/src/mrmat_python_api_fastapi/apis/resource/__init__.py +++ b/src/mrmat_python_api_fastapi/apis/platform/__init__.py @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .v1 import api_resource_v1 \ No newline at end of file +from .v1 import api_platform_v1 diff --git a/src/mrmat_python_api_fastapi/apis/resource/v1/__init__.py b/src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py similarity index 85% rename from src/mrmat_python_api_fastapi/apis/resource/v1/__init__.py rename to src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py index 2408c07..6516d40 100644 --- a/src/mrmat_python_api_fastapi/apis/resource/v1/__init__.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/__init__.py @@ -20,4 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .api import router as api_resource_v1 +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..733563c --- /dev/null +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py @@ -0,0 +1,290 @@ +# 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 typing import Union +from fastapi import APIRouter, Depends, status, HTTPException +from fastapi.responses import JSONResponse, 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=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.query(Resource).get(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', + responses={ + status.HTTP_201_CREATED: { + 'description': 'The resource was created', + '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 ResourceSchema(uid=resource.uid, + owner_uid=resource.owner_uid, + name=resource.name) + 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 + } + }) +async def modify_resource(uid: str, + data: ResourceInputSchema, + response: Response, + db: Session = Depends(get_db)): + try: + resource = db.query(Resource).get(uid) + if not resource: + response.status_code = 404 + return StatusSchema(code=404, msg='Not found') + resource.name = data.name + db.add(resource) + db.commit() + return ResourceSchema(uid=resource.uid, + owner_uid=resource.owner_uid, + name=resource.name) + 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.query(Resource).get(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(db: Session = Depends(get_db)): + try: + owners = db.query(Owner).all() + return OwnerListSchema(owners=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': ResourceSchema + } + }) +async def get_owner(uid: str, + response: Response, + session: Session = Depends(get_db)): + try: + owner = session.query(Owner).get(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', + responses={ + status.HTTP_201_CREATED: {'description': 'The owner was created', '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 OwnerSchema(uid=owner.uid, name=owner.name) + 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 + } + }) +async def modify_owner(uid: str, + data: OwnerInputSchema, + response: Response, + db: Session = Depends(get_db)): + try: + owner = db.query(Owner).get(uid) + if not owner: + response.status_code = 404 + return StatusSchema(code=404, msg='The owner was not found') + owner.name = data.name + db.add(owner) + db.commit() + return OwnerSchema(uid=owner.uid, name=owner.name) + 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, + db: Session = Depends(get_db)): + owner = db.query(Owner).get(uid) + if not owner: + response.status_code = status.HTTP_410_GONE + return + db.delete(owner) + db.commit() + response.status_code = status.HTTP_204_NO_CONTENT diff --git a/src/mrmat_python_api_fastapi/apis/resource/v1/db.py b/src/mrmat_python_api_fastapi/apis/platform/v1/db.py similarity index 78% rename from src/mrmat_python_api_fastapi/apis/resource/v1/db.py rename to src/mrmat_python_api_fastapi/apis/platform/v1/db.py index 974a912..8912c7c 100644 --- a/src/mrmat_python_api_fastapi/apis/resource/v1/db.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/db.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from sqlalchemy import ForeignKey, Column, Integer, String, UniqueConstraint, BigInteger +import uuid +from sqlalchemy import ForeignKey, Column, String, UniqueConstraint, UUID from sqlalchemy.orm import relationship from mrmat_python_api_fastapi import Base @@ -29,7 +30,8 @@ class Owner(Base): __tablename__ = 'owners' __schema__ = 'mrmat-python-api-fastapi' - id = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True) + #uid = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True) + uid = Column(String, primary_key=True, default=str(uuid.uuid4())) client_id = Column(String(255), nullable=False, unique=True) name = Column(String(255), nullable=False) resources = relationship('Resource', back_populates='owner') @@ -38,9 +40,9 @@ class Owner(Base): class Resource(Base): __tablename__ = 'resources' __schema__ = 'mrmat-python-api-fastapi' - id = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True) - owner_id = Column(Integer, ForeignKey('owners.id'), nullable=False) + uid = Column(String, primary_key=True, default=str(uuid.uuid4())) + owner_uid = Column(String, ForeignKey('owners.uid'), nullable=False) name = Column(String(255), nullable=False) owner = relationship('Owner', back_populates='resources') - UniqueConstraint('owner_id', 'name', name='no_duplicate_names_per_owner') + UniqueConstraint('owner_uid', 'name', name='no_duplicate_names_per_owner') diff --git a/src/mrmat_python_api_fastapi/apis/resource/v1/schema.py b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py similarity index 87% rename from src/mrmat_python_api_fastapi/apis/resource/v1/schema.py rename to src/mrmat_python_api_fastapi/apis/platform/v1/schema.py index 01f7f8b..a0f0ea2 100644 --- a/src/mrmat_python_api_fastapi/apis/resource/v1/schema.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py @@ -20,26 +20,33 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from pydantic import BaseModel +import typing +from pydantic import BaseModel class OwnerInputSchema(BaseModel): name: str - class OwnerSchema(OwnerInputSchema): - id: int + uid: str class Config: from_attributes = True +class OwnerListSchema(BaseModel): + owners: typing.List[OwnerSchema] class ResourceInputSchema(BaseModel): name: str + owner_uid: str class ResourceSchema(ResourceInputSchema): + uid: str name: str class Config: from_attributes = True + +class ResourceListSchema(BaseModel): + resources: typing.List[ResourceSchema] \ No newline at end of file diff --git a/src/mrmat_python_api_fastapi/apis/resource/v1/api.py b/src/mrmat_python_api_fastapi/apis/resource/v1/api.py deleted file mode 100644 index 200f685..0000000 --- a/src/mrmat_python_api_fastapi/apis/resource/v1/api.py +++ /dev/null @@ -1,173 +0,0 @@ -# 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 typing import Union, List -from fastapi import APIRouter, Depends, status -from fastapi.responses import JSONResponse, Response -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, OwnerSchema, OwnerInputSchema - -router = APIRouter() - - -@router.get('/resources', - name='list_resources', - summary='List all known resources', - description='Returns all currently known resources and their metadata', - response_model=List[ResourceSchema]) -async def get_resources(db: Session = Depends(get_db)): - return db.query(Resource).all() - - -@router.get('/resources/{identity}', - name='get_resource', - summary='Get a single resource', - description='Return a single resource identified by its resource id', - response_model=Union[ResourceSchema, StatusSchema]) -async def get_resource(identity: int, db: Session = Depends(get_db)): - resource = db.query(Resource).get(identity).one_or_none() - if not resource: - return JSONResponse(status_code=404, - content=StatusSchema(code=404, msg='Not found').dict()) - return resource - - -@router.post('/resources', - name='create_resource', - summary='Create a resource', - description='Create a resource owned by the authenticated user', - response_model=Union[ResourceSchema, StatusSchema]) -async def create_resource(data: ResourceInputSchema, db: Session = Depends(get_db)): - resource = Resource(name=data.name) - db.add(resource) - db.commit() - return resource - - -@router.put('/resources/{identity}', - name='modify_resource', - summary='Modify a resource', - description='Modify a resource by its resource id', - response_model=Union[ResourceSchema, StatusSchema]) -async def modify_resource(identity: int, data: ResourceInputSchema, db: Session = Depends(get_db)): - resource = db.query(Resource).get(identity) - if not resource: - return JSONResponse(status_code=404, - content=StatusSchema(code=404, msg='Not found').dict()) - resource.name = data.name - db.add(resource) - db.commit() - return resource - - -@router.delete('/resources/{identity}', - 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'} - }) -async def remove_resource(identity: int, response: Response, db: Session = Depends(get_db)): - resource = db.query(Resource).get(identity) - if not resource: - return JSONResponse(status_code=204, - content=StatusSchema(code=404, msg='Not found').dict()) - db.delete(resource) - db.commit() - response.status_code = 410 - return {} - - - -@router.get('/owners', - name='list_owners', - summary='List all known owners', - description='Returns all currently known owners and their metadata', - response_model=List[OwnerSchema]) -async def get_owners(db: Session = Depends(get_db)): - return db.query(Owner).all() - - -@router.get('/owners/{identity}', - name='get_owner', - summary='Get a single owner', - description='Return a single owner identified by its owner id', - response_model=Union[OwnerSchema, StatusSchema]) -async def get_owner(identity: int, db: Session = Depends(get_db)): - owner = db.query(Owner).get(identity) - if not owner: - return JSONResponse(status_code=404, - content=StatusSchema(code=404, msg='Not found').dict()) - return owner - - -@router.post('/owners', - name='create_owner', - summary='Create a owner', - description='Create a owner', - response_model=Union[OwnerSchema, StatusSchema]) -async def create_owner(data: OwnerInputSchema, db: Session = Depends(get_db)): - owner = Owner(name=data.name, client_id='TODO') - db.add(owner) - db.commit() - return owner - - -@router.put('/owners/{identity}', - name='modify_owner', - summary='Modify a owner', - description='Modify a owner by its owner id', - response_model=Union[OwnerSchema, StatusSchema]) -async def modify_owner(identity: int, data: ResourceInputSchema, db: Session = Depends(get_db)): - owner = db.query(Owner).get(identity) - if not owner: - return JSONResponse(status_code=404, - content=StatusSchema(code=404, msg='Not found').dict()) - owner.name = data.name - db.add(owner) - db.commit() - return owner - - -@router.delete('/owners/{identity}', - 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'} - }) -async def remove_owner(identity: int, response: Response, db: Session = Depends(get_db)): - owner = db.query(Owner).get(identity) - if not owner: - response.status_code = status.HTTP_410_GONE - return - db.delete(owner) - db.commit() - response.status_code = status.HTTP_204_NO_CONTENT diff --git a/src/mrmat_python_api_fastapi/app.py b/src/mrmat_python_api_fastapi/app.py index 61fc995..0f00470 100644 --- a/src/mrmat_python_api_fastapi/app.py +++ b/src/mrmat_python_api_fastapi/app.py @@ -24,14 +24,14 @@ 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.resource import api_resource_v1 +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_resource_v1, prefix='/api/resource/v1', tags=['resource']) +app.include_router(api_platform_v1, prefix='/api/platform/v1', tags=['platform']) @app.get('/') diff --git a/src/mrmat_python_api_fastapi/db.py b/src/mrmat_python_api_fastapi/db.py index 258da8d..1e81551 100644 --- a/src/mrmat_python_api_fastapi/db.py +++ b/src/mrmat_python_api_fastapi/db.py @@ -21,17 +21,19 @@ # 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 +from mrmat_python_api_fastapi import app_config, Base @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}) + engine = create_engine(url=app_config.db_url, connect_args={'check_same_thread': False}, echo=True) else: engine = create_engine(url=app_config.db_url) session_local = sessionmaker(bind=engine) + Base.metadata.create_all(bind=engine) return session_local() diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..75631fb --- /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, Base +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(): + Base.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_excuse.py b/tests/test_healthz.py similarity index 57% rename from tests/test_excuse.py rename to tests/test_healthz.py index d797aed..d8779f9 100644 --- a/tests/test_excuse.py +++ b/tests/test_healthz.py @@ -1,6 +1,6 @@ # MIT License # -# Copyright (c) 2022 Mathieu Imfeld +# 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 @@ -20,5 +20,21 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -def test_excuse(): - assert True +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 From 1391d68707c67505fa13568f4433225bc28edef0 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 18:28:42 +0200 Subject: [PATCH 27/33] Updated build pipeline --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21502f7..178ca65 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ name: Build env: MAJOR: 0 MINOR: 0 - PYTHON_VERSION: 3.12.0 + PYTHON_VERSION: 3.13.0 # # Establish when the workflow is run @@ -75,7 +75,7 @@ jobs: PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n - name: Upload test results - uses: actions/upload-artifact@v3.0.0 + uses: actions/upload-artifact@v3 if: ${{ always() }} with: name: Test and Coverage From 2196327d3a1fdcc4828a63a9c6bd18ec95b4fa18 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 18:29:59 +0200 Subject: [PATCH 28/33] Updated build pipeline --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 178ca65..fd53aa2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,7 @@ jobs: PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n - name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: Test and Coverage From 9297f0c37f2a878ef5e135f2e7a8e4664818c4a7 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 18:55:43 +0200 Subject: [PATCH 29/33] Updated build pipeline --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd53aa2..69dbfaf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,8 +71,8 @@ jobs: run: | export PYTHONUSERBASE=${HOME}/.local pip install --user -r requirements.txt -r requirements.dev.txt - PYTHONPATH=${GITHUB_WORKSPACE} python -m pytest --cov=mrmat_python_api_fastapi - PYTHONPATH=${GITHUB_WORKSPACE} python -m build --wheel -n + PYTHONPATH=${GITHUB_WORKSPACE}/src python -m pytest --cov=mrmat_python_api_fastapi + PYTHONPATH=${GITHUB_WORKSPACE}/src python -m build --wheel -n - name: Upload test results uses: actions/upload-artifact@v4 From 61d3e318a4d85603725440d195fea6b2f874cf19 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 18:58:55 +0200 Subject: [PATCH 30/33] Testing where the files go --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69dbfaf..c064c84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,6 +73,7 @@ jobs: pip install --user -r requirements.txt -r requirements.dev.txt PYTHONPATH=${GITHUB_WORKSPACE}/src python -m pytest --cov=mrmat_python_api_fastapi PYTHONPATH=${GITHUB_WORKSPACE}/src python -m build --wheel -n + ls build - name: Upload test results uses: actions/upload-artifact@v4 From 8d0c05f4431f5676abe31f20510724c6aa331ba7 Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 19:13:35 +0200 Subject: [PATCH 31/33] Fixed pytest configuration --- .github/workflows/build.yml | 2 +- pyproject.toml | 2 +- src/mrmat_python_api_fastapi/__init__.py | 7 +++- .../apis/platform/v1/api.py | 41 ++++++++++--------- .../apis/platform/v1/schema.py | 20 ++++----- src/mrmat_python_api_fastapi/db.py | 2 +- 6 files changed, 38 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c064c84..a5ae74a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: run: | export PYTHONUSERBASE=${HOME}/.local pip install --user -r requirements.txt -r requirements.dev.txt - PYTHONPATH=${GITHUB_WORKSPACE}/src python -m pytest --cov=mrmat_python_api_fastapi + PYTHONPATH=${GITHUB_WORKSPACE}/src pytest PYTHONPATH=${GITHUB_WORKSPACE}/src python -m build --wheel -n ls build diff --git a/pyproject.toml b/pyproject.toml index 78000e2..a6288cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ init_forbid_extra = true init_typed = true warn_required_dynamic_aliases = true -[pytest] +[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' diff --git a/src/mrmat_python_api_fastapi/__init__.py b/src/mrmat_python_api_fastapi/__init__.py index ac05679..00720fd 100644 --- a/src/mrmat_python_api_fastapi/__init__.py +++ b/src/mrmat_python_api_fastapi/__init__.py @@ -19,9 +19,14 @@ # 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.ext.declarative import declarative_base +from sqlalchemy.orm import declarative_base Base = declarative_base() + + +class BaseSchema(BaseModel): + model_config = {'from_attributes': True} diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/api.py b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py index 733563c..3e462ce 100644 --- a/src/mrmat_python_api_fastapi/apis/platform/v1/api.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py @@ -73,7 +73,7 @@ async def get_resource(uid: str, response: Response, session: Session = Depends(get_db)): try: - resource = session.query(Resource).get(uid) + resource = session.get(Resource, uid) if not resource: response.status_code = 404 return StatusSchema(code=404, msg='The resource was not found') @@ -124,15 +124,15 @@ async def create_resource(data: ResourceInputSchema, async def modify_resource(uid: str, data: ResourceInputSchema, response: Response, - db: Session = Depends(get_db)): + session: Session = Depends(get_db)): try: - resource = db.query(Resource).get(uid) + resource = session.get(Resource, uid) if not resource: response.status_code = 404 return StatusSchema(code=404, msg='Not found') resource.name = data.name - db.add(resource) - db.commit() + session.add(resource) + session.commit() return ResourceSchema(uid=resource.uid, owner_uid=resource.owner_uid, name=resource.name) @@ -158,7 +158,7 @@ async def remove_resource(uid: str, response: Response, session: Session = Depends(get_db)): try: - resource = session.query(Resource).get(uid) + resource = session.get(Resource, uid) if not resource: response.status_code = 410 return StatusSchema(code=410, msg='The resource was already gone') @@ -203,7 +203,7 @@ async def get_owner(uid: str, response: Response, session: Session = Depends(get_db)): try: - owner = session.query(Owner).get(uid) + owner = session.get(Owner, uid) if not owner: response.status_code = 404 return StatusSchema(code=404, msg='The owner was not found') @@ -250,15 +250,15 @@ async def create_owner(data: OwnerInputSchema, async def modify_owner(uid: str, data: OwnerInputSchema, response: Response, - db: Session = Depends(get_db)): + session: Session = Depends(get_db)): try: - owner = db.query(Owner).get(uid) + 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 - db.add(owner) - db.commit() + session.add(owner) + session.commit() return OwnerSchema(uid=owner.uid, name=owner.name) except SQLAlchemyError as e: raise HTTPException(status_code=500, detail="A database error occurred") from e @@ -280,11 +280,14 @@ async def modify_owner(uid: str, }) async def remove_owner(uid: str, response: Response, - db: Session = Depends(get_db)): - owner = db.query(Owner).get(uid) - if not owner: - response.status_code = status.HTTP_410_GONE - return - db.delete(owner) - db.commit() - response.status_code = status.HTTP_204_NO_CONTENT + 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/schema.py b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py index a0f0ea2..f5afd11 100644 --- a/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py @@ -22,31 +22,25 @@ import typing -from pydantic import BaseModel +from mrmat_python_api_fastapi import BaseSchema -class OwnerInputSchema(BaseModel): + +class OwnerInputSchema(BaseSchema): name: str class OwnerSchema(OwnerInputSchema): uid: str - class Config: - from_attributes = True - -class OwnerListSchema(BaseModel): +class OwnerListSchema(BaseSchema): owners: typing.List[OwnerSchema] -class ResourceInputSchema(BaseModel): +class ResourceInputSchema(BaseSchema): name: str owner_uid: str - class ResourceSchema(ResourceInputSchema): uid: str name: str - class Config: - from_attributes = True - -class ResourceListSchema(BaseModel): - resources: typing.List[ResourceSchema] \ No newline at end of file +class ResourceListSchema(BaseSchema): + resources: typing.List[ResourceSchema] diff --git a/src/mrmat_python_api_fastapi/db.py b/src/mrmat_python_api_fastapi/db.py index 1e81551..45140b3 100644 --- a/src/mrmat_python_api_fastapi/db.py +++ b/src/mrmat_python_api_fastapi/db.py @@ -31,7 +31,7 @@ @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}, echo=True) + 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(bind=engine) From 71978b97bb69b8547fe96b9a20ba6277bb4986ef Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 19:14:09 +0200 Subject: [PATCH 32/33] No need to debug anymore --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5ae74a..36af9e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,6 @@ jobs: pip install --user -r requirements.txt -r requirements.dev.txt PYTHONPATH=${GITHUB_WORKSPACE}/src pytest PYTHONPATH=${GITHUB_WORKSPACE}/src python -m build --wheel -n - ls build - name: Upload test results uses: actions/upload-artifact@v4 From 9201430f40da4f3ad7cf8c2478c1826dcc783f6a Mon Sep 17 00:00:00 2001 From: Mathieu Imfeld Date: Fri, 25 Apr 2025 19:41:53 +0200 Subject: [PATCH 33/33] Fixed mypy complaints --- .idea/runConfigurations/db_revision.xml | 24 ---------- .idea/runConfigurations/db_upgrade__head_.xml | 24 ---------- src/mrmat_python_api_fastapi/__init__.py | 7 +-- .../apis/platform/v1/api.py | 48 ++++++++----------- .../apis/platform/v1/db.py | 30 ++++++------ .../apis/platform/v1/schema.py | 15 +++--- src/mrmat_python_api_fastapi/db.py | 6 +-- tests/conftest.py | 4 +- 8 files changed, 52 insertions(+), 106 deletions(-) delete mode 100644 .idea/runConfigurations/db_revision.xml delete mode 100644 .idea/runConfigurations/db_upgrade__head_.xml diff --git a/.idea/runConfigurations/db_revision.xml b/.idea/runConfigurations/db_revision.xml deleted file mode 100644 index 88c16dd..0000000 --- a/.idea/runConfigurations/db_revision.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/db_upgrade__head_.xml b/.idea/runConfigurations/db_upgrade__head_.xml deleted file mode 100644 index a3a58fa..0000000 --- a/.idea/runConfigurations/db_upgrade__head_.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/mrmat_python_api_fastapi/__init__.py b/src/mrmat_python_api_fastapi/__init__.py index 00720fd..32dea2a 100644 --- a/src/mrmat_python_api_fastapi/__init__.py +++ b/src/mrmat_python_api_fastapi/__init__.py @@ -23,10 +23,11 @@ from .config import Config app_config = Config.from_json_file() +from sqlalchemy.orm import DeclarativeBase -from sqlalchemy.orm import declarative_base -Base = declarative_base() +class ORMBase(DeclarativeBase): + pass -class BaseSchema(BaseModel): +class SchemaBase(BaseModel): model_config = {'from_attributes': True} diff --git a/src/mrmat_python_api_fastapi/apis/platform/v1/api.py b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py index 3e462ce..3a54c46 100644 --- a/src/mrmat_python_api_fastapi/apis/platform/v1/api.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/api.py @@ -20,9 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Union from fastapi import APIRouter, Depends, status, HTTPException -from fastapi.responses import JSONResponse, Response +from fastapi.responses import Response from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session @@ -49,7 +48,10 @@ async def get_resources(session: Session = Depends(get_db)) -> ResourceListSchema: try: resources = session.query(Resource).all() - return ResourceListSchema(resources=resources) + 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 @@ -86,12 +88,7 @@ async def get_resource(uid: str, name='create_resource', summary='Create a resource', description='Create a resource owned by the authenticated user', - responses={ - status.HTTP_201_CREATED: { - 'description': 'The resource was created', - 'model': ResourceSchema - }, - }) + response_model=ResourceSchema) async def create_resource(data: ResourceInputSchema, response: Response, session: Session = Depends(get_db)): @@ -100,9 +97,7 @@ async def create_resource(data: ResourceInputSchema, session.add(resource) session.commit() response.status_code = 201 - return ResourceSchema(uid=resource.uid, - owner_uid=resource.owner_uid, - name=resource.name) + return resource except SQLAlchemyError as e: raise HTTPException(status_code=500, detail="A database error occurred") from e @@ -120,7 +115,8 @@ async def create_resource(data: ResourceInputSchema, 'description': 'The resource was not found', 'model': StatusSchema } - }) + }, + response_model=ResourceSchema) async def modify_resource(uid: str, data: ResourceInputSchema, response: Response, @@ -133,9 +129,7 @@ async def modify_resource(uid: str, resource.name = data.name session.add(resource) session.commit() - return ResourceSchema(uid=resource.uid, - owner_uid=resource.owner_uid, - name=resource.name) + return resource except SQLAlchemyError as e: raise HTTPException(status_code=500, detail="A database error occurred") from e @@ -176,10 +170,10 @@ async def remove_resource(uid: str, summary='List all known owners', description='Returns all currently known owners and their metadata', response_model=OwnerListSchema) -async def get_owners(db: Session = Depends(get_db)): +async def get_owners(session: Session = Depends(get_db)): try: - owners = db.query(Owner).all() - return OwnerListSchema(owners=owners) + 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 @@ -196,9 +190,10 @@ async def get_owners(db: Session = Depends(get_db)): }, status.HTTP_200_OK: { 'description': 'The requested owner', - 'model': ResourceSchema + 'model': OwnerSchema } - }) + }, + response_model=OwnerSchema) async def get_owner(uid: str, response: Response, session: Session = Depends(get_db)): @@ -216,9 +211,7 @@ async def get_owner(uid: str, name='create_owner', summary='Create a owner', description='Create a owner', - responses={ - status.HTTP_201_CREATED: {'description': 'The owner was created', 'model': OwnerSchema} - }) + response_model=OwnerSchema) async def create_owner(data: OwnerInputSchema, response: Response, session: Session = Depends(get_db)): @@ -227,7 +220,7 @@ async def create_owner(data: OwnerInputSchema, session.add(owner) session.commit() response.status_code = 201 - return OwnerSchema(uid=owner.uid, name=owner.name) + 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 @@ -246,7 +239,8 @@ async def create_owner(data: OwnerInputSchema, 'description': 'The owner was not found', 'model': StatusSchema } - }) + }, + response_model=OwnerSchema) async def modify_owner(uid: str, data: OwnerInputSchema, response: Response, @@ -259,7 +253,7 @@ async def modify_owner(uid: str, owner.name = data.name session.add(owner) session.commit() - return OwnerSchema(uid=owner.uid, name=owner.name) + return owner 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 index 8912c7c..fb90a02 100644 --- a/src/mrmat_python_api_fastapi/apis/platform/v1/db.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/db.py @@ -21,28 +21,28 @@ # SOFTWARE. import uuid -from sqlalchemy import ForeignKey, Column, String, UniqueConstraint, UUID -from sqlalchemy.orm import relationship +from sqlalchemy import ForeignKey, String, UniqueConstraint, UUID +from sqlalchemy.orm import relationship, Mapped, mapped_column -from mrmat_python_api_fastapi import Base +from mrmat_python_api_fastapi import ORMBase -class Owner(Base): +class Owner(ORMBase): __tablename__ = 'owners' __schema__ = 'mrmat-python-api-fastapi' - #uid = Column(BigInteger().with_variant(Integer, 'sqlite'), primary_key=True) - uid = Column(String, primary_key=True, default=str(uuid.uuid4())) - client_id = Column(String(255), nullable=False, unique=True) - name = Column(String(255), nullable=False) - resources = relationship('Resource', back_populates='owner') + 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(Base): + +class Resource(ORMBase): __tablename__ = 'resources' __schema__ = 'mrmat-python-api-fastapi' - uid = Column(String, primary_key=True, default=str(uuid.uuid4())) - owner_uid = Column(String, ForeignKey('owners.uid'), nullable=False) - name = Column(String(255), nullable=False) + 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 = relationship('Owner', back_populates='resources') - UniqueConstraint('owner_uid', 'name', name='no_duplicate_names_per_owner') + 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 index f5afd11..553372c 100644 --- a/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py +++ b/src/mrmat_python_api_fastapi/apis/platform/v1/schema.py @@ -22,25 +22,24 @@ import typing -from mrmat_python_api_fastapi import BaseSchema +from mrmat_python_api_fastapi import SchemaBase -class OwnerInputSchema(BaseSchema): +class OwnerInputSchema(SchemaBase): name: str class OwnerSchema(OwnerInputSchema): uid: str -class OwnerListSchema(BaseSchema): - owners: typing.List[OwnerSchema] +class OwnerListSchema(SchemaBase): + owners: typing.Sequence[OwnerSchema] -class ResourceInputSchema(BaseSchema): +class ResourceInputSchema(SchemaBase): name: str owner_uid: str class ResourceSchema(ResourceInputSchema): uid: str - name: str -class ResourceListSchema(BaseSchema): - resources: typing.List[ResourceSchema] +class ResourceListSchema(SchemaBase): + resources: typing.Sequence[ResourceSchema] diff --git a/src/mrmat_python_api_fastapi/db.py b/src/mrmat_python_api_fastapi/db.py index 45140b3..4ae9335 100644 --- a/src/mrmat_python_api_fastapi/db.py +++ b/src/mrmat_python_api_fastapi/db.py @@ -25,7 +25,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker -from mrmat_python_api_fastapi import app_config, Base +from mrmat_python_api_fastapi import app_config, ORMBase @lru_cache @@ -34,6 +34,6 @@ def get_db() -> Session: 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(bind=engine) - Base.metadata.create_all(bind=engine) + 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 index 75631fb..786c910 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,7 +24,7 @@ import pytest import fastapi.testclient -from mrmat_python_api_fastapi import app_config, Base +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 @@ -46,5 +46,5 @@ def client(test_db_path) -> fastapi.testclient.TestClient: app_config.db_url = f'sqlite:///{test_db_path}' session = get_db() with session.begin(): - Base.metadata.create_all(session.bind) + ORMBase.metadata.create_all(session.bind) return fastapi.testclient.TestClient(app)