diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index eb474dc..462a418 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -16,6 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Test - run: docker-compose run --rm app sh -c "python manage.py test" - - name: Lint + run: docker-compose run --rm app sh -c "python manage.py wait_for_db && python manage.py test" + - name: run: docker-compose run --rm app sh -c "flake8" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 633552a..39f2150 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,11 +12,15 @@ EXPOSE 8000 ARG DEV=false RUN python -m venv /py && \ /py/bin/pip install --upgrade pip && \ + apk add --update --no-cache postgresql-client && \ + apk add --update --no-cache --virtual .tmp-build-deps \ + build-base postgresql-dev musl-dev && \ /py/bin/pip install -r /tmp/requirements.txt && \ if [ $DEV = "true" ]; \ then /py/bin/pip install -r /tmp/requirements.dev.txt ; \ fi && \ rm -rf /tmp && \ + apk del .tmp-build-deps && \ adduser \ --disabled-password \ --no-create-home \ diff --git a/app/app/calc.py b/app/app/calc.py new file mode 100644 index 0000000..9b9849a --- /dev/null +++ b/app/app/calc.py @@ -0,0 +1,12 @@ +""" +Calculator functions +""" + + +def add(x, y): + """Add x and y and return result.""" + return x + y + + +def subtract(x, y): + return y - x diff --git a/app/app/settings.py b/app/app/settings.py index db01d07..787e479 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/3.2/ref/settings/ """ +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -37,6 +38,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'core', ] MIDDLEWARE = [ @@ -75,8 +77,11 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'HOST': os.environ.get('DB_HOST'), + 'NAME': os.environ.get("DB_NAME"), + 'USER': os.environ.get("DB_USER"), + 'PASSWORD': os.environ.get('DB_PASS'), } } diff --git a/app/app/tests.py b/app/app/tests.py new file mode 100644 index 0000000..f81eb08 --- /dev/null +++ b/app/app/tests.py @@ -0,0 +1,22 @@ +""" +Sample tests +""" +from django.test import SimpleTestCase + +from app import calc + + +class CalcTests(SimpleTestCase): + """Test the calc module""" + + def test_add_numbers(self): + """Test adding numbers together""" + res = calc.add(5, 6) + + self.assertEqual(res, 11) + + def test_substract_numbers(self): + """Test subtracting numbers""" + res = calc.subtract(10, 15) + + self.assertEqual(res, 5) diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/admin.py b/app/core/admin.py new file mode 100644 index 0000000..6af52da --- /dev/null +++ b/app/core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin # noqa + +# Register your models here. diff --git a/app/core/apps.py b/app/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/app/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/app/core/management/__init__.py b/app/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/management/commands/__init__.py b/app/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/management/commands/wait_for_db.py b/app/core/management/commands/wait_for_db.py new file mode 100644 index 0000000..8230dbe --- /dev/null +++ b/app/core/management/commands/wait_for_db.py @@ -0,0 +1,27 @@ +""" +Django command to wait for the database to be available. +""" +import time + +from psycopg2 import OperationalError as Psycopg2Error + +from django.db.utils import OperationalError +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + """Django command to wait for database.""" + + def handle(self, *args, **options): + """Entrypoint for command""" + self.stdout.write("Waiting for database...") + db_up = False + while db_up is False: + try: + self.check(databases=['default']) + db_up = True + except (Psycopg2Error, OperationalError): + self.stdout.write('Database unavailable, waiting 1 second...') + time.sleep(1) + + self.stdout.write(self.style.SUCCESS('Database available!')) diff --git a/app/core/migrations/__init__.py b/app/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/models.py b/app/core/models.py new file mode 100644 index 0000000..56b48a5 --- /dev/null +++ b/app/core/models.py @@ -0,0 +1,3 @@ +from django.db import models # noqa + +# Create your models here. diff --git a/app/core/tests/__init__.py b/app/core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/tests/test_commands.py b/app/core/tests/test_commands.py new file mode 100644 index 0000000..46922ba --- /dev/null +++ b/app/core/tests/test_commands.py @@ -0,0 +1,33 @@ +""" +Test custom Django management commands. +""" +from unittest.mock import patch + +from psycopg2 import OperationalError as Psycopg2Error + +from django.core.management import call_command +from django.db.utils import OperationalError +from django.test import SimpleTestCase + + +@patch('core.management.commands.wait_for_db.Command.check') +class CommandsTestS(SimpleTestCase): + """Test commands.""" + + def test_wait_for_db_ready(self, patched_check): + """Test waiting for database if database ready""" + patched_check.return_value = True + + call_command('wait_for_db') + + patched_check.assert_called_once_with(databases=['default']) + + @patch('time.sleep') + def test_wait_for_db_delay(self, patched_sleep, patched_check): + patched_check.side_effect = [Psycopg2Error] * 2 + \ + [OperationalError] * 3 + [True] + + call_command('wait_for_db') + + self.assertEqual(patched_check.call_count, 6) + patched_check.assert_called_with(databases=['default']) diff --git a/docker-compose.yml b/docker-compose.yml index fbf9c6c..62c91e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,5 +11,28 @@ services: volumes: - ./app:/app command: > - sh -c "python manage.py runserver 0.0.0.0:8000" - + sh -c "python manage.py wait_for_db && + python manage.py migrate && + python manage.py runserver 0.0.0.0:8000" + environment: + - DB_HOST=db + - DB_NAME=devdb + - DB_USER=devuser + - DB_PASS=changeme + depends_on: + - db + + + db: + image: postgres:13-alpine + volumes: + - dev-db-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=devdb + - POSTGRES_USER=devuser + - POSTGRES_PASSWORD=changeme + + +volumes: + dev-db-data: + diff --git a/requirements.txt b/requirements.txt index 981b9a4..ef44a77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ Django>=3.2.4,<3.3 -djangorestframework>=3.12.4,<3.13 \ No newline at end of file +djangorestframework>=3.12.4,<3.13 +psycopg2>=2.8.6,<2.11 \ No newline at end of file