From e9143420f82d77afa845c9dce61f5720574f2efe Mon Sep 17 00:00:00 2001 From: John Rofrano Date: Wed, 17 Jun 2026 00:43:54 +0000 Subject: [PATCH 1/3] Added type hints and cleaned up returns --- service/models.py | 31 +++++++++++++++---------------- service/routes.py | 8 ++++---- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/service/models.py b/service/models.py index 909371f3..cca114b8 100644 --- a/service/models.py +++ b/service/models.py @@ -1,5 +1,5 @@ ###################################################################### -# Copyright 2016, 2023 John Rofrano. All Rights Reserved. +# Copyright 2016, 2026 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. @@ -13,10 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. ###################################################################### +# cSpell: flushall """ Counter Model """ + import logging +from typing import Optional from redis.exceptions import ConnectionError as RedisConnectionError from service import redis @@ -38,7 +41,7 @@ class Counter: This follows the same standards as SQLAlchemy URIs """ - def __init__(self, name: str = "hits", value: int = None): + def __init__(self, name: str = "hits", value: Optional[int] = None) -> None: """Constructor""" self.name = name if not value: @@ -47,49 +50,45 @@ def __init__(self, name: str = "hits", value: int = None): self.value = value @property - def value(self): + def value(self) -> int: """Returns the current value of the counter""" return int(redis.get(self.name)) @value.setter - def value(self, value): + def value(self, value: int) -> None: """Sets the value of the counter""" redis.set(self.name, value) @value.deleter - def value(self): + def value(self) -> None: """Removes the counter fom the database""" redis.delete(self.name) - def increment(self): + def increment(self) -> int: """Increments the current value of the counter by 1""" return redis.incr(self.name) - def serialize(self): + def serialize(self) -> dict[str, str | int]: """Converts a counter into a dictionary""" - return { - "name": self.name, - "counter": int(redis.get(self.name)) - } + return {"name": self.name, "counter": int(redis.get(self.name))} ###################################################################### # F I N D E R M E T H O D S ###################################################################### @classmethod - def all(cls): + def all(cls) -> list: """Returns all of the counters""" try: counters = [ - {"name": key, "counter": int(redis.get(key))} - for key in redis.keys("*") + {"name": key, "counter": int(redis.get(key))} for key in redis.keys("*") ] except Exception as err: raise DatabaseConnectionError(err) from err return counters @classmethod - def find(cls, name): + def find(cls, name) -> Optional["Counter"]: """Finds a counter with the name or returns None""" counter = None try: @@ -101,7 +100,7 @@ def find(cls, name): return counter @classmethod - def remove_all(cls): + def remove_all(cls) -> None: """Removes all of the keys in the database""" try: redis.flushall() diff --git a/service/routes.py b/service/routes.py index 2872e201..2e1f9943 100644 --- a/service/routes.py +++ b/service/routes.py @@ -1,5 +1,5 @@ ###################################################################### -# Copyright 2016, 2023 John J. Rofrano. All Rights Reserved. +# Copyright 2016, 2026 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. @@ -66,7 +66,7 @@ def list_counters(): except DatabaseConnectionError as err: abort(status.HTTP_503_SERVICE_UNAVAILABLE, err) - return jsonify(counters) + return counters ############################################################ @@ -86,7 +86,7 @@ def read_counters(name): abort(status.HTTP_404_NOT_FOUND, f"Counter {name} does not exist") app.logger.info("Returning: %d...", counter.value) - return jsonify(counter.serialize()) + return counter.serialize() ############################################################ @@ -107,7 +107,7 @@ def create_counters(name): location_url = url_for("read_counters", name=name, _external=True) return ( - jsonify(counter.serialize()), + counter.serialize(), status.HTTP_201_CREATED, {"Location": location_url}, ) From 4c1716c5011101ad59f49baa6c5801f678614880 Mon Sep 17 00:00:00 2001 From: John Rofrano Date: Wed, 17 Jun 2026 00:57:00 +0000 Subject: [PATCH 2/3] Fixed return type --- service/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/service/models.py b/service/models.py index cca114b8..03ec5889 100644 --- a/service/models.py +++ b/service/models.py @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. ###################################################################### -# cSpell: flushall +# cSpell: disable=flushall """ Counter Model """ import logging -from typing import Optional +from typing import Optional, Self from redis.exceptions import ConnectionError as RedisConnectionError from service import redis @@ -88,7 +88,7 @@ def all(cls) -> list: return counters @classmethod - def find(cls, name) -> Optional["Counter"]: + def find(cls, name) -> Optional[Self]: """Finds a counter with the name or returns None""" counter = None try: @@ -97,7 +97,7 @@ def find(cls, name) -> Optional["Counter"]: counter = Counter(name, count) except Exception as err: raise DatabaseConnectionError(err) from err - return counter + return counter # type: ignore @classmethod def remove_all(cls) -> None: From 161ceef90117bf6143242825103484cd0570d20a Mon Sep 17 00:00:00 2001 From: John Rofrano Date: Wed, 17 Jun 2026 01:19:00 +0000 Subject: [PATCH 3/3] added type hints and return codes --- service/routes.py | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/service/routes.py b/service/routes.py index 2e1f9943..e60cdbca 100644 --- a/service/routes.py +++ b/service/routes.py @@ -14,26 +14,25 @@ # limitations under the License. ###################################################################### -# pylint: disable=cyclic-import """ Module for Hit Counter Service Routes """ -import os -from flask import jsonify, abort, url_for +from os import getenv +from flask import jsonify, abort, url_for, Response from flask import current_app as app from service.common import status # HTTP Status Codes from service.models import Counter, DatabaseConnectionError -DEBUG = os.getenv("DEBUG", "False") == "True" -PORT = os.getenv("PORT", "8080") +DEBUG = getenv("DEBUG", "False") == "True" +PORT = getenv("PORT", "8080") ############################################################ # Health Endpoint ############################################################ @app.route("/health") -def health(): +def health() -> tuple[dict, int]: """Health Status""" return {"status": "OK"}, status.HTTP_200_OK @@ -42,14 +41,17 @@ def health(): # Index page ############################################################ @app.route("/") -def index(): +def index() -> tuple[Response, int]: """Root URL""" app.logger.info("Request for Base URL") - return jsonify( - status=status.HTTP_200_OK, - message="Hit Counter Service", - version="1.0.0", - url=url_for("list_counters", _external=True), + return ( + jsonify( + status=status.HTTP_200_OK, + message="Hit Counter Service", + version="1.0.0", + url=url_for("list_counters", _external=True), + ), + status.HTTP_200_OK, ) @@ -57,7 +59,7 @@ def index(): # List counters ############################################################ @app.route("/counters", methods=["GET"]) -def list_counters(): +def list_counters() -> tuple[list[Counter], int]: """List counters""" app.logger.info("Request to list all counters...") counters = [] @@ -66,14 +68,14 @@ def list_counters(): except DatabaseConnectionError as err: abort(status.HTTP_503_SERVICE_UNAVAILABLE, err) - return counters + return counters, status.HTTP_200_OK ############################################################ # Read counters ############################################################ @app.route("/counters/", methods=["GET"]) -def read_counters(name): +def read_counters(name: str) -> tuple[dict, int]: """Read a counter""" app.logger.info("Request to Read counter: %s...", name) @@ -86,20 +88,20 @@ def read_counters(name): abort(status.HTTP_404_NOT_FOUND, f"Counter {name} does not exist") app.logger.info("Returning: %d...", counter.value) - return counter.serialize() + return counter.serialize(), status.HTTP_200_OK ############################################################ # Create counter ############################################################ @app.route("/counters/", methods=["POST"]) -def create_counters(name): +def create_counters(name: str) -> tuple[dict, int, dict]: """Create a counter""" 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 + abort(status.HTTP_409_CONFLICT, f"Counter '{name}' already exists") counter = Counter(name) except DatabaseConnectionError as err: @@ -117,7 +119,7 @@ def create_counters(name): # Update counters ############################################################ @app.route("/counters/", methods=["PUT"]) -def update_counters(name): +def update_counters(name: str) -> tuple[Response, int]: """Update a counter""" app.logger.info("Request to Update counter...") try: @@ -129,14 +131,14 @@ def update_counters(name): except DatabaseConnectionError as err: abort(status.HTTP_503_SERVICE_UNAVAILABLE, err) - return jsonify(name=name, counter=count) + return jsonify(name=name, counter=count), status.HTTP_200_OK ############################################################ # Delete counters ############################################################ @app.route("/counters/", methods=["DELETE"]) -def delete_counters(name): +def delete_counters(name: str) -> tuple[dict, int]: """Delete a counter""" app.logger.info("Request to Delete counter...") try: @@ -146,4 +148,4 @@ def delete_counters(name): except DatabaseConnectionError as err: abort(status.HTTP_503_SERVICE_UNAVAILABLE, err) - return "", status.HTTP_204_NO_CONTENT + return {}, status.HTTP_204_NO_CONTENT