From 0be947b3d194f23950cf9421187f6c33b19668ba Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Fri, 25 Mar 2022 20:03:33 +0100 Subject: [PATCH 1/2] Add Docker dev environment and initial code --- .gitignore | 164 +++++------------------------------------- Dockerfile.dev | 22 ++++++ LICENSE => LICENSE.md | 0 Makefile | 47 ++++++++++++ Pipfile | 13 ++++ Pipfile.lock | 45 ++++++++++++ README.md | 4 +- docker-compose.yml | 18 +++++ tofu_api/__init__.py | 0 tofu_api/app.py | 10 +++ 10 files changed, 173 insertions(+), 150 deletions(-) create mode 100644 Dockerfile.dev rename LICENSE => LICENSE.md (100%) create mode 100644 Makefile create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 docker-compose.yml create mode 100644 tofu_api/__init__.py create mode 100644 tofu_api/app.py diff --git a/.gitignore b/.gitignore index 55be276..f11a714 100644 --- a/.gitignore +++ b/.gitignore @@ -1,154 +1,22 @@ -# ---> Python -# Byte-compiled / optimized / DLL files +# IDEs / editors +.idea/ +.vscode/ + +# General +/tmp +/_tmp +.cache + +# Python __pycache__/ *.py[cod] -*$py.class -# C extensions -*.so +# Python packaging and dev environments +/.pip +/venv -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ +# Python testing .coverage .coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# 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/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - +.pytest_cache +.tox/ diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..e67e44c --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,22 @@ +FROM python:3.10 + +# Install pipenv container-wide as root +RUN pip install pipenv + +# Create unprivileged user with UID as specified by the build argument DEV_USER_UID, defaulting to 1000. +# (This should match the UID of your local user. If it doesn't, use an ".env" file to overwrite this variable.) +ARG DEV_USER_UID=1000 +RUN echo Creating dev user with UID $DEV_USER_UID && \ + useradd -m -u $DEV_USER_UID dev +USER dev + +## Create virtual environment using pipenv +WORKDIR /app +COPY Pipfile Pipfile.lock ./ +RUN pipenv install --dev --deploy + +# Set entrypoint to always run commands inside virtual environment +ENTRYPOINT ["pipenv", "run"] + +# Set default command +CMD ["bash"] diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b2befc4 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# Define variables +DOCKER_COMPOSE = docker-compose +DOCKER_RUN = $(DOCKER_COMPOSE) run --rm backend + +# Default target +.PHONY: all +all: docker-up + + +# Container management +# -------------------- + +.PHONY: docker-up +docker-up: + $(DOCKER_COMPOSE) up --build + +.PHONY: docker-down +docker-down: + $(DOCKER_COMPOSE) down + +.PHONY: docker-build +docker-build: + $(DOCKER_COMPOSE) build + +.PHONY: docker-rebuild +docker-rebuild: + $(DOCKER_COMPOSE) build --pull --no-cache + +.PHONY: docker-purge +docker-purge: + $(DOCKER_COMPOSE) down --volumes + +.PHONY: docker-restart +docker-restart: + $(DOCKER_COMPOSE) restart $(SERVICE) + +.PHONY: docker-logs +docker-logs: + $(DOCKER_COMPOSE) logs -f $(SERVICE) || true + +.PHONY: docker-run +docker-run: + $(DOCKER_RUN) "$(CMD)" + +.PHONY: docker-shell +docker-shell: + $(DOCKER_RUN) bash diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..ec7dc2e --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +gunicorn = "~=20.1" +werkzeug = "~=2.0" + +[dev-packages] + +[requires] +python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..24260f8 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,45 @@ +{ + "_meta": { + "hash": { + "sha256": "574b2898e332128ecb874d71081cf7a8c62e4e219a603d0d76a5d9afb3ac164c" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "gunicorn": { + "hashes": [ + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + ], + "index": "pypi", + "version": "==20.1.0" + }, + "setuptools": { + "hashes": [ + "sha256:6221e37dc86fcdc9dad9d9eb2002e9f9798fe4aca1bf18f280e66e50c0eb7fca", + "sha256:ad88b13f3dc60420259c9877486908ddad12c7befaff0d624c7190f742abd64f" + ], + "markers": "python_version >= '3.7'", + "version": "==61.0.0" + }, + "werkzeug": { + "hashes": [ + "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8", + "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c" + ], + "index": "pypi", + "version": "==2.0.3" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index dcf0061..3a27296 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# tofu-api +# Tofu - REST API -REST API backend for Tofu - a to-do list app with a twist. \ No newline at end of file +REST API backend for Tofu - a to-do list app with a twist. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e6e3655 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + backend: + build: + context: . + dockerfile: Dockerfile.dev + args: + # Use an ".env" file to overwrite this variable if your local user's UID is not 1000. + DEV_USER_UID: ${DEV_USER_UID:-1000} + ports: + - '8080:8080' + volumes: + - ./:/app/ + command: gunicorn --bind=0.0.0.0:8080 --reload tofu_api.app:app + +volumes: + mariadb_data: diff --git a/tofu_api/__init__.py b/tofu_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tofu_api/app.py b/tofu_api/app.py new file mode 100644 index 0000000..e570081 --- /dev/null +++ b/tofu_api/app.py @@ -0,0 +1,10 @@ +def app(environ, start_response): + """Simplest possible application object""" + data = b'Hello, wooorld!\n' + status = '200 OK' + response_headers = [ + ('Content-type', 'text/plain'), + ('Content-Length', str(len(data))) + ] + start_response(status, response_headers) + return iter([data]) From 2da6e4379783bc65034d1a0fc1530b4e640f7387 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Sat, 26 Mar 2022 01:07:47 +0100 Subject: [PATCH 2/2] Add base code for REST API using Flask --- Makefile | 2 +- Pipfile | 2 + Pipfile.lock | 119 ++++++++++++++++++++++++++++++++- config.dev.yml | 1 + development.env | 3 + docker-compose.yml | 6 +- tofu_api/api/__init__.py | 1 + tofu_api/api/rest_api.py | 14 ++++ tofu_api/api/tasks/__init__.py | 1 + tofu_api/api/tasks/task_api.py | 84 +++++++++++++++++++++++ tofu_api/app.py | 42 +++++++++--- tofu_api/config.py | 5 ++ 12 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 config.dev.yml create mode 100644 development.env create mode 100644 tofu_api/api/__init__.py create mode 100644 tofu_api/api/rest_api.py create mode 100644 tofu_api/api/tasks/__init__.py create mode 100644 tofu_api/api/tasks/task_api.py create mode 100644 tofu_api/config.py diff --git a/Makefile b/Makefile index b2befc4..7815e2a 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ docker-logs: .PHONY: docker-run docker-run: - $(DOCKER_RUN) "$(CMD)" + $(DOCKER_RUN) $(CMD) .PHONY: docker-shell docker-shell: diff --git a/Pipfile b/Pipfile index ec7dc2e..cfbb521 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,8 @@ name = "pypi" [packages] gunicorn = "~=20.1" werkzeug = "~=2.0" +flask = "~=2.0" +pyyaml = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 24260f8..c007e70 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "574b2898e332128ecb874d71081cf7a8c62e4e219a603d0d76a5d9afb3ac164c" + "sha256": "57e212392ff70382ed9ae0637e05f3696fa1d815f226e564910d296f7e2b5027" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,22 @@ ] }, "default": { + "click": { + "hashes": [ + "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1", + "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.4" + }, + "flask": { + "hashes": [ + "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f", + "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d" + ], + "index": "pypi", + "version": "==2.0.3" + }, "gunicorn": { "hashes": [ "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", @@ -24,6 +40,107 @@ "index": "pypi", "version": "==20.1.0" }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:a2f09a92f358b96b5f6ca6ecb4502669c4acb55d8733bbb2b2c9c4af5564c605", + "sha256:da424924c069a4013730d8dd010cbecac7e7bb752be388db3741688bffb48dc6" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.0" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "pyyaml": { + "hashes": [ + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "index": "pypi", + "version": "==6.0" + }, "setuptools": { "hashes": [ "sha256:6221e37dc86fcdc9dad9d9eb2002e9f9798fe4aca1bf18f280e66e50c0eb7fca", diff --git a/config.dev.yml b/config.dev.yml new file mode 100644 index 0000000..8fe1ddb --- /dev/null +++ b/config.dev.yml @@ -0,0 +1 @@ +SECRET_KEY: 'development' diff --git a/development.env b/development.env new file mode 100644 index 0000000..5faadd5 --- /dev/null +++ b/development.env @@ -0,0 +1,3 @@ +FLASK_APP=tofu_api.app +FLASK_ENV=development +FLASK_CONFIG_FILE=config.dev.yml diff --git a/docker-compose.yml b/docker-compose.yml index e6e3655..4020a66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,10 +9,12 @@ services: # Use an ".env" file to overwrite this variable if your local user's UID is not 1000. DEV_USER_UID: ${DEV_USER_UID:-1000} ports: - - '8080:8080' + - '5000:5000' volumes: - ./:/app/ - command: gunicorn --bind=0.0.0.0:8080 --reload tofu_api.app:app + env_file: + - development.env + command: flask run --host=0.0.0.0 volumes: mariadb_data: diff --git a/tofu_api/api/__init__.py b/tofu_api/api/__init__.py new file mode 100644 index 0000000..f1a622b --- /dev/null +++ b/tofu_api/api/__init__.py @@ -0,0 +1 @@ +from .rest_api import TofuApiBlueprint diff --git a/tofu_api/api/rest_api.py b/tofu_api/api/rest_api.py new file mode 100644 index 0000000..e9abdaa --- /dev/null +++ b/tofu_api/api/rest_api.py @@ -0,0 +1,14 @@ +from flask import Blueprint + +from .tasks import TaskApiBlueprint + + +class TofuApiBlueprint(Blueprint): + """ + Main blueprint for the Tofu REST API. + """ + + def __init__(self): + super().__init__('rest_api', __name__, url_prefix='/api') + + self.register_blueprint(TaskApiBlueprint()) diff --git a/tofu_api/api/tasks/__init__.py b/tofu_api/api/tasks/__init__.py new file mode 100644 index 0000000..021af83 --- /dev/null +++ b/tofu_api/api/tasks/__init__.py @@ -0,0 +1 @@ +from .task_api import TaskApiBlueprint diff --git a/tofu_api/api/tasks/task_api.py b/tofu_api/api/tasks/task_api.py new file mode 100644 index 0000000..931c4aa --- /dev/null +++ b/tofu_api/api/tasks/task_api.py @@ -0,0 +1,84 @@ +from flask import Blueprint, jsonify +from flask.views import MethodView + + +class TaskApiBlueprint(Blueprint): + """ + Blueprint for the tasks REST API. + """ + + def __init__(self): + super().__init__('rest_api_tasks', __name__, url_prefix='/tasks') + + self.add_url_rule( + '/', + view_func=TaskCollectionView.as_view(TaskCollectionView.__name__), + methods=['GET', 'POST'], + ) + self.add_url_rule( + '/', + view_func=TaskItemView.as_view(TaskItemView.__name__), + methods=['GET', 'PATCH', 'DELETE'], + ) + + +class TaskCollectionView(MethodView): + """ + View class for `/tasks` endpoint. + """ + + def get(self): + """ + Get list of all tasks. + """ + # TODO: Get actual data + return jsonify({ + 'count': 1, + 'items': [ + { + 'id': 1, + 'title': 'Do stuff', + 'description': '', + 'status': 'open', + }, + ], + }), 200 + + def post(self): + """ + Create a new task. + """ + # TODO: Implement + raise NotImplementedError + + +class TaskItemView(MethodView): + """ + View class for `/tasks/` endpoint. + """ + + def get(self, task_id: int): + """ + Get a single task by ID. + """ + # TODO: Get actual data + return jsonify({ + 'id': task_id, + 'title': 'Do stuff', + 'description': '', + 'status': 'open', + }), 200 + + def patch(self, task_id: int): + """ + Update a single task by ID. + """ + # TODO: Implement + raise NotImplementedError + + def delete(self, task_id: int): + """ + Delete a single task by ID. + """ + # TODO: Implement + raise NotImplementedError diff --git a/tofu_api/app.py b/tofu_api/app.py index e570081..b21d5de 100644 --- a/tofu_api/app.py +++ b/tofu_api/app.py @@ -1,10 +1,32 @@ -def app(environ, start_response): - """Simplest possible application object""" - data = b'Hello, wooorld!\n' - status = '200 OK' - response_headers = [ - ('Content-type', 'text/plain'), - ('Content-Length', str(len(data))) - ] - start_response(status, response_headers) - return iter([data]) +import os + +import yaml +from flask import Flask + +from tofu_api.api import TofuApiBlueprint +from tofu_api.config import DefaultConfig + + +def create_app() -> Flask: + """ + App factory, returns a Flask app object. + """ + # Set instance path to the project root directory + project_root_dir = os.path.abspath('.') + + # Create and configure the app + app = Flask( + 'tofu_api', + instance_path=project_root_dir, + instance_relative_config=True, + ) + app.config.from_object(DefaultConfig) + + # Load app configuration from YAML file + app.config.from_file(os.getenv('FLASK_CONFIG_FILE', default='config.yml'), load=yaml.safe_load) + + # Register blueprints + app.register_blueprint(TofuApiBlueprint()) + + # Return WSGI app + return app diff --git a/tofu_api/config.py b/tofu_api/config.py new file mode 100644 index 0000000..fbca7a3 --- /dev/null +++ b/tofu_api/config.py @@ -0,0 +1,5 @@ +class DefaultConfig: + """ + This class defined default values for the app configuration. + """ + # TODO