Skip to content

Commit

Permalink
Fixed linting warnings and added pylint
Browse files Browse the repository at this point in the history
  • Loading branch information
rofrano committed Jul 9, 2022
1 parent 9d2eefe commit 8c8d6f0
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 69 deletions.
2 changes: 1 addition & 1 deletion dot-env-example → .flaskenv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
PORT=8080
FLASK_RUN_PORT=8080
FLASK_APP=service:app
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
58 changes: 49 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,34 +1,74 @@
.PHONY: all help install venv test run
# These can be overidden with env vars.
REGISTRY ?= rofrano
IMAGE_NAME ?= hitcounter
IMAGE_TAG ?= 1.0
IMAGE ?= $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
PLATFORM ?= "linux/amd64,linux/arm64"

.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-\\.]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

.PHONY: all
all: help

.PHONY: clean
clean: ## Removes all dangling build cache
$(info Removing all dangling build cache..)
-docker rmi $(IMAGE)
docker image prune -f
docker buildx prune -f

.PHONY: venv
venv: ## Create a Python virtual environment
$(info Creating Python 3 virtual environment...)
python3 -m venv .venv

.PHONY: install
install: ## Install dependencies
$(info Installing dependencies...)
sudo pip install -r requirements.txt

.PHONY: lint
lint: ## Run the linter
$(info Running linting...)
flake8 service --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 service --count --max-complexity=10 --max-line-length=127 --statistics
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --max-complexity=10 --max-line-length=127 --statistics
pylint service

.PHONY: test
test: ## Run the unit tests
$(info Running tests...)
nosetests --with-spec --spec-color

.PHONY: run
run: ## Run the service
$(info Starting service...)
honcho start

depoy: ## Deploy the service on Kubernetes
$(info Deploying service...)
kubectl apply -f kube/redis.yaml
kubectl apply -f kube/secrets.yaml
kubectl apply -f kube/deployment.yaml
kubectl apply -f kube/service.yaml
.PHONY: deploy
depoy: ## Deploy the service on local Kubernetes
$(info Deploying service locally...)
kubectl apply -k kube/overlay/local

############################################################
# COMMANDS FOR BUILDING THE IMAGE
############################################################

.PHONY: init
init: export DOCKER_BUILDKIT=1
init: ## Creates the buildx instance
$(info Initializing Builder...)
docker buildx create --use --name=qemu
docker buildx inspect --bootstrap

.PHONY: build
build: ## Build all of the project Docker images
$(info Building $(IMAGE) for $(PLATFORM)...)
docker buildx build --file Dockerfile --pull --platform=$(PLATFORM) --tag $(IMAGE) --push .

.PHONY: remove
remove: ## Stop and remove the buildx builder
$(info Stopping and removing the builder image...)
docker buildx stop
docker buildx rm
16 changes: 7 additions & 9 deletions service/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2016, 2020 John J. Rofrano. All Rights Reserved.
# Copyright 2016, 2022 John J. Rofrano. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@
import os
import logging
from flask import Flask
from service.utils import log_handlers

# NOTE: Do not change the order of this code
# The Flask app must be created
Expand All @@ -28,16 +29,13 @@
# Create the Flask aoo
app = Flask(__name__)

# Import the routes After the Flask app is created
from service import routes, models, error_handlers
# Dependencies require we import the routes AFTER the Flask app is created
# pylint: disable=wrong-import-position, wrong-import-order, cyclic-import
from service import routes
from service.utils import error_handlers # noqa: F401, E402

# Set up logging for production
app.logger.propagate = False
if __name__ != "__main__":
gunicorn_logger = logging.getLogger("gunicorn.error")
if gunicorn_logger:
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
log_handlers.init_logging(app, "gunicorn.error")

app.logger.info(70 * "*")
app.logger.info(" H I T C O U N T E R S E R V I C E ".center(70, "*"))
Expand Down
20 changes: 11 additions & 9 deletions service/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
######################################################################
# Copyright 2016, 2020 John Rofrano. All Rights Reserved.
# Copyright 2016, 2022 John Rofrano. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
Expand All @@ -19,18 +19,18 @@
import os
import logging
from redis import Redis
from redis.exceptions import ConnectionError
from redis.exceptions import ConnectionError as RedisConnectionError

logger = logging.getLogger(__name__)

DATABASE_URI = os.getenv("DATABASE_URI", "redis://localhost:6379")


class DatabaseConnectionError(ConnectionError):
pass
class DatabaseConnectionError(RedisConnectionError):
"""Indicates that a database connection error has occurred"""


class Counter(object):
class Counter():
"""An integer counter that is persisted in Redis
You can establish a connection to Redis using an environment
Expand Down Expand Up @@ -71,6 +71,7 @@ def increment(self):
return Counter.redis.incr(self.name)

def serialize(self):
"""Creates a Python dictionary from the instance"""
return dict(name=self.name, counter=int(Counter.redis.get(self.name)))

######################################################################
Expand All @@ -86,7 +87,7 @@ def all(cls):
for key in cls.redis.keys("*")
]
except Exception as err:
raise DatabaseConnectionError(err)
raise DatabaseConnectionError(err) from err
return counters

@classmethod
Expand All @@ -98,15 +99,16 @@ def find(cls, name):
if count:
counter = Counter(name, count)
except Exception as err:
raise DatabaseConnectionError(err)
raise DatabaseConnectionError(err) from err
return counter

@classmethod
def remove_all(cls):
"""Deletes all of the counters"""
try:
cls.redis.flushall()
except Exception as err:
raise DatabaseConnectionError(err)
raise DatabaseConnectionError(err) from err

######################################################################
# R E D I S D A T A B A S E C O N N E C T I O N M E T H O D S
Expand All @@ -120,7 +122,7 @@ def test_connection(cls):
cls.redis.ping()
logger.info("Connection established")
success = True
except ConnectionError:
except RedisConnectionError:
logger.warning("Connection Error!")
return success

Expand Down
28 changes: 15 additions & 13 deletions service/routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015, 2021 John J. Rofrano All Rights Reserved.
# Copyright 2015, 2022 John J. Rofrano All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -14,14 +14,10 @@
"""
Redis Counter Demo in Docker
"""
import os
from flask import jsonify, abort, url_for
from . import app, status # HTTP Status Codes
from service import DATABASE_URI
from .models import Counter, DatabaseConnectionError

DEBUG = os.getenv("DEBUG", "False") == "True"
PORT = os.getenv("PORT", "8080")
from service import app, DATABASE_URI
from service.utils import status # HTTP Status Codes
from service.models import Counter, DatabaseConnectionError


############################################################
Expand All @@ -47,6 +43,7 @@ def index():
############################################################
@app.route("/counters", methods=["GET"])
def list_counters():
"""Lists all counters in the database"""
app.logger.info("Request to list all counters...")
try:
counters = Counter.all()
Expand All @@ -61,17 +58,18 @@ def list_counters():
############################################################
@app.route("/counters/<name>", methods=["GET"])
def read_counters(name):
app.logger.info("Request to Read counter: {}...".format(name))
"""Reads a counter from the database"""
app.logger.info("Request to Read counter: %s...", name)

try:
counter = Counter.find(name)
except DatabaseConnectionError as err:
abort(status.HTTP_503_SERVICE_UNAVAILABLE, err)

if not counter:
abort(status.HTTP_404_NOT_FOUND, "Counter {} does not exist".format(name))
abort(status.HTTP_404_NOT_FOUND, f"Counter [{name}] does not exist")

app.logger.info("Returning: {}...".format(counter.value))
app.logger.info("Returning: %d...", counter.value)
return jsonify(counter.serialize())


Expand All @@ -80,11 +78,12 @@ def read_counters(name):
############################################################
@app.route("/counters/<name>", methods=["POST"])
def create_counters(name):
"""Creates a counter in the database"""
app.logger.info("Request to Create counter...")
try:
counter = Counter.find(name)
if counter is not None:
return jsonify(code=409, error="Counter already exists"), 409
return jsonify(code=409, error=f"Counter [{name}] already exists"), 409

counter = Counter(name)
except DatabaseConnectionError as err:
Expand All @@ -103,12 +102,13 @@ def create_counters(name):
############################################################
@app.route("/counters/<name>", methods=["PUT"])
def update_counters(name):
"""Updates a counter in the database"""
app.logger.info("Request to Update counter...")
try:
counter = Counter.find(name)
if counter is None:
return (
jsonify(code=404, error="Counter {} does not exist".format(name)),
jsonify(code=404, error=f"Counter [{name}] does not exist"),
404,
)

Expand All @@ -124,6 +124,7 @@ def update_counters(name):
############################################################
@app.route("/counters/<name>", methods=["DELETE"])
def delete_counters(name):
"""Removes te counter from teh database"""
app.logger.info("Request to Delete counter...")
try:
counter = Counter.find(name)
Expand All @@ -142,6 +143,7 @@ def delete_counters(name):

@app.before_first_request
def init_db():
"""Initialize the Redis database"""
try:
app.logger.info("Initializing the Redis database")
Counter.connect(DATABASE_URI)
Expand Down
48 changes: 46 additions & 2 deletions service/error_handlers.py → service/utils/error_handlers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
# Copyright 2016, 2022 John J. Rofrano. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Module: error_handlers
"""
from flask import jsonify
from . import app, status
from service import app
from . import status


######################################################################
# Error Handlers
######################################################################
# @app.errorhandler(status.HTTP_400_BAD_REQUEST)
# def bad_request(error):
# """Handles bad requests with 400_BAD_REQUEST"""
# message = str(error)
# app.logger.warning(message)
# return (
# jsonify(
# status=status.HTTP_400_BAD_REQUEST, error="Bad Request", message=message
# ),
# status.HTTP_400_BAD_REQUEST,
# )


@app.errorhandler(status.HTTP_404_NOT_FOUND)
Expand Down Expand Up @@ -32,6 +61,21 @@ def method_not_supported(error):
)


# @app.errorhandler(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
# def mediatype_not_supported(error):
# """Handles unsupported media requests with 415_UNSUPPORTED_MEDIA_TYPE"""
# message = str(error)
# app.logger.warning(message)
# return (
# jsonify(
# status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
# error="Unsupported media type",
# message=message,
# ),
# status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
# )


@app.errorhandler(status.HTTP_500_INTERNAL_SERVER_ERROR)
def internal_server_error(error):
"""Handles unexpected server error with 500_SERVER_ERROR"""
Expand All @@ -49,7 +93,7 @@ def internal_server_error(error):

@app.errorhandler(status.HTTP_503_SERVICE_UNAVAILABLE)
def service_unavailable(error):
"""Handles unexpected server error with 503_SERVICE_UNAVAILABLE"""
""" Handles unexpected server error with 503_SERVICE_UNAVAILABLE """
message = str(error)
app.logger.error(message)
return (
Expand Down
Loading

0 comments on commit 8c8d6f0

Please sign in to comment.