From a9d4dbee6d237d18ed9d3a77d81d6814e510e379 Mon Sep 17 00:00:00 2001 From: Rudainasaleh Date: Mon, 29 Apr 2024 12:09:23 +0400 Subject: [PATCH 1/5] genzsprfour-48: API for Package Measurement Conversion --- README.md | 34 +++++++++ __init__.py | 0 controller/__init__.py | 0 controller/converter_controller.py | 68 +++++++++++++++++ converter.db | Bin 0 -> 8192 bytes main_app.py | 22 ++++++ models/__init__.py | 0 models/package_converter.py | 117 +++++++++++++++++++++++++++++ models/sequence_history.py | 94 +++++++++++++++++++++++ models/test/__init__.py | 0 models/test/test_sequence.py | 71 +++++++++++++++++ requirements.txt | 3 + 12 files changed, 409 insertions(+) create mode 100644 README.md create mode 100644 __init__.py create mode 100644 controller/__init__.py create mode 100644 controller/converter_controller.py create mode 100644 converter.db create mode 100644 main_app.py create mode 100644 models/__init__.py create mode 100644 models/package_converter.py create mode 100644 models/sequence_history.py create mode 100644 models/test/__init__.py create mode 100644 models/test/test_sequence.py create mode 100644 requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..f84005d --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Package Converter API +## Overview +The Package Converter API is a web service that +provides functionality to convert package measurements +based on user input. It exposes endpoints for converting +measurements and retrieving conversion history. + +## Features +- **Sequence Input Handling:** Accepts a sequence of characters and underscores as input for conversion. +- **Efficient Conversion Algorithms:** Implements efficient algorithms for converting the input sequence into a list of measurements. +- **Clear and adaptable:** Easily modified and extended to include additional functionalities. + +## Installation +Clone the repository: +``` +git clone https://github.com/Rudainasaleh/PackageMeasurementConversionAPI.git +``` + +## Install dependencies: +``` +pip install -r requirements.txt +``` + +## Usage +- Start the server: +```python main_app.py``` +- ```GET /api/v1/convert_measurements?input=aa``` Convert package measurements based on user input +- ```GET /api/v1/get_history``` Retrieve conversion history. + +## Testing + +- To run unit tests: + +``` python -m unittest ./models/test/test_sequence.py``` \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controller/converter_controller.py b/controller/converter_controller.py new file mode 100644 index 0000000..6dc9eb5 --- /dev/null +++ b/controller/converter_controller.py @@ -0,0 +1,68 @@ +import cherrypy +from models.package_converter import PackageConverter +from models.sequence_history import SequenceHistory +from models.sequence_history import Sequence + + +class ConverterAPI: + def __init__(self): + self.converter = PackageConverter() + self.sequence_history = SequenceHistory('converter.db') + + @cherrypy.expose + @cherrypy.tools.json_out() + def convert_measurements(self, input=None): + """ + Function that exposes an API endpoint to convert the measurements given by user and handles the errors + :param input: sequence of characters from user + :return: a JSON response containing the status, response, measurements, and error + """ + try: + if input is None: + input_string = cherrypy.request.params.get("input", "") + + input_string = input + measurement = self.converter.package_measurement_conversion(input_string) + + if measurement != "Invalid": + # Assuming no error message or response for now + error_message = None + response = "Measurements saved successfully" + + sequence = Sequence(input_string, measurement) + self.sequence_history.save_curr_seq(sequence, error_message, response) + return {"status": "success", + "response": response, "measurements": measurement, + "error": error_message} + + else: + error_message = "Invalid input" + response = "cant convert measurement input string" + measurement = None + sequence = Sequence(input_string, measurement) + self.sequence_history.save_curr_seq(sequence, error_message, response) + return {"status": "error", + "response": response, "measurements": measurement, + "error": error_message} + + except Exception as e: + cherrypy.response.status = 500 + error_message = str(e) + response = 500 + self.sequence_history.save_curr_seq(sequence, error_message, response) + return {"status": "error", "data": None, "error": str(e)} + + @cherrypy.expose + @cherrypy.tools.json_out() + def get_history(self): + """ + Function that exposes an API endpoint to retrieve measurements history + :return: a JSON response containing the history data, or an error message + """ + try: + history = self.sequence_history.get_history() + return history + except Exception as e: + cherrypy.response.status = 500 # Internal Server Error + return {"status": "error", "data": None, "error": str(e)} + diff --git a/converter.db b/converter.db new file mode 100644 index 0000000000000000000000000000000000000000..22057f5c99672b10517beaeefec79194b6982351 GIT binary patch literal 8192 zcmeI$-AcnS6ae7UshvN#Dnq;y4~Y1~5Z&h_nqGq`%)l5J25ce%z>uynT|dIm zjsB00eh2-i7=k77JgR3IiAT_df&@r_1W14cNPq-LfCNZ@1W14ceigVsHsX`h)5gP% zV&`=(`IXOw%jIVywq3Myj*U)kYuColVVobvh^SRDzwg*Pb`cMX`Mq563=8&IDteu0 z`Klt7R}+`$*r!hPk_Kne_jrQ~C3{@LJ+J9i8ah*U$$MPmaoK7L*`5At9t*+Qd@%6< z)|>TWJy~~F)=DN`^#BD4kN^pg011!)36KB@kN^pg015oF02?&nb^;2W4y*j-DW%uS zdtXo1lC^mirSAq81Q&k2ezO=iVLS;1#!4%ZbIso8W(Q12PRyOSt}FC^@}kY^`aYo+ zTn)HPD0G{m%{_%*PX67m*u6N&^oJW{f7;+%PS;F|IfY?rQ++1P%mT1;x7w|hQaXj} kVOcIu#Y~u;0jPEYPNrFP+UboHu4YoWQXVnF{2;V=2Vk((Q2+n{ literal 0 HcmV?d00001 diff --git a/main_app.py b/main_app.py new file mode 100644 index 0000000..7052d1a --- /dev/null +++ b/main_app.py @@ -0,0 +1,22 @@ +import sys +import logging +import cherrypy +from controller.converter_controller import ConverterAPI + +if __name__ == '__main__': + + # Configure LOGGING to file + logging.basicConfig(filename='error_log.log', level=logging.CRITICAL, + format='%(asctime)s:%(levelname)s:%(message)s') + port = 8080 # Default port + + if len(sys.argv) > 1: + try: + port = int(sys.argv[1]) + except ValueError: + print("Invalid port number. Using the default port (8080).") + + cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': port}) + cherrypy.tree.mount(ConverterAPI(), '/') + cherrypy.engine.start() + cherrypy.engine.block() diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/package_converter.py b/models/package_converter.py new file mode 100644 index 0000000..1eb69b1 --- /dev/null +++ b/models/package_converter.py @@ -0,0 +1,117 @@ +import string +from abc import ABC, abstractmethod + + +class PackageConverter: + def __init__(self): + pass + + def search(self, lst): + """ + Function to count the number of the letter z + :param lst: a list containing numbers + :return: an integer indicating the occurrences of z + """ + count = 0 + for i in lst: + if i != 26: + return count + count += 1 + + def calc(self, lst): + """ + Function to return the sum of a given list + :param lst: a list containing integers + :return: an integer indicating the sum of a list + """ + return sum(lst) + + def is_empty(self, lst): + """ + Function to check if the list is empty + :param lst: a list of integers + :return: a boolean (True/False) + """ + return len(lst) < 1 + + def convert_char_to_conversion(self, char): + """ + Function to convert characters to numeric values + :param char: a single character either a letter or an underscore + :return: an integer value of the character + """ + if char == '_': + return 0 + else: + return string.ascii_lowercase.index(char.lower()) + 1 + + def process_list(self, lst): + """ + Function to process the list and add values of z (26) together. It also checks if z is the last character + :param lst: a list containing integers + :return: returns the processed list, or returns invalid in case z was the last character + """ + i = 0 + while i < len(lst): + if lst[i] == 26: + if i < len(lst) - 1: + new_num = self.search(lst[i:]) + lst[i + new_num] += lst[i] * new_num + + del lst[i:i + new_num] + i += new_num + i += 1 + else: + return "Invalid" + else: + i += 1 + return lst + + def package_measurement_conversion(self, input_string): + """ + Function to convert a sequence of characters to a list of the total measurements + :param input_string: a string inputted by the user + :return: a list of the measurements calculated from the input string + """ + package_list = [self.convert_char_to_conversion(char) for char in input_string] + list_to_measure = self.process_list(package_list) + measurements = [] + if list_to_measure == "Invalid": + measurements = "Invalid" + else: + while not self.is_empty(list_to_measure): + n = list_to_measure[0] + if n == 0: + measurements.append(0) + break + elif n >= len(list_to_measure) or self.is_empty(list_to_measure): + measurements = "Invalid" + break + else: + list_to_measure.pop(0) + measurements.append(self.calc(list_to_measure[:n])) + list_to_measure = list_to_measure[n:] + return measurements + + + +# # # Test the function +# converter = PackageConverter() +# converter.package_measurement_conversion("aa") # [1] +# converter.package_measurement_conversion("__") # [0] +# converter.package_measurement_conversion("a_") # [0] +# converter.package_measurement_conversion("abz") # invalid +# converter.package_measurement_conversion("abc") # invalid +# +# converter.package_measurement_conversion("baaca") # invalid +# converter.package_measurement_conversion("aaa") # invalid +# converter.package_measurement_conversion("bbb") # [4] +# converter.package_measurement_conversion("ccc") # invalid +# converter.package_measurement_conversion('abbcc') # [2,6] +# converter.package_measurement_conversion('abcdabcdab') # [2,7,7] +# converter.package_measurement_conversion('abcdabcdab_') # [[2, 7, 7, 0 ] +# converter.package_measurement_conversion('dz_a_aazzaaa') # [28, 53, 1] +# converter.package_measurement_conversion('zdaaaaaaaabaaaaaaaabaaaaaaaabbaa') # [34] +# converter.package_measurement_conversion('za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa') # [40,1] +# converter.package_measurement_conversion('zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_') # [26] +# # diff --git a/models/sequence_history.py b/models/sequence_history.py new file mode 100644 index 0000000..bbe5b5b --- /dev/null +++ b/models/sequence_history.py @@ -0,0 +1,94 @@ +import json +import sqlite3 + + +class Sequence: + def __init__(self, input_string, measurement): + self.input_string = input_string + self.measurement = measurement + + +class SequenceHistory: + def __init__(self, db_path): + self.db_path = db_path + self.create_table() + + def create_table(self): + """ + Function to create the SQL database + :return: the database connection object + """ + try: + connection = sqlite3.connect(self.db_path) + cursor = connection.cursor() + cursor.execute('''CREATE TABLE IF NOT EXISTS sequences ( + id INTEGER PRIMARY KEY, + input_string TEXT, + measurements TEXT, + error_message TEXT, + response TEXT + )''') + connection.commit() + except sqlite3.Error as e: + print(f"Error creating table: {e}") + finally: + if connection: + connection.close() + + def save_curr_seq(self, sequence: Sequence, error_message: str = None, response: str = None) -> bool: + """ + Function to insert the data coming from the requests to the SQL database + :param sequence: the sequence of characters from user + :param error_message: in case of errors, an error message will be returned + :param response: it states if the request is successful or not + :return: a boolean value indicating whether the sequence was saved or not + """ + try: + connection = sqlite3.connect(self.db_path) + cursor = connection.cursor() + cursor.execute( + "INSERT INTO sequences (input_string, measurements, error_message, response) VALUES (?, ?, ?, ?)", + (sequence.input_string, json.dumps(sequence.measurement), error_message, response)) + connection.commit() + + # Clear input_string and measurements lists + sequence.input_string = "" + sequence.measurement = [] + + return True + except sqlite3.Error as e: + print(f"Error saving sequence: {e}") + return False + finally: + if connection: + connection.close() + + def get_history(self) -> list: + """ + Function to return the stored history from the SQL database + :return: a list of history data including the string, measurements, error message, and response + """ + try: + connection = sqlite3.connect(self.db_path) + cursor = connection.cursor() + cursor.execute("SELECT input_string, measurements, error_message, response FROM sequences") + rows = cursor.fetchall() + + history = [] + for row in rows: + data = { + 'input_string': row[0], + 'measurements': json.loads(row[1]), + 'error_message': row[2], + 'response': row[3] + } + history.append(data) + + return history + except sqlite3.Error as e: + print(f"Error getting history: {e}") + return {"status": "error", "data": None, "error": str(e)} + finally: + if connection: + connection.close() + diff --git a/models/test/__init__.py b/models/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/test/test_sequence.py b/models/test/test_sequence.py new file mode 100644 index 0000000..462596f --- /dev/null +++ b/models/test/test_sequence.py @@ -0,0 +1,71 @@ +import unittest +from models.package_converter import PackageConverter + + +class TestPackageConverter(unittest.TestCase): + """ + Two functions containing multiple test cases for valid and invalid user inputs + """ + + def setUp(self): + self.converter = PackageConverter() + + def test_aa_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("aa"), [1]) + + def test_double_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("__"), [0]) + + def test_single_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("a_"), [0]) + + def test_abbcc_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('abbcc'), [2, 6]) + + def test_abcdabcdab_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('abcdabcdab'), [2, 7, 7]) + + def test_abcdabcdab_with_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('abcdabcdab_'), [2, 7, 7, 0]) + + def test_dz_a_aazzaaa_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('dz_a_aazzaaa'), [28, 53, 1]) + + def test_zdaaaaaaaabaaaaaaaabaaaaaaaabbaa_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('zdaaaaaaaabaaaaaaaabaaaaaaaabbaa'), [34]) + + def test_za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa'), [40, 1]) + + def test_zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion('zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_'), [26]) + + def test_single_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("_"), [0]) + + def test_underscore_ad_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("_ad"), [0]) + + def test_a_underscore_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("a_"), [0]) + + def test_underscore_zzzb_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("_zzzb"), [0]) + + def test_abz_invalid_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("abz"), "Invalid") + + def test_aaa_invalid_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("aaa"), "Invalid") + + def test_abc_invalid_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("abc"), "Invalid") + + def test_baaca_invalid_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("baaca"), "Invalid") + + def test_ccc_invalid_conversion(self): + self.assertEqual(self.converter.package_measurement_conversion("ccc"), "Invalid") + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cdd578d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +CherryPy==18.9.0 +requests==2.31.0 +setuptools==69.2.0 \ No newline at end of file From 84786e0a0100feb54d91aeb7e5e05864e9586ea9 Mon Sep 17 00:00:00 2001 From: Rudainasaleh Date: Mon, 29 Apr 2024 13:45:41 +0400 Subject: [PATCH 2/5] genzsprfour-48: API for Package Measurement Conversion --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f84005d..ffea65c 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ pip install -r requirements.txt ## Usage - Start the server: ```python main_app.py``` -- ```GET /api/v1/convert_measurements?input=aa``` Convert package measurements based on user input -- ```GET /api/v1/get_history``` Retrieve conversion history. +- ```GET http://localhost:8080/convert_measurements?input=aa``` Convert package measurements based on user input +- ```GET http://localhost:8080/get_history``` Retrieve conversion history. ## Testing From 5cdfa761e589d8e2dc43902db209f77e0e407a39 Mon Sep 17 00:00:00 2001 From: Rudainasaleh Date: Wed, 1 May 2024 11:49:09 +0400 Subject: [PATCH 3/5] add gitignore and update dir structure --- .gitignore | 2 + README.md | 2 +- controller/converter_controller.py | 31 +++++++------- main_app.py | 3 +- models/sequence.py | 9 ++++ models/test/__init__.py | 0 {models => services}/package_converter.py | 1 - .../test/test_package_conversion.py | 2 +- converter.db => utilis/converter.db | Bin 8192 -> 8192 bytes .../db_operator.py | 40 ++++++------------ utilis/error_log.log | 16 +++++++ 11 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 .gitignore create mode 100644 models/sequence.py delete mode 100644 models/test/__init__.py rename {models => services}/package_converter.py (99%) rename models/test/test_sequence.py => services/test/test_package_conversion.py (98%) rename converter.db => utilis/converter.db (89%) rename models/sequence_history.py => utilis/db_operator.py (66%) create mode 100644 utilis/error_log.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b29db5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index ffea65c..f1a02ea 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,4 @@ pip install -r requirements.txt - To run unit tests: -``` python -m unittest ./models/test/test_sequence.py``` \ No newline at end of file +``` python -m unittest ./services/test/test_package_conversion.py``` \ No newline at end of file diff --git a/controller/converter_controller.py b/controller/converter_controller.py index 6dc9eb5..6cc1bbc 100644 --- a/controller/converter_controller.py +++ b/controller/converter_controller.py @@ -1,13 +1,14 @@ import cherrypy -from models.package_converter import PackageConverter -from models.sequence_history import SequenceHistory -from models.sequence_history import Sequence +from datetime import datetime +from services.package_converter import PackageConverter +from utilis.db_operator import PackageMeasurementHistory +from models.sequence import Sequence class ConverterAPI: def __init__(self): self.converter = PackageConverter() - self.sequence_history = SequenceHistory('converter.db') + self.sequence_history = PackageMeasurementHistory('./utilis/converter.db') @cherrypy.expose @cherrypy.tools.json_out() @@ -23,33 +24,31 @@ def convert_measurements(self, input=None): input_string = input measurement = self.converter.package_measurement_conversion(input_string) - + time = datetime.now() if measurement != "Invalid": # Assuming no error message or response for now error_message = None response = "Measurements saved successfully" - sequence = Sequence(input_string, measurement) - self.sequence_history.save_curr_seq(sequence, error_message, response) - return {"status": "success", - "response": response, "measurements": measurement, - "error": error_message} + sequence = Sequence(input_string, measurement, time, response) + self.sequence_history.save_curr_seq(sequence) + return measurement else: error_message = "Invalid input" response = "cant convert measurement input string" measurement = None - sequence = Sequence(input_string, measurement) - self.sequence_history.save_curr_seq(sequence, error_message, response) - return {"status": "error", - "response": response, "measurements": measurement, - "error": error_message} + + sequence = Sequence(input_string, measurement, time, response) + self.sequence_history.save_curr_seq(sequence) + return "{}, {}".format(error_message, response) except Exception as e: cherrypy.response.status = 500 error_message = str(e) response = 500 - self.sequence_history.save_curr_seq(sequence, error_message, response) + sequence = Sequence(input_string, measurement=None, time=time, response=response) + self.sequence_history.save_curr_seq(sequence) return {"status": "error", "data": None, "error": str(e)} @cherrypy.expose diff --git a/main_app.py b/main_app.py index 7052d1a..81404a3 100644 --- a/main_app.py +++ b/main_app.py @@ -3,10 +3,11 @@ import cherrypy from controller.converter_controller import ConverterAPI + if __name__ == '__main__': # Configure LOGGING to file - logging.basicConfig(filename='error_log.log', level=logging.CRITICAL, + logging.basicConfig(filename='utilis/error_log.log', level=logging.CRITICAL, format='%(asctime)s:%(levelname)s:%(message)s') port = 8080 # Default port diff --git a/models/sequence.py b/models/sequence.py new file mode 100644 index 0000000..93ff16a --- /dev/null +++ b/models/sequence.py @@ -0,0 +1,9 @@ + + +class Sequence: + def __init__(self, input_string, measurement, time, response: str = None): + self.input_string = input_string + self.measurement = measurement + self.response = response + self.time = time + diff --git a/models/test/__init__.py b/models/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/models/package_converter.py b/services/package_converter.py similarity index 99% rename from models/package_converter.py rename to services/package_converter.py index 1eb69b1..a864232 100644 --- a/models/package_converter.py +++ b/services/package_converter.py @@ -1,5 +1,4 @@ import string -from abc import ABC, abstractmethod class PackageConverter: diff --git a/models/test/test_sequence.py b/services/test/test_package_conversion.py similarity index 98% rename from models/test/test_sequence.py rename to services/test/test_package_conversion.py index 462596f..21ef026 100644 --- a/models/test/test_sequence.py +++ b/services/test/test_package_conversion.py @@ -1,5 +1,5 @@ import unittest -from models.package_converter import PackageConverter +from services.package_converter import PackageConverter class TestPackageConverter(unittest.TestCase): diff --git a/converter.db b/utilis/converter.db similarity index 89% rename from converter.db rename to utilis/converter.db index 22057f5c99672b10517beaeefec79194b6982351..6b5f6444894a6041e6c51d66ffc0c97d84cb608c 100644 GIT binary patch delta 357 zcmZp0XmFSy&B!)U#+jduK`(D7F9QPuBi|YZzBQW_1?>2^ngSWw#pUH0TM8#{;(O0t zlv-SnpI1D2Ex!hTNoH=U0*ELsNz5(anjFY4Gxl`NpeXO{n3$BD0>RNnItu1M1k$L4q0z)t&%n^cz`)dnnT$_(`^ z4NNV~OKR*D7@8Ua E0HkeV$DkpE_d(WF% zRFq#7pPO1-oS2?ExsKmtav;C#Z{WA*7v_7;*RrwDnr~u* zAy*;?8$+!qgLhTcr^2DdhTzoOv$8P=i>ikwCnx8X z=Hz(hl_ln6rYL0Q6_l1FC+3wXBrcEpBSH*SXGsnm>6wjp`&1GtfOEU%gqS(0RvFbFcttqN@I@z diff --git a/models/sequence_history.py b/utilis/db_operator.py similarity index 66% rename from models/sequence_history.py rename to utilis/db_operator.py index bbe5b5b..aee2084 100644 --- a/models/sequence_history.py +++ b/utilis/db_operator.py @@ -1,14 +1,9 @@ import json import sqlite3 +from models.sequence import Sequence -class Sequence: - def __init__(self, input_string, measurement): - self.input_string = input_string - self.measurement = measurement - - -class SequenceHistory: +class PackageMeasurementHistory: def __init__(self, db_path): self.db_path = db_path self.create_table() @@ -25,8 +20,8 @@ def create_table(self): id INTEGER PRIMARY KEY, input_string TEXT, measurements TEXT, - error_message TEXT, - response TEXT + response TEXT, + time timestamp )''') connection.commit() except sqlite3.Error as e: @@ -35,25 +30,24 @@ def create_table(self): if connection: connection.close() - def save_curr_seq(self, sequence: Sequence, error_message: str = None, response: str = None) -> bool: + def save_curr_seq(self, sequence: Sequence) -> bool: """ Function to insert the data coming from the requests to the SQL database :param sequence: the sequence of characters from user - :param error_message: in case of errors, an error message will be returned - :param response: it states if the request is successful or not :return: a boolean value indicating whether the sequence was saved or not """ try: connection = sqlite3.connect(self.db_path) cursor = connection.cursor() cursor.execute( - "INSERT INTO sequences (input_string, measurements, error_message, response) VALUES (?, ?, ?, ?)", - (sequence.input_string, json.dumps(sequence.measurement), error_message, response)) + "INSERT INTO sequences (input_string, measurements, response, time) VALUES (?, ?, ?, ?)", + (sequence.input_string, json.dumps(sequence.measurement), + sequence.response, sequence.time)) connection.commit() # Clear input_string and measurements lists - sequence.input_string = "" - sequence.measurement = [] + Sequence.input_string = "" + Sequence.measurement = [] return True except sqlite3.Error as e: @@ -71,20 +65,10 @@ def get_history(self) -> list: try: connection = sqlite3.connect(self.db_path) cursor = connection.cursor() - cursor.execute("SELECT input_string, measurements, error_message, response FROM sequences") + cursor.execute("SELECT input_string, measurements, time FROM sequences") rows = cursor.fetchall() - history = [] - for row in rows: - data = { - 'input_string': row[0], - 'measurements': json.loads(row[1]), - 'error_message': row[2], - 'response': row[3] - } - history.append(data) - - return history + return rows except sqlite3.Error as e: print(f"Error getting history: {e}") return {"status": "error", "data": None, "error": str(e)} diff --git a/utilis/error_log.log b/utilis/error_log.log new file mode 100644 index 0000000..06bb1f6 --- /dev/null +++ b/utilis/error_log.log @@ -0,0 +1,16 @@ +2024-05-01 10:28:37,464:INFO:[01/May/2024:10:28:37] ENGINE Bus STARTING +2024-05-01 10:28:37,464:INFO:[01/May/2024:10:28:37] ENGINE Started monitor thread 'Autoreloader'. +2024-05-01 10:28:37,683:INFO:[01/May/2024:10:28:37] ENGINE Serving on http://0.0.0.0:8080 +2024-05-01 10:28:37,684:INFO:[01/May/2024:10:28:37] ENGINE Bus STARTED +2024-05-01 10:28:41,934:INFO:127.0.0.1 - - [01/May/2024:10:28:41] "GET /convert_measurements?input=aa HTTP/1.1" 200 3 "" "PostmanRuntime/7.37.3" +2024-05-01 10:28:45,019:INFO:127.0.0.1 - - [01/May/2024:10:28:45] "GET /convert_measurements?input=abcdabcdab HTTP/1.1" 200 9 "" "PostmanRuntime/7.37.3" +2024-05-01 10:28:58,069:INFO:127.0.0.1 - - [01/May/2024:10:28:58] "GET /convert_measurements?input=zdaaaaaaaabaaaaaaaabaaaaaaaabbaa HTTP/1.1" 200 4 "" "PostmanRuntime/7.37.3" +2024-05-01 10:29:00,885:INFO:127.0.0.1 - - [01/May/2024:10:29:00] "GET /get_history HTTP/1.1" 200 325 "" "PostmanRuntime/7.37.3" +2024-05-01 10:29:18,338:INFO:[01/May/2024:10:29:18] ENGINE Keyboard Interrupt: shutting down bus +2024-05-01 10:29:18,338:INFO:[01/May/2024:10:29:18] ENGINE Bus STOPPING +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE Stopped thread 'Autoreloader'. +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE Bus STOPPED +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE Bus EXITING +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE Bus EXITED +2024-05-01 10:29:18,537:INFO:[01/May/2024:10:29:18] ENGINE Waiting for child threads to terminate... From 1a30aa1a4a090d26bf2433474716c858e393ab4d Mon Sep 17 00:00:00 2001 From: Rudainasaleh Date: Thu, 2 May 2024 08:47:35 +0400 Subject: [PATCH 4/5] update return message --- controller/converter_controller.py | 18 ++++++++---------- utilis/converter.db | Bin 8192 -> 0 bytes utilis/error_log.log | 16 ---------------- 3 files changed, 8 insertions(+), 26 deletions(-) delete mode 100644 utilis/converter.db delete mode 100644 utilis/error_log.log diff --git a/controller/converter_controller.py b/controller/converter_controller.py index 6cc1bbc..a4e70a5 100644 --- a/controller/converter_controller.py +++ b/controller/converter_controller.py @@ -5,6 +5,7 @@ from utilis.db_operator import PackageMeasurementHistory from models.sequence import Sequence + class ConverterAPI: def __init__(self): self.converter = PackageConverter() @@ -27,21 +28,17 @@ def convert_measurements(self, input=None): time = datetime.now() if measurement != "Invalid": # Assuming no error message or response for now - error_message = None response = "Measurements saved successfully" - sequence = Sequence(input_string, measurement, time, response) self.sequence_history.save_curr_seq(sequence) - return measurement - + return {"status": "success", "err_msg": "", "result": measurement} else: - error_message = "Invalid input" + error_message = "invalid sequence" response = "cant convert measurement input string" - measurement = None - + measurement = [] sequence = Sequence(input_string, measurement, time, response) self.sequence_history.save_curr_seq(sequence) - return "{}, {}".format(error_message, response) + return {"status": "fail", "err_msg": error_message, "result": measurement} except Exception as e: cherrypy.response.status = 500 @@ -49,7 +46,7 @@ def convert_measurements(self, input=None): response = 500 sequence = Sequence(input_string, measurement=None, time=time, response=response) self.sequence_history.save_curr_seq(sequence) - return {"status": "error", "data": None, "error": str(e)} + return {"status": "error", "err_msg": error_message, "result": None} @cherrypy.expose @cherrypy.tools.json_out() @@ -63,5 +60,6 @@ def get_history(self): return history except Exception as e: cherrypy.response.status = 500 # Internal Server Error - return {"status": "error", "data": None, "error": str(e)} + return {"status": "error", "err_msg": str(e), "result": None} + diff --git a/utilis/converter.db b/utilis/converter.db deleted file mode 100644 index 6b5f6444894a6041e6c51d66ffc0c97d84cb608c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeI#K}*9h6ae6KZWRQlBI0rJrZ7v|HCr7hR##+f6IXjshDf$W8LV|Kt>DCq`cJ(2 z2mEDTP0eH@BR(&K^r z#^a<=kruqp%T-GD1<3{h0w4eaAOHd&00JNY0w4eaAOHf10uN1Pb~Tq%o~}dI?sLJf zBOY|Pm?Wuv*ReeZd-h)4!INpcK8F*c*TvOC&pB{heB@RewtI?e&S^e*UoeQma}f@E z!6o*b6EAs-&zXpZ+~+|k{Q%2!H?xfB*=900@8p2!H?xfB*=56X;}A)L5z6qb~c5_RmC4wF>502ik6mTP1e$ Date: Thu, 2 May 2024 10:16:02 +0400 Subject: [PATCH 5/5] edited new test cases --- services/package_converter.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/services/package_converter.py b/services/package_converter.py index a864232..aaaf3e6 100644 --- a/services/package_converter.py +++ b/services/package_converter.py @@ -15,6 +15,8 @@ def search(self, lst): for i in lst: if i != 26: return count + elif i == "invalid": + return "invalid" count += 1 def calc(self, lst): @@ -41,8 +43,10 @@ def convert_char_to_conversion(self, char): """ if char == '_': return 0 - else: + elif char.lower() in string.ascii_lowercase: return string.ascii_lowercase.index(char.lower()) + 1 + else: + return "invalid" def process_list(self, lst): """ @@ -52,6 +56,7 @@ def process_list(self, lst): """ i = 0 while i < len(lst): + if lst[i] == 26: if i < len(lst) - 1: new_num = self.search(lst[i:]) @@ -62,6 +67,8 @@ def process_list(self, lst): i += 1 else: return "Invalid" + elif lst[i] == "invalid": + return "Invalid" else: i += 1 return lst @@ -73,6 +80,7 @@ def package_measurement_conversion(self, input_string): :return: a list of the measurements calculated from the input string """ package_list = [self.convert_char_to_conversion(char) for char in input_string] + list_to_measure = self.process_list(package_list) measurements = [] if list_to_measure == "Invalid": @@ -95,8 +103,8 @@ def package_measurement_conversion(self, input_string): # # # Test the function -# converter = PackageConverter() -# converter.package_measurement_conversion("aa") # [1] +converter = PackageConverter() +print(converter.package_measurement_conversion("@@@")) # [1] # converter.package_measurement_conversion("__") # [0] # converter.package_measurement_conversion("a_") # [0] # converter.package_measurement_conversion("abz") # invalid