Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_ENV=development
ENV_FOR_DYNACONF=development
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ instance/
.scrapy

# Sphinx documentation
docs/_build/
documentation/_build/

# PyBuilder
target/
Expand Down Expand Up @@ -127,3 +127,10 @@ dmypy.json

# Pyre type checker
.pyre/

# Intellij
.idea/
*.iml

# MacOS
DS_Store
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
# pyflask-base
An opinionated basic flask project structure for ideal development
pyflask-microservice-base
------------------------------------------------------------------------------
An "optionally opinionated and structured" flask boilerplate microservice for ideal development

![WIP](https://img.shields.io/badge/%20%F0%9F%9A%A7%20-Work%20in%20progress-important)

Usage Guide
------------------------------------------------------------------------------
This is a template project hosted on GitHub which can be used to create new repositories.

![GitHub Guide: Creating a repository from a template](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template)


20 changes: 20 additions & 0 deletions base_constructor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

echo "Welcome to pyflask base constructor"

# Check for venv, conda else exit

echo "Installing pip-tools . . ."
pip install pip-tools

echo "Generating requirements"
pip-compile --output-file=requirements/requirements.txt requirements/requirements.in

echo "Generating dev requirements"
pip-compile --output-file=requirements/dev-requirements.txt requirements/dev-requirements.in

echo "Installing requirements"
pip install -r requirements/requirements.txt

echo "Installing dev requirements"
pip install -r requirements/dev-requirements.txt
32 changes: 32 additions & 0 deletions documentation/conda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Conda documentation

### 1. Install Pip
### 2. Install Anaconda
##### Linux
`wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh`

`bash ./Miniconda3-latest-Linux-x86_64.sh`

##### MacOS
`brew cask install anaconda`

`export PATH="/usr/local/anaconda3/bin:$PATH"`

##### Create conda virtual env
`conda create -n pyflask-base python=3.8`

```
#
# To activate this environment, use
#
# $ conda activate pyflask-base
#
# To deactivate an active environment, use
#
# $ conda deactivate
```

##### Run conda virtual env
`conda activate pyflask-base`


7 changes: 7 additions & 0 deletions documentation/flask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Introduction to Flask

Flask [Homepage](https://flask.palletsprojects.com/en/1.1.x/)

# Tutorial

Follow [The Flask Mega-Tutorial Part I: Hello, World!](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world)
Empty file.
6 changes: 6 additions & 0 deletions requirements/dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file=requirements/dev-requirements.txt requirements/dev-requirements.in
#
4 changes: 4 additions & 0 deletions requirements/requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Flask
dynaconf
flask_apispec
flask_cors
19 changes: 19 additions & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
#
apispec==3.3.1 # via flask-apispec
click==7.1.2 # via flask
dynaconf==3.0.0 # via -r requirements/requirements.in
flask-apispec==0.9.0 # via -r requirements/requirements.in
flask-cors==3.0.8 # via -r requirements/requirements.in
flask==1.1.2 # via -r requirements/requirements.in, flask-apispec, flask-cors
itsdangerous==1.1.0 # via flask
jinja2==2.11.2 # via flask
markupsafe==1.1.1 # via jinja2
marshmallow==3.7.1 # via flask-apispec, webargs
six==1.15.0 # via flask-apispec, flask-cors
webargs==5.5.3 # via flask-apispec
werkzeug==1.0.1 # via flask
15 changes: 15 additions & 0 deletions service_master.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dynaconf import settings

from src.app import app
from src.core import Logger

log = Logger()


if __name__ == "__main__":
log.info(f"Created app instance. Initiating run . . .")
app.run(
host=settings.API.SERVER.url,
port=settings.API.SERVER.port,
debug=settings.DEBUG,
)
77 changes: 77 additions & 0 deletions settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"default": {
"DEBUG": true,
"PROFILER": {
"flask-profiler": true,
"line-profiler": true,
"line-profiler-type": "deterministic"
},
"API": {
"SERVER": {
"url": "0.0.0.0",
"port": 8420
},
"VERSION": "/api/v1/"
},
"APP_NAME": "pyflask-service",
"LOGGING": {
"basepath": "logs"
},
"MONGO": {
"uri": "",
"db": "",
"max_pool_size": 100
},
"POSTGRES": {
"uri": "",
"db": ""
},
"REDIS": {
"url": ""
}
},
"development": {
"DEBUG": true,
"PROFILER": {
"flask-profiler": true,
"line-profiler": true,
"line-profiler-type": "deterministic"
},
"MONGO": {
"uri": "mongodb://localhost:27017/testdb",
"db": "testdb",
"max_pool_size": 100
},
"POSTGRES": {
"uri": "postgresql://localhost:5432",
"db": "testdb"
},
"REDIS": {
"url": "redis://localhost:6379"
}
},
"qa": {
"DEBUG": false,
"PROFILER": {
"flask-profiler": true,
"line-profiler": false,
"line-profiler-type": ""
}
},
"preprod": {
"DEBUG": false,
"PROFILER": {
"flask-profiler": false,
"line-profiler": false,
"line-profiler-type": ""
}
},
"production": {
"DEBUG": false,
"PROFILER": {
"flask-profiler": false,
"line-profiler": false,
"line-profiler-type": ""
}
}
}
Empty file added src/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from src.core import Logger
from src.routes import register_blueprints
from src.server import Server

log = Logger()

log.info(f"Creating app instance . . .")
server = Server() # Generate singleton instance of server
app = server.get_app() # Get Flask app reference from server instance
log.info(f"Registering blueprints . . .")
register_blueprints(app) # Register all blueprints to app reference

3 changes: 3 additions & 0 deletions src/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from src.core.logger import Logger
from src.core.namespace import Namespace
from src.core.singleton import Singleton
54 changes: 54 additions & 0 deletions src/core/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# coding: utf-8

import logging
import os
import sys
from logging.handlers import TimedRotatingFileHandler
from dynaconf import settings

from src.core.singleton import Singleton

FORMATTER = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
LOGGING_FILE_BASE_PATH = settings["LOGGING"]["basepath"]


class Logger(metaclass=Singleton):
def __init__(self, name="logger"):
self.name = name
self.logger = logging.getLogger(settings["APP_NAME"])
self.logger.setLevel(logging.DEBUG)
self.logger.addHandler(self.get_console_handler())
self.logger.addHandler(self.get_file_handler())

@staticmethod
def check_create_base_folder():
if not os.path.exists(LOGGING_FILE_BASE_PATH):
os.makedirs(LOGGING_FILE_BASE_PATH)

@staticmethod
def get_console_handler():
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(FORMATTER)
return console_handler

def get_file_handler(self):
self.check_create_base_folder()
log_file = f"{LOGGING_FILE_BASE_PATH}/{settings['APP_NAME']}.log"
file_handler = TimedRotatingFileHandler(log_file, when="midnight")
file_handler.setFormatter(FORMATTER)
return file_handler

def debug(self, msg):
self.logger.debug(msg)

def info(self, msg):
self.logger.info(msg)

def warning(self, msg):
self.logger.warning(msg)

def error(self, e):
self.logger.error(e, exc_info=True)

def exception(self):
return self.logger.exception
9 changes: 9 additions & 0 deletions src/core/namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask import Blueprint
from dynaconf import settings


class Namespace(object):
def __init__(self, path=None):
self.base_api_url = settings.API.VERSION + settings.APP_NAME
self.path = path
self.api = Blueprint(path, __name__, url_prefix=self.base_api_url + "/" + path)
7 changes: 7 additions & 0 deletions src/core/singleton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Singleton(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
9 changes: 9 additions & 0 deletions src/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from src.core import Logger
from .health import api as health_blueprint

log = Logger()


def register_blueprints(app):
app.register_blueprint(health_blueprint)
log.info(f"Registered health blueprint")
12 changes: 12 additions & 0 deletions src/routes/health/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from src.core import Logger, Namespace

Client = Namespace("health")
api = Client.api
log = Logger()


@api.route("/ping", methods=["GET"])
def get_health():
log.info("Request received : ping")
return {"statusCode": 200}

24 changes: 24 additions & 0 deletions src/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dynaconf import settings
from flask import Flask
from flask_apispec import FlaskApiSpec
from flask_cors import CORS

from src.core import Singleton, Logger

log = Logger()


class Server(metaclass=Singleton):
def __init__(self):
app = Flask(__name__)
with app.app_context():
log.info(f"Starting {settings.APP_NAME} server on {settings.API.SERVER}")
CORS(app)
self.app = app
self.docs = FlaskApiSpec(app)

def get_app(self):
return self.app

def get_docs(self):
return self.docs
5 changes: 5 additions & 0 deletions src/util/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

def has_no_empty_params(rule):
defaults = rule.defaults if rule.defaults is not None else ()
arguments = rule.arguments if rule.arguments is not None else ()
return len(defaults) >= len(arguments)