Skip to content

Commit c245dc4

Browse files
committed
Add app factory
1 parent 8a8635f commit c245dc4

File tree

12 files changed

+555
-460
lines changed

12 files changed

+555
-460
lines changed

Procfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web: gunicorn --bind 0.0.0.0:$PORT --log-level=info service:app
1+
web: gunicorn --bind 0.0.0.0:$PORT --log-level=info wsgi:app

poetry.lock

Lines changed: 20 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ readme = "README.md"
1010
python = "^3.11"
1111
Flask = "^3.0.2"
1212
redis = "^4.5.3"
13+
flask-redis = "^0.4.0"
1314
python-dotenv = "^1.0.0"
1415
gunicorn = "^21.2.0"
1516
honcho = "^1.1.0"

service/__init__.py

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,62 @@
1+
# Copyright 2016, 2023 John J. Rofrano. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
"""
2-
Package: service
316
Package for the application models and service routes
4-
This module creates and configures the Flask app and sets up the logging
5-
and SQL database
617
"""
7-
import sys
818
from flask import Flask
19+
from flask_redis import FlaskRedis
920
from service import config
10-
from .common import log_handlers
11-
12-
# Create Flask application
13-
app = Flask(__name__)
14-
app.config.from_object(config)
15-
16-
# Dependencies require we import the routes AFTER the Flask app is created
17-
# pylint: disable=wrong-import-position, wrong-import-order
18-
from service import routes
19-
# flake8: noqa: F401
20-
# pylint: disable=wrong-import-position
21-
from .common import error_handlers
22-
from .models import Counter
23-
24-
# Set up logging for production
25-
log_handlers.init_logging(app, "gunicorn.error")
26-
27-
app.logger.info(70 * "*")
28-
app.logger.info(" S E R V I C E R U N N I N G ".center(70, "*"))
29-
app.logger.info(70 * "*")
30-
31-
try:
32-
Counter.init_db(app) # Initialize Redis
33-
except Exception as error: # pylint: disable=broad-except
34-
app.logger.critical("%s: Cannot continue", error)
35-
# gunicorn requires exit code 4 to stop spawning workers when they die
36-
sys.exit(4)
37-
38-
app.logger.info("Service initialized!")
21+
from service.common import log_handlers
22+
23+
# Globally accessible libraries
24+
# redis = FlaskRedis()
25+
26+
27+
############################################################
28+
# Initialize the Flask instance
29+
############################################################
30+
def init_app():
31+
"""Initialize the core application."""
32+
app = Flask(__name__)
33+
app.config.from_object(config)
34+
35+
# Initialize Plugins
36+
# redis.init_app(app)
37+
38+
with app.app_context():
39+
# Include our Routes
40+
41+
# pylint: disable=import-outside-toplevel, unused-import
42+
from service import routes, models
43+
from service.common import error_handlers
44+
45+
# Set up logging for production
46+
log_handlers.init_logging(app, "gunicorn.error")
47+
48+
app.logger.info(70 * "*")
49+
app.logger.info(" H I T C O U N T E R S E R V I C E ".center(70, "*"))
50+
app.logger.info(70 * "*")
51+
52+
app.logger.info("Service initialized!")
53+
54+
# Initialize the database
55+
try:
56+
app.logger.info("Initializing the Redis database")
57+
models.Counter.connect(app.config['DATABASE_URI'])
58+
app.logger.info("Connected!")
59+
except models.DatabaseConnectionError as err:
60+
app.logger.error(str(err))
61+
62+
return app

service/common/error_handlers.py

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,17 @@
1515
######################################################################
1616

1717
"""
18-
Module: error_handlers
18+
Error Handlers
19+
20+
This module contains error handlers functions to send back errors as json
1921
"""
2022
from flask import jsonify
21-
from service import app
22-
from . import status
23-
23+
from flask import current_app as app
24+
from service.common import status
2425

2526
######################################################################
2627
# Error Handlers
2728
######################################################################
28-
@app.errorhandler(status.HTTP_400_BAD_REQUEST)
29-
def bad_request(error):
30-
"""Handles bad requests with 400_BAD_REQUEST"""
31-
message = str(error)
32-
app.logger.warning(message)
33-
return (
34-
jsonify(
35-
status=status.HTTP_400_BAD_REQUEST, error="Bad Request", message=message
36-
),
37-
status.HTTP_400_BAD_REQUEST,
38-
)
3929

4030

4131
@app.errorhandler(status.HTTP_404_NOT_FOUND)
@@ -64,46 +54,31 @@ def method_not_supported(error):
6454
)
6555

6656

67-
@app.errorhandler(status.HTTP_409_CONFLICT)
68-
def resource_conflict(error):
69-
"""Handles resource conflicts with HTTP_409_CONFLICT"""
70-
message = str(error)
71-
app.logger.warning(message)
72-
return (
73-
jsonify(
74-
status=status.HTTP_409_CONFLICT,
75-
error="Conflict",
76-
message=message,
77-
),
78-
status.HTTP_409_CONFLICT,
79-
)
80-
81-
82-
@app.errorhandler(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
83-
def mediatype_not_supported(error):
84-
"""Handles unsupported media requests with 415_UNSUPPORTED_MEDIA_TYPE"""
57+
@app.errorhandler(status.HTTP_500_INTERNAL_SERVER_ERROR)
58+
def internal_server_error(error):
59+
"""Handles unexpected server error with 500_SERVER_ERROR"""
8560
message = str(error)
86-
app.logger.warning(message)
61+
app.logger.error(message)
8762
return (
8863
jsonify(
89-
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
90-
error="Unsupported media type",
64+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
65+
error="Internal Server Error",
9166
message=message,
9267
),
93-
status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
68+
status.HTTP_500_INTERNAL_SERVER_ERROR,
9469
)
9570

9671

97-
@app.errorhandler(status.HTTP_500_INTERNAL_SERVER_ERROR)
98-
def internal_server_error(error):
99-
"""Handles unexpected server error with 500_SERVER_ERROR"""
72+
@app.errorhandler(status.HTTP_503_SERVICE_UNAVAILABLE)
73+
def service_unavailable(error):
74+
"""Handles unexpected server error with 503_SERVICE_UNAVAILABLE"""
10075
message = str(error)
10176
app.logger.error(message)
10277
return (
10378
jsonify(
104-
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
105-
error="Internal Server Error",
79+
status=status.HTTP_503_SERVICE_UNAVAILABLE,
80+
error="Service is unavailable",
10681
message=message,
10782
),
108-
status.HTTP_500_INTERNAL_SERVER_ERROR,
83+
status.HTTP_503_SERVICE_UNAVAILABLE,
10984
)

service/common/status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# coding: utf8
12
"""
23
Descriptive HTTP status codes, for code readability.
34
See RFC 2616 and RFC 6585.

service/config.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
Global Configuration for Application
33
"""
44
import os
5+
import logging
56

6-
# Get the database from the environment (12 factor)
7-
DATABASE_URI = os.getenv("DATABASE_URI", "redis://localhost:6379")
8-
9-
# Secret for session management
10-
SECRET_KEY = os.getenv("SECRET_KEY", "s3cr3t-key-shhhh")
7+
# Get configuration from environment
8+
DATABASE_URI = os.getenv("DATABASE_URI", "redis://:@localhost:6379/0")
9+
LOGGING_LEVEL = logging.INFO

0 commit comments

Comments
 (0)