diff --git a/.gitignore b/.gitignore index 3c277e0..ca95377 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,135 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# VS code extension +.vscode/ + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +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/ + +# 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 +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 -env -venv -__pycache__ - -*.pyc -*.sqlite -*.coverage -.DS_Store -env.sh -migrations +.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/ + +# pre-commit +packagename.egg-info/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..b7668d2 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,6 @@ +[settings] +profile = black +line_length = 88 +multi_line_output = 3 +include_trailing_comma = True +known_third_party = base,flask,flask_bootstrap,flask_testing,pandas,pydantic,redis,requests,rq \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..48cecf3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +repos: +- repo: https://github.com/asottile/seed-isort-config + rev: v2.2.0 + hooks: + - id: seed-isort-config +- repo: https://github.com/pre-commit/mirrors-isort + rev: v5.10.1 + hooks: + - id: isort +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 # Use the ref you want to point at + hooks: + - id: trailing-whitespace + - id: check-case-conflict + - id: check-added-large-files + exclude: ^pre-commit + - id: check-merge-conflict +- repo: https://github.com/ambv/black + rev: 22.3.0 + hooks: + - id: black +- repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + args: [ + "--max-line-length=89", + "--max-complexity=18", + "--config=setup.cfg", + "--ignore=T001,T003" + ] + exclude: __init__.py|manage.py|config.py|env.py + additional_dependencies: [ + flake8-print, pep8-naming, flake8-alfred, flake8-bugbear, flake8-todo, pydocstyle, Pygments, + # flake8-builtins,flake8-comprehensions, flake8-pytest-style, flake8-docstrings, flake8-eradicate, flake8-unused-arguments, + ] diff --git a/.python-version b/.python-version index 1981190..7b59a5c 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.8.0 +3.10.2 diff --git a/Dockerfile b/Dockerfile index c5b4822..5bcdd6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,61 @@ # base image -FROM python:3.8.0-alpine +FROM ubuntu:20.04 -# set working directory +# Set location +ENV TZ=Asia/Kolkata \ + DEBIAN_FRONTEND=noninteractive + +# Update and install python, pip +RUN apt-get update -y +RUN apt-get install tzdata +RUN apt-get install -y python3-pip python3-dev build-essential curl +RUN apt-get update -y +RUN apt upgrade -y +RUN apt-get install -y wkhtmltopdf + +RUN apt-get update && \ + apt-get install -y software-properties-common && \ + rm -rf /var/lib/apt/lists/* +RUN add-apt-repository -y ppa:alex-p/tesseract-ocr +RUN apt-get update +RUN apt-get -y install tesseract-ocr +# pytesseract using pip forgot to install binaries. +# Ref: https://stackoverflow.com/questions/50655738/how-do-i-resolve-a-tesseractnotfounderror +# Ref: https://docs.ropensci.org/tesseract/ +RUN apt-get install -y libtesseract-dev libleptonica-dev tesseract-ocr-eng +RUN cd /usr/share/tesseract-ocr/4.00/tessdata/ +RUN apt install wget +RUN wget https://github.com/tesseract-ocr/tessdata/blob/main/eng.traineddata +RUN cd /usr/share/tesseract-ocr/4.00/tessdata/ +RUN wget https://github.com/tesseract-ocr/tessdata/blob/main/osd.traineddata + +CMD /bin/bash + +# LABEL about the custom image +LABEL maintainer="vaibhav.hiwase@gmail.com" +LABEL version="0.1" +LABEL description="This is custom Docker Image." + +# Set working directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app -# add requirements (to leverage Docker cache) +# Install some requirements (not a part of this project) +RUN pip3 install pip --upgrade +RUN pip3 install pillow==8.3.2 +RUN pip3 install pytesseract==0.3.8 +RUN pip3 install spacy==3.2.4 +RUN python3 -m spacy download en_core_web_sm + +# Add requirements (to leverage Docker cache) ADD ./requirements.txt /usr/src/app/requirements.txt -# install requirements -RUN pip install -r requirements.txt +# Install depedencies +RUN pip3 install -r requirements.txt -# copy project + +# Copy project COPY . /usr/src/app + +EXPOSE 5000 +CMD ["python3", "-u", "manage.py", "run", "-h", "0.0.0.0"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index a59e7e6..b3f8034 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Michael Herman +Copyright (c) 2022 Michael Herman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3ee1614..86769fe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Flask Redis Queue +# Demo flask redis dockerized templates Example of how to handle background processes with Flask, Redis Queue, and Docker @@ -7,7 +7,77 @@ Example of how to handle background processes with Flask, Redis Queue, and Docke Spin up the containers: ```sh -$ docker-compose up -d --build +$ docker-compose up --build -V --scale worker=4 +``` + +Spin up the containers in background: + +```sh +$ docker-compose up -d --build -V --scale worker=4 +``` + + +Stop all containers and workers: + +```sh +$ docker-compose down -v ``` Open your browser to http://localhost:5004 + +Open redis dashboard in http://localhost:9181/ + +Show logs from worker containers: +```sh +docker-compose logs --tail=0 -f master +docker-compose logs --tail=0 -f worker +``` + +You can view a list of all the allocated volumes in your system with +```sh +docker volume ls +``` + +If you prefer a more automatic cleanup, the following command will remove any unused images or volumes, and any stopped containers that are still in the system. +```sh +docker system prune --volumes +``` + +## Some important docker commands: +Below command will remove the following: + - all stopped containers + - all networks not used by at least one container + - all dangling images + - all dangling build cache +```sh +docker system prune +``` +Below command will remove the following: + - all stopped containers + - all networks not used by at least one container + - all images without at least one container associated to them + - all build cache +```sh +docker system prune --all --force +``` +Below command will remove all docker images: +```sh +docker rmi --force $(docker images --all --quiet) +``` + +# Contribution + +## Pre-commit +Following command will help to remove trailing-whitespace, check case conflict, check added large files, +check merge conflict by using isort, black and flake8 automation tools. +```sh +python3 pre-commit-2.15.0.pyz run -a +``` + +## Delete __pycache__ files +```sh +find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf +``` + +## Upgrade +This architecture has been upgraded in [this](https://github.com/vhiwase/react_native-python_flask-sql-nignx-with-authentication-boilerplate) repo. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5b479bf..5e38a24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,63 @@ -version: '3.7' +version: '3.8' services: - web: + master: build: . - image: web - container_name: web + image: master + container_name: master ports: - 5004:5000 - command: python manage.py run -h 0.0.0.0 volumes: - - .:/usr/src/app + - app-volume:/usr/src/app + # volumes: + # - .:/usr/src/app environment: - FLASK_DEBUG=1 - APP_SETTINGS=project.server.config.DevelopmentConfig + - REDIS_URL=redis://redis:6379/0 + depends_on: + - redis + + worker: + image: master + command: python3 -u manage.py run_worker + volumes: + - app-volume:/usr/src/app + # volumes: + # - .:/usr/src/app + environment: + - APP_SETTINGS=project.server.config.DevelopmentConfig + - REDIS_URL=redis://redis:6379/0 + depends_on: + - redis + - master + + redis: + image: redis:6.2-alpine + ports: + - 6379:6379 + + dashboard: + build: ./project/dashboard + image: dashboard + container_name: dashboard + ports: + - 9181:9181 + command: rq-dashboard -H redis + depends_on: + - redis + + webhook: + build: ./webhook + image: webhook + container_name: webhook + ports: + - 8888:8888 + environment: + - APP_SETTINGS=project.server.config.DevelopmentConfig + - WEBHOOK_ENDPOINT=http://webhook:8888 + +volumes: + app-volume: + name: app-volume-001 \ No newline at end of file diff --git a/manage.py b/manage.py index f395c01..68342a0 100644 --- a/manage.py +++ b/manage.py @@ -3,11 +3,12 @@ import unittest +import redis from flask.cli import FlaskGroup +from rq import Connection, Worker from project.server import create_app - app = create_app() cli = FlaskGroup(create_app=create_app) @@ -22,5 +23,14 @@ def test(): return 1 +@cli.command("run_worker") +def run_worker(): + redis_url = app.config["REDIS_URL"] + redis_connection = redis.from_url(redis_url) + with Connection(redis_connection): + worker = Worker(app.config["QUEUES"]) + worker.work() + + if __name__ == "__main__": cli() diff --git a/postman-collection/Demo Flask Redis Docker templates.postman_collection.json b/postman-collection/Demo Flask Redis Docker templates.postman_collection.json new file mode 100644 index 0000000..2a06ab6 --- /dev/null +++ b/postman-collection/Demo Flask Redis Docker templates.postman_collection.json @@ -0,0 +1,150 @@ +{ + "info": { + "_postman_id": "b6992e83-bf22-461c-a075-5a863b64aff3", + "name": "Demo Flask Redis Docker templates", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "POST sync", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = JSON.parse(responseBody);\r", + "pm.collectionVariables.set(\"job_id\", jsonData.data.job_id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "webhook_endpoint", + "value": "http://52.172.224.181:5000", + "type": "text", + "disabled": true + }, + { + "key": "delay", + "value": "5", + "type": "text" + }, + { + "key": "file1", + "type": "file", + "src": [] + }, + { + "key": "file2", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "http://localhost:5004/sync/jobs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "5004", + "path": [ + "sync", + "jobs" + ] + } + }, + "response": [] + }, + { + "name": "POST async", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = JSON.parse(responseBody);\r", + "pm.collectionVariables.set(\"job_id\", jsonData.data.job_id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "webhook_endpoint", + "value": "http://52.172.224.181:5000", + "type": "text" + }, + { + "key": "delay", + "value": "15", + "type": "text" + }, + { + "key": "file1", + "type": "file", + "src": [] + }, + { + "key": "file2", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "http://localhost:5004/async/jobs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "5004", + "path": [ + "async", + "jobs" + ] + } + }, + "response": [] + }, + { + "name": "GET", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:5004/jobs/{{job_id}}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "5004", + "path": [ + "jobs", + "{{job_id}}" + ] + } + }, + "response": [] + } + ], + "variable": [ + { + "key": "job_id", + "value": "" + } + ] +} \ No newline at end of file diff --git a/pre-commit-2.15.0.pyz b/pre-commit-2.15.0.pyz new file mode 100644 index 0000000..d1d9a72 Binary files /dev/null and b/pre-commit-2.15.0.pyz differ diff --git a/project/client/static/favicon.ico b/project/client/static/favicon.ico new file mode 100644 index 0000000..8f67463 Binary files /dev/null and b/project/client/static/favicon.ico differ diff --git a/project/client/static/main.js b/project/client/static/main.js deleted file mode 100644 index 61c8bdc..0000000 --- a/project/client/static/main.js +++ /dev/null @@ -1,43 +0,0 @@ -// custom javascript - -$( document ).ready(() => { - console.log('Sanity Check!'); -}); - -$('.btn').on('click', function() { - $.ajax({ - url: '/tasks', - data: { type: $(this).data('type') }, - method: 'POST' - }) - .done((res) => { - getStatus(res.data.task_id) - }) - .fail((err) => { - console.log(err) - }); -}); - -function getStatus(taskID) { - $.ajax({ - url: `/tasks/${taskID}`, - method: 'GET' - }) - .done((res) => { - const html = ` -