diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b09dd39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.log +*.db +**/__pycache__ diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..be5d152 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,50 @@ + +# Package Measurement Conversion + +## Overview +This application exposes a RESTful API endpoint that accepts a masurement input as string of characters, and returns a response of a list of total values of measured inflows. + +## Features +- **Package Measurement Conversion:** Accepts a sequence of characters and returns a result list of measured inflows. +- **Clear and adaptable:** Easily modified and extended to include additional functionalities. + +## Installation +- Clone the following url in your command prompt: https://github.com/aaziz9/PackageMeasurementConversionAPI.git + +### Prerequisites +- Python 3.x +- CherryPy + +### Setup +1. Clone the repository (see the Installation section above). +2. Install CherryPy package: + ```pip install CherryPy==18.9.0``` + +### Running the Program +- **Script**: + + Default: ```python main_app.py``` + + Specific port: ```python main_app.py ``` + +### Usage +- Call the API using the following URLs: + + "http://localhost:8080/convert?input_str= + + +- Get history of all conversions: + + Default: http://localhost:8080/history + +## Contributing +Contributions to this project are prohibited due to the course restrictions. + +## License +This project is licensed under the MIT License. + +## Contact +For any queries, please contact aaziz9 @ github + +## Acknowledgements +Project by Abdulaziz Ali Al Badi \ No newline at end of file diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controller/measurement_controller.py b/controller/measurement_controller.py new file mode 100644 index 0000000..dcdc930 --- /dev/null +++ b/controller/measurement_controller.py @@ -0,0 +1,35 @@ +import cherrypy +import sqlite3 +from services.measurement_service import convert_measurements # Ensure this module and function are correctly implemented. +import logging +from db_utils.db_crud_operations import DbCrudOpearations +from models.processed_result import ProcessResult +import ast + +#DATABASE_NAME = "measurements.db" + +class MeasurementAPI: + def __init__(self) -> None: + self.storing_history = DbCrudOpearations() + + @cherrypy.expose + @cherrypy.tools.json_out() + def convert(self, input_str=""): + if not input_str: + return {"status": "fail", "err_msg": "invalid sequence", "result": []} + result = convert_measurements(input_str) + # Create a new object and then pass it + # self.add_to_history(input_str, str(result)) + processed_result_obj=ProcessResult() + processed_result_obj.given_seq = input_str + processed_result_obj.generated_seq = str(result) + + self.storing_history.add_to_history(processed_result_obj) + return {"status": "success", "err_msg": "", "result": ast.literal_eval(processed_result_obj.generated_seq)} #To convert a string representation of a list into an actual list in Python, you can use the ast.literal_eval() function from the ast module. this is done for easier testing as tester wants it in a certain format + #return {"input": processed_result_obj.given_seq, "output": processed_result_obj.generated_seq} + + @cherrypy.expose + @cherrypy.tools.json_out() + def history(self): + return {"history": self.storing_history.get_history()} + \ No newline at end of file diff --git a/db_utils/__init__.py b/db_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/db_utils/db_crud_operations.py b/db_utils/db_crud_operations.py new file mode 100644 index 0000000..bbc1e8d --- /dev/null +++ b/db_utils/db_crud_operations.py @@ -0,0 +1,47 @@ +import sqlite3 +import logging +from services.measurement_service import convert_measurements # Ensure this module and function are correctly implemented. +from models.processed_result import ProcessResult + +import cherrypy + + +DATABASE_NAME = "measurements.db" + +class DbCrudOpearations: + + def __init__(self): + self.create_table() + + def connect(self): + return sqlite3.connect(DATABASE_NAME) + + def create_table(self): + + with self.connect() as conn: + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + input_str TEXT NOT NULL, + output_str TEXT NOT NULL + ); + ''') + + def add_to_history(self, processed_res_obj: ProcessResult): + try: + with self.connect() as conn: + conn.execute('INSERT INTO history (input_str, output_str) VALUES (?, ?)', (processed_res_obj.given_seq, processed_res_obj.generated_seq)) + except sqlite3.DatabaseError as e: + logging.error(f"Database error: {e}") + # Optionally, re-raise or handle the error differently + raise + + def get_history(self): + try: + with self.connect() as conn: + cursor = conn.execute('SELECT input_str, output_str FROM history') + return [{"input": row[0], "output": row[1]} for row in cursor.fetchall()] + except sqlite3.DatabaseError as e: + logging.error(f"Database error: {e}") + return [] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..da60cc6 --- /dev/null +++ b/main.py @@ -0,0 +1,11 @@ +import cherrypy +import logging + +from controller.measurement_controller import MeasurementAPI + + +if __name__ == '__main__': + logging.basicConfig(filename='error_log.log', level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s') + cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 8080}) + + cherrypy.quickstart(MeasurementAPI(), '/') diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/processed_result.py b/models/processed_result.py new file mode 100644 index 0000000..ce4e9dd --- /dev/null +++ b/models/processed_result.py @@ -0,0 +1,4 @@ +class ProcessResult: + def __init__(self) -> None: + self.given_seq: str = "" + self.generated_seq: list[int] = [] \ No newline at end of file diff --git a/services/__init__.py b/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/measurement_service.py b/services/measurement_service.py new file mode 100644 index 0000000..2287570 --- /dev/null +++ b/services/measurement_service.py @@ -0,0 +1,80 @@ +def convert_measurements(input_str): #function to convert input into ints + """ + + """ + def Char_To_Num(letter): + if letter!="_": #convert letters to ascsi except underscore + return(ord(letter)-96) + else: + return 0 + + input_str = input_str.lower() # convert any uppercase letter into lowercase for easier processing + mainList=list() #creating a list to store the values + + inputLength = len(input_str) + + if inputLength==1: + mainList.append(Char_To_Num(input_str[0])) #if there is only one value in the list return the value of it + return mainList + + elif inputLength<=0: + return "Invalid input" #if there is no input return invalid input + + else: #this is where the main logic is processed + counter=0 #i + while(True): + if counter>=inputLength: + break + else: + steps=0 #resets the cycle to 0 + while(input_str[counter]=='z'): #if the first letter is Z use it to to count the number of steps, we start with the Z special cases , first while loop is for the cycling + steps+=Char_To_Num(input_str[counter]) + counter+=1 + steps += Char_To_Num(input_str[counter]) + + if steps==0: + mainList.append(0) #underscore case + break + + indexSum=0 + + + for d in range(steps): #counts the number of steps to count to process them and adding the values + counter+=1 + if counter>=inputLength: + break + + while(input_str[counter]=='z'): #2nd Z case, if there is multiple Z's in the same cycle + indexSum+=Char_To_Num(input_str[counter]) + counter+=1 + + indexSum+=Char_To_Num(input_str[counter]) + + counter+=1 + mainList.append(indexSum) + + return mainList + + +if __name__ == "__main__": + print(convert_measurements("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa")) + print(convert_measurements("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_")) + print(convert_measurements("dz_a_a")) + print(convert_measurements("azza")) + print(convert_measurements("dz_a_aazzaaa")) + print(convert_measurements("abbcc")) + print(convert_measurements("a")) + print(convert_measurements("aa")) + print(convert_measurements("a_")) + print(convert_measurements("abcdabcdab")) + print(convert_measurements("abcdabcdab_asas")) + print(convert_measurements("_")) + print(convert_measurements("_ad")) + print(convert_measurements("a_")) + print(convert_measurements("_______")) + print(convert_measurements("aaa")) + + + +# if z inside a cycle dont count step only value +# if z is the first letter that indicates a cycle add the number next to it to the number of steps \ No newline at end of file diff --git a/services/test/__init__.py b/services/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/test/test_measurement_service.py b/services/test/test_measurement_service.py new file mode 100644 index 0000000..a510441 --- /dev/null +++ b/services/test/test_measurement_service.py @@ -0,0 +1,39 @@ +import unittest +from services.measurement_service import convert_measurements + +class TestConvertMeasurements(unittest.TestCase): + """ + A group of unit tests for the 'convert_measurements' function. + """ + + def test_string_with_double_letters(self): + # Directly use the function with the appropriate input. + result = convert_measurements('aa') + self.assertEqual(result, [1]) # Adjust expected result as necessary based on actual function logic. + + def test_string_multiple_letters(self): + result = convert_measurements('abbcc') + self.assertEqual(result, [2, 6]) # Assuming this is what convert_measurements should return. + + def test_string_with_underscore(self): + result = convert_measurements('dz_a_aazzaaa') + self.assertEqual(result, [28, 53, 1]) # Adjust based on actual logic. + + def test_string_with_one_letter_underscore(self): + result = convert_measurements('a_') + self.assertEqual(result, [0]) + + def test_valid_string1(self): + result = convert_measurements('abcdabcdab') + self.assertEqual(result, [2, 7, 7]) + + def test_string_ending_with_underscore(self): + result = convert_measurements('abcdabcdab_') + self.assertEqual(result, [2, 7, 7, 0]) + + def test_string_starting_with_z(self): + result = convert_measurements('zdaaaaaaaabaaaaaaaabaaaaaaaabbaa') + self.assertEqual(result, [34]) + +if __name__ == "__main__": + unittest.main(verbosity=2)