diff --git a/service/error_handlers.py b/service/error_handlers.py index b6ae913..8f05821 100644 --- a/service/error_handlers.py +++ b/service/error_handlers.py @@ -5,9 +5,10 @@ # Error Handlers ###################################################################### + @app.errorhandler(status.HTTP_404_NOT_FOUND) def not_found(error): - """ Handles resources not found with 404_NOT_FOUND """ + """Handles resources not found with 404_NOT_FOUND""" message = str(error) app.logger.warning(message) return ( @@ -18,7 +19,7 @@ def not_found(error): @app.errorhandler(status.HTTP_405_METHOD_NOT_ALLOWED) def method_not_supported(error): - """ Handles unsupported HTTP methods with 405_METHOD_NOT_SUPPORTED """ + """Handles unsupported HTTP methods with 405_METHOD_NOT_SUPPORTED""" message = str(error) app.logger.warning(message) return ( @@ -33,7 +34,7 @@ def method_not_supported(error): @app.errorhandler(status.HTTP_500_INTERNAL_SERVER_ERROR) def internal_server_error(error): - """ Handles unexpected server error with 500_SERVER_ERROR """ + """Handles unexpected server error with 500_SERVER_ERROR""" message = str(error) app.logger.error(message) return ( @@ -45,9 +46,10 @@ def internal_server_error(error): status.HTTP_500_INTERNAL_SERVER_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 ( diff --git a/service/models.py b/service/models.py index b30710f..be6e600 100644 --- a/service/models.py +++ b/service/models.py @@ -17,7 +17,6 @@ Counter Model """ import os -import re import logging from redis import Redis from redis.exceptions import ConnectionError @@ -44,8 +43,8 @@ class Counter(object): redis = None - def __init__(self, name: str="hits", value: int=None): - """ Constructor """ + def __init__(self, name: str = "hits", value: int = None): + """Constructor""" self.name = name if not value: self.value = 0 @@ -54,21 +53,21 @@ def __init__(self, name: str="hits", value: int=None): @property def value(self): - """ Returns the current value of the counter """ + """Returns the current value of the counter""" return int(Counter.redis.get(self.name)) @value.setter def value(self, value): - """ Sets the value of the counter """ + """Sets the value of the counter""" Counter.redis.set(self.name, value) @value.deleter def value(self): - """ Removes the counter fom the database """ + """Removes the counter fom the database""" Counter.redis.delete(self.name) def increment(self): - """ Increments the current value of the counter by 1 """ + """Increments the current value of the counter by 1""" return Counter.redis.incr(self.name) def serialize(self): @@ -80,16 +79,19 @@ def serialize(self): @classmethod def all(cls): - """ Returns all of the counters """ + """Returns all of the counters""" try: - counters = [dict(name=key, counter=int(cls.redis.get(key))) for key in cls.redis.keys('*')] + counters = [ + dict(name=key, counter=int(cls.redis.get(key))) + for key in cls.redis.keys("*") + ] except Exception as err: raise DatabaseConnectionError(err) return counters @classmethod def find(cls, name): - """ Finds a counter with the name or returns None """ + """Finds a counter with the name or returns None""" counter = None try: count = cls.redis.get(name) @@ -112,7 +114,7 @@ def remove_all(cls): @classmethod def test_connection(cls): - """ Test connection by pinging the host """ + """Test connection by pinging the host""" success = False try: cls.redis.ping() diff --git a/service/routes.py b/service/routes.py index 49eee3c..96db59b 100644 --- a/service/routes.py +++ b/service/routes.py @@ -15,7 +15,7 @@ Redis Counter Demo in Docker """ import os -from flask import jsonify, json, abort, request, url_for +from flask import jsonify, abort, url_for from . import app, status # HTTP Status Codes from service import DATABASE_URI from .models import Counter, DatabaseConnectionError @@ -23,12 +23,13 @@ DEBUG = os.getenv("DEBUG", "False") == "True" PORT = os.getenv("PORT", "8080") + ############################################################ # Health Endpoint ############################################################ @app.route("/health") def health(): - """ Health Status """ + """Health Status""" return jsonify(dict(status="OK")), status.HTTP_200_OK @@ -37,9 +38,10 @@ def health(): ############################################################ @app.route("/") def index(): - """ Home Page """ + """Home Page""" return app.send_static_file("index.html") + ############################################################ # List counters ############################################################ @@ -88,8 +90,12 @@ def create_counters(name): except DatabaseConnectionError as err: abort(status.HTTP_503_SERVICE_UNAVAILABLE, err) - location_url = url_for('read_counters', name=name, _external=True) - return jsonify(counter.serialize()), status.HTTP_201_CREATED, {'Location': location_url} + location_url = url_for("read_counters", name=name, _external=True) + return ( + jsonify(counter.serialize()), + status.HTTP_201_CREATED, + {"Location": location_url}, + ) ############################################################ @@ -101,7 +107,10 @@ def update_counters(name): try: counter = Counter.find(name) if counter is None: - return jsonify(code=404, error="Counter {} does not exist".format(name)), 404 + return ( + jsonify(code=404, error="Counter {} does not exist".format(name)), + 404, + ) count = counter.increment() except DatabaseConnectionError as err: @@ -130,6 +139,7 @@ def delete_counters(name): # U T I L I T Y F U N C I O N S ############################################################ + @app.before_first_request def init_db(): try: diff --git a/tests/test_models.py b/tests/test_models.py index cd962ce..80fc34a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -31,25 +31,26 @@ logging.disable(logging.CRITICAL) + ###################################################################### # T E S T C A S E S ###################################################################### class CounterTests(TestCase): - """ Counter Model Tests """ + """Counter Model Tests""" @classmethod def setUpClass(cls): - """ Run before all tests """ + """Run before all tests""" # Counter.connect(DATABASE_URI) def setUp(self): - """ This runs before each test """ + """This runs before each test""" Counter.connect(DATABASE_URI) Counter.remove_all() self.counter = Counter() def tearDown(self): - """ This runs after each test """ + """This runs after each test""" # Counter.redis.flushall() pass @@ -58,51 +59,51 @@ def tearDown(self): ###################################################################### def test_create_counter_with_name(self): - """ Create a counter with a name """ + """Create a counter with a name""" counter = Counter("foo") self.assertIsNotNone(counter) self.assertEqual(counter.name, "foo") self.assertEqual(counter.value, 0) def test_create_counter_no_name(self): - """ Create a counter without a name """ + """Create a counter without a name""" self.assertIsNotNone(self.counter) self.assertEqual(self.counter.name, "hits") self.assertEqual(self.counter.value, 0) def test_serialize_counter(self): - """ Serialize a counter """ + """Serialize a counter""" self.assertIsNotNone(self.counter) data = self.counter.serialize() self.assertEqual(data["name"], "hits") self.assertEqual(data["counter"], 0) def test_set_list_counters(self): - """ List all of the counter """ + """List all of the counter""" _ = Counter("foo") _ = Counter("bar") counters = Counter.all() self.assertEqual(len(counters), 3) def test_set_find_counter(self): - """ Find a counter """ + """Find a counter""" _ = Counter("foo") _ = Counter("bar") foo = Counter.find("foo") self.assertEqual(foo.name, "foo") def test_counter_not_found(self): - """ counter not found """ + """counter not found""" foo = Counter.find("foo") self.assertIsNone(foo) def test_set_get_counter(self): - """ Set and then Get the counter """ + """Set and then Get the counter""" self.counter.value = 13 self.assertEqual(self.counter.value, 13) def test_delete_counter(self): - """ Delete a counter """ + """Delete a counter""" counter = Counter("foo") self.assertEqual(counter.value, 0) del counter.value @@ -112,7 +113,7 @@ def test_delete_counter(self): self.assertEqual(self.counter.value, 0) def test_increment_counter(self): - """ Increment the current value of the counter by 1 """ + """Increment the current value of the counter by 1""" count = self.counter.value next_count = self.counter.increment() logging.debug( @@ -125,21 +126,21 @@ def test_increment_counter(self): self.assertEqual(next_count, count + 1) def test_increment_counter_to_2(self): - """ Increment the counter to 2 """ + """Increment the counter to 2""" self.assertEqual(self.counter.value, 0) self.counter.increment() self.assertEqual(self.counter.value, 1) - counter = Counter.find("hits") + self.counter = Counter.find("hits") self.counter.increment() self.assertEqual(self.counter.value, 2) @patch("redis.Redis.ping") def test_no_connection(self, ping_mock): - """ Handle failed connection """ + """Handle failed connection""" ping_mock.side_effect = ConnectionError() self.assertRaises(DatabaseConnectionError, self.counter.connect, DATABASE_URI) @patch.dict(os.environ, {"DATABASE_URI": ""}) def test_missing_environment_creds(self): - """ Missing environment credentials """ + """Missing environment credentials""" self.assertRaises(DatabaseConnectionError, self.counter.connect) diff --git a/tests/test_routes.py b/tests/test_routes.py index d4398bd..7487ee7 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -24,38 +24,39 @@ import logging from unittest import TestCase from unittest.mock import patch -from service import app, DATABASE_URI +from service import app from service.models import Counter, DatabaseConnectionError DATABASE_URI = os.getenv("DATABASE_URI", "redis://:@localhost:6379/0") # logging.disable(logging.CRITICAL) + ###################################################################### # T E S T C A S E S ###################################################################### class ServiceTest(TestCase): - """ REST API Server Tests """ + """REST API Server Tests""" @classmethod def setUpClass(cls): - """ This runs once before the entire test suite """ + """This runs once before the entire test suite""" app.testing = True app.debug = False @classmethod def tearDownClass(cls): - """ This runs once after the entire test suite """ + """This runs once after the entire test suite""" pass def setUp(self): - """ This runs before each test """ + """This runs before each test""" Counter.connect(DATABASE_URI) Counter.remove_all() self.app = app.test_client() def tearDown(self): - """ This runs after each test """ + """This runs after each test""" pass ###################################################################### @@ -63,33 +64,33 @@ def tearDown(self): ###################################################################### def test_index(self): - """ Get the home page """ + """Get the home page""" resp = self.app.get("/") self.assertEquals(resp.status_code, 200) def test_health(self): - """ Get the health endpoint """ + """Get the health endpoint""" resp = self.app.get("/health") self.assertEquals(resp.status_code, 200) data = resp.get_json() self.assertEqual(data["status"], "OK") def test_create_counter(self): - """ Create a counter """ + """Create a counter""" resp = self.app.post("/counters/foo") self.assertEquals(resp.status_code, 201) data = resp.get_json() self.assertEqual(data["counter"], 0) def test_counter_already_exists(self): - """ Counter already exists """ + """Counter already exists""" resp = self.app.post("/counters/foo") self.assertEquals(resp.status_code, 201) resp = self.app.post("/counters/foo") self.assertEquals(resp.status_code, 409) def test_list_counters(self): - """ Get the counter """ + """Get the counter""" resp = self.app.post("/counters/foo") self.assertEquals(resp.status_code, 201) resp = self.app.post("/counters/bar") @@ -100,7 +101,7 @@ def test_list_counters(self): self.assertEqual(len(data), 2) def test_get_counter(self): - """ Get the counter """ + """Get the counter""" self.test_create_counter() resp = self.app.get("/counters/foo") self.assertEquals(resp.status_code, 200) @@ -108,17 +109,17 @@ def test_get_counter(self): self.assertEqual(data["counter"], 0) def test_get_counter_not_found(self): - """ Test counter not found """ + """Test counter not found""" resp = self.app.get("/counters/foo") self.assertEquals(resp.status_code, 404) def test_put_counter_not_found(self): - """ Test counter not found """ + """Test counter not found""" resp = self.app.put("/counters/foo") self.assertEquals(resp.status_code, 404) def test_increment_counter(self): - """ Increment the counter """ + """Increment the counter""" self.test_get_counter() resp = self.app.put("/counters/foo") self.assertEquals(resp.status_code, 200) @@ -132,32 +133,31 @@ def test_increment_counter(self): self.assertEqual(data["counter"], 2) def test_delete_counter(self): - """ Delete the counter """ + """Delete the counter""" self.test_create_counter() resp = self.app.delete("/counters/foo") self.assertEquals(resp.status_code, 204) def test_method_not_allowed(self): - """ Test Method Not Allowed """ + """Test Method Not Allowed""" resp = self.app.post("/counters") self.assertEquals(resp.status_code, 405) - ###################################################################### # T E S T E R R O R H A N D L E R S ###################################################################### @patch("service.routes.Counter.redis.get") def test_failed_get_request(self, redis_mock): - """ Error handlers for failed GET """ + """Error handlers for failed GET""" redis_mock.return_value = 0 redis_mock.side_effect = DatabaseConnectionError() resp = self.app.get("/counters/foo") self.assertEqual(resp.status_code, 503) - + @patch("service.models.Counter.increment") def test_failed_update_request(self, value_mock): - """ Error handlers for failed UPDATE """ + """Error handlers for failed UPDATE""" value_mock.return_value = 0 value_mock.side_effect = DatabaseConnectionError() self.test_create_counter() @@ -166,7 +166,7 @@ def test_failed_update_request(self, value_mock): @patch("service.models.Counter.__init__") def test_failed_post_request(self, value_mock): - """ Error handlers for failed POST """ + """Error handlers for failed POST""" value_mock.return_value = 0 value_mock.side_effect = DatabaseConnectionError() resp = self.app.post("/counters/foo") @@ -174,14 +174,14 @@ def test_failed_post_request(self, value_mock): @patch("service.routes.Counter.redis.keys") def test_failed_list_request(self, redis_mock): - """ Error handlers for failed LIST """ + """Error handlers for failed LIST""" redis_mock.return_value = 0 redis_mock.side_effect = Exception() resp = self.app.get("/counters") self.assertEqual(resp.status_code, 503) def test_failed_delete_request(self): - """ Error handlers for failed DELETE """ + """Error handlers for failed DELETE""" self.test_create_counter() with patch("service.routes.Counter.redis.get") as redis_mock: redis_mock.return_value = 0