From 87ae00f5b4600e5467dcfcef0979ad14a93e263e Mon Sep 17 00:00:00 2001 From: Haitham Yasse Hamood Al Maamari <71521@omantel.om> Date: Wed, 1 May 2024 14:17:31 +0400 Subject: [PATCH 1/5] Initial commit --- README.md | 54 +++++++++++++++++++ attempt1/Main.py | 43 ++++++++++++++++ attempt1/a | 0 attempt1/sequence.py | 94 ++++++++++++++++++++++++++++++++++ attempt1/sequencecontroller.py | 11 ++++ attempt1/sequencehistory.py | 41 +++++++++++++++ attempt1/sequenceservice.py | 62 ++++++++++++++++++++++ attempt1/test_sequence.py | 58 +++++++++++++++++++++ converter.py | 47 +++++++++++++++++ database.py | 51 ++++++++++++++++++ main_app.py | 29 +++++++++++ measurement.py | 57 +++++++++++++++++++++ unittest.py | 58 +++++++++++++++++++++ 13 files changed, 605 insertions(+) create mode 100644 README.md create mode 100644 attempt1/Main.py create mode 100644 attempt1/a create mode 100644 attempt1/sequence.py create mode 100644 attempt1/sequencecontroller.py create mode 100644 attempt1/sequencehistory.py create mode 100644 attempt1/sequenceservice.py create mode 100644 attempt1/test_sequence.py create mode 100644 converter.py create mode 100644 database.py create mode 100644 main_app.py create mode 100644 measurement.py create mode 100644 unittest.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd94696 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Package Measurement Conversion API + +This API is designed to convert measurement input strings into a list of total values of measured inflows for each package. The input strings follow a specific encoding format, where each package consists of a number indicating the count of values measured in each measurement cycle, followed by the measured values encoded using alphabetical characters. + +## Pre-requisites + +- Python 3.x +- CherryPy +- SQLite + +## Installation + +### 1. Clone the repository: + + https://github.com/HaithamAlMaamari/PackageMeasurementConversionAPI.git + + +### 2. Navigate to the project directory: + + cd Package_Measurement_Conversion_API + +### 3. Install the required dependencies: + + pip install python + pip install pycharm + pip install sqlite3 + +## Running the Application + +### 1. Start the server application: + + python main_app.py + +### 2. The server will start, and you will see a status message. + +## API Usage +### 1. To view the converted alphabet input string into the corresponding list of numbers in JSON format. + + + +### Example requests: + + http://localhost:8888/convert_measurements/abbcc + + http://localhost:8888/convert_measurements/aa + +### To retrieve the stored request history, use the following endpoint: + + GET /get_data_from_db + +This endpoint will return the persisted history of all requests made to the conversion endpoint. + +## Contributing +Contributing to this project is prohibited due to Course Restrictions. \ No newline at end of file diff --git a/attempt1/Main.py b/attempt1/Main.py new file mode 100644 index 0000000..a8a11f3 --- /dev/null +++ b/attempt1/Main.py @@ -0,0 +1,43 @@ +import cherrypy +import logging +from sequenceservice import SequenceService + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + +class PackageMeasurementAPI: + def __init__(self): + self.service = SequenceService() + + @cherrypy.expose + @cherrypy.tools.json_out() + def convert_measurements(self, input_sequence=None): + if input_sequence is None: + logging.info("No input provided, fetching history.") + try: + history = self.service.get_history() + return {"history": history} + except Exception as e: + logging.error("Error fetching history", exc_info=True) + cherrypy.response.status = 500 + return {"error": str(e)} + else: + logging.info(f"Received input for conversion: {input_sequence}") + try: + result = self.service.process_sequence(input_sequence) + logging.info(f"Conversion result for '{input_sequence}': {result}") + return {"input": input_sequence, "result": result} + except Exception as e: + logging.error("Error processing conversion", exc_info=True) + cherrypy.response.status = 400 + return {"error": str(e)} + + +if __name__ == '__main__': + cherrypy.config.update({ + 'server.socket_host': '0.0.0.0', + 'server.socket_port': 8080 + }) + logging.info("Starting web server...") + cherrypy.quickstart(PackageMeasurementAPI(), '/') diff --git a/attempt1/a b/attempt1/a new file mode 100644 index 0000000..e69de29 diff --git a/attempt1/sequence.py b/attempt1/sequence.py new file mode 100644 index 0000000..66304ba --- /dev/null +++ b/attempt1/sequence.py @@ -0,0 +1,94 @@ +import logging + + +class Sequence: + def __init__(self): + self.sequence = "" + logging.debug("Sequence initialized as empty.") + + def set_sequence(self, sequence): + self.sequence = sequence + logging.debug(f"Sequence set to: {sequence}") + + def encoder(self, character): + encoder = { + "_": 0, "a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7, "h": 8, + "i": 9, "j": 10, "k": 11, "l": 12, "m": 13, "n": 14, "o": 15, "p": 16, "q": 17, + "r": 18, "s": 19, "t": 20, "u": 21, "v": 22, "w": 23, "x": 24, "y": 25, "z": 26 + } + return encoder[character] + + def is_valid(self): + sequence = self.sequence + + steps = 0 + index = 0 + is_counting = False + while index < len(sequence): + if is_counting: # IF in the middle of cycle + steps = steps - 1 + if sequence[index] == 'z': + steps = steps + 1 + if steps == 0: + is_counting = False + else: # IF at the start of cycle + steps = self.encoder(sequence[index]) + if sequence[index] == 'z': + steps = steps + self.encoder(sequence[index + 1]) + if sequence[index + 1] == 'z': + steps = steps + self.encoder(sequence[index + 2]) + index += 1 + index += 1 + is_counting = True + + index += 1 + + if steps == 0: + return True + else: + return False + + def get_sequence_as_str(self): + logging.debug(f"Returning sequence: {self.sequence}") + return self.sequence + + def parse_sequence(self): + logging.info(f"Parsing sequence: {self.sequence}") + + # Mapping from characters to their respective values + value_map = {chr(i): i - 96 for i in range(ord('a'), ord('z') + 1)} + value_map['_'] = 0 + + result = [] + current_sum = 0 + current_count = 0 + + i = 0 + while i < len(self.sequence): + if self.sequence[i] == 'z' and i + 1 < len(self.sequence) and self.sequence[i + 1].isalpha(): + # Special handling for 'z' followed by another character + current_sum += value_map['z'] + i += 1 + while i < len(self.sequence) and self.sequence[i] == 'z': + current_sum += value_map['z'] + i += 1 + if i < len(self.sequence) and self.sequence[i] != '_': + current_sum += value_map[self.sequence[i]] + result.append(current_sum) + current_sum = 0 + i += 1 + elif self.sequence[i].isalpha(): + current_count = value_map[self.sequence[i]] + current_sum = 0 + i += 1 + while current_count > 0 and i < len(self.sequence) and self.sequence[i] != '_': + current_sum += value_map[self.sequence[i]] + current_count -= 1 + i += 1 + result.append(current_sum) + else: + result.append(0) + i += 1 + + logging.info(f"Sequence parsed. Result: {result}") + return result diff --git a/attempt1/sequencecontroller.py b/attempt1/sequencecontroller.py new file mode 100644 index 0000000..118e8da --- /dev/null +++ b/attempt1/sequencecontroller.py @@ -0,0 +1,11 @@ +import cherrypy +from sequenceservice import SequenceService + + +class SequenceController: + @cherrypy.expose + @cherrypy.tools.json_out() + def index(self, input_str=''): + service = SequenceService() + result = service.process_sequence(input_str) + return result diff --git a/attempt1/sequencehistory.py b/attempt1/sequencehistory.py new file mode 100644 index 0000000..b45bde7 --- /dev/null +++ b/attempt1/sequencehistory.py @@ -0,0 +1,41 @@ +import sqlite3 +import threading + +class SequenceHistory: + def __init__(self, db_path='sequence_history.db'): + self.db_path = db_path + self.local_storage = threading.local() + + def get_connection(self): + if not hasattr(self.local_storage, 'conn'): + self.local_storage.conn = sqlite3.connect(self.db_path, check_same_thread=False) + self.create_table() + return self.local_storage.conn + + def create_table(self): + conn = self.get_connection() + query = ''' + CREATE TABLE IF NOT EXISTS history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sequence TEXT NOT NULL, + result TEXT NOT NULL + ); + ''' + conn.execute(query) + conn.commit() + + def add_entry(self, sequence, result): + conn = self.get_connection() + query = 'INSERT INTO history (sequence, result) VALUES (?, ?)' + conn.execute(query, (sequence, str(result))) + conn.commit() + + def get_history(self): + conn = self.get_connection() + cursor = conn.cursor() + cursor.execute('SELECT sequence, result FROM history') + return cursor.fetchall() + + def __del__(self): + if hasattr(self.local_storage, 'conn'): + self.local_storage.conn.close() diff --git a/attempt1/sequenceservice.py b/attempt1/sequenceservice.py new file mode 100644 index 0000000..7baba75 --- /dev/null +++ b/attempt1/sequenceservice.py @@ -0,0 +1,62 @@ +from sequence import Sequence +from sequencehistory import SequenceHistory + + +class SequenceService: + def __init__(self): + self.sequence_manager = Sequence() + self.sequence_history = SequenceHistory() + self.result = [] + + def get_sequence(self, str_representation): + self.sequence_manager.set_sequence(str_representation) + if not self.sequence_manager.is_valid(): + raise Exception("INVALID SEQUENCE") + + def append_num_to_list(self, number): + self.result.append(number) + return self.result + + def process_sequence(self): + sequence = self.sequence_manager.get_sequence_as_str() + self.result = [] + index = 0 + is_counting = False + sum = 0 + steps = 0 + + while index < len(sequence): + current_char = sequence[index] + current_value = self.sequence_manager.encoder(current_char) + if current_value == 26: # 'z' + sum = 26 + index += 1 + while index < len(sequence) and self.sequence_manager.encoder(sequence[index]) == 26: + sum += 26 + index += 1 + if index < len(sequence) and self.sequence_manager.encoder(sequence[index]) != 0: + sum += self.sequence_manager.encoder(sequence[index]) + index += 1 + + count = sum + sum = 0 + while count > 0 and index < len(sequence): + sum += self.sequence_manager.encoder(sequence[index]) + count -= 1 + index += 1 + self.append_num_to_list(sum) + elif 1 <= current_value <= 25 or current_value == 0: + count = current_value if current_value > 0 else 1 + sum = 0 + index += 1 + while count > 0 and index < len(sequence): + sum += self.sequence_manager.encoder(sequence[index]) + count -= 1 + index += 1 + self.append_num_to_list(sum) + index += 1 + + # Log the sequence and result to history + self.sequence_history.add_entry(sequence, self.result) + + return self.result diff --git a/attempt1/test_sequence.py b/attempt1/test_sequence.py new file mode 100644 index 0000000..5bec93c --- /dev/null +++ b/attempt1/test_sequence.py @@ -0,0 +1,58 @@ +import unittest +from + +class TestMeasurementService(unittest.TestCase): + def setUp(self): + """Set up test environment before each test method.""" + self.service = MeasurementService() + + def tearDown(self): + """Clean up test environment after each test method.""" + pass + + def test_conversion(self): + """Test conversion functionality.""" + test_cases = [ + ("aa", [1]), + ("abbcc", [2, 6]), + ("a_", [0]), + ("abcdabcdab", [2, 7, 7]), + ("abcdabcdab_", [2, 7, 7, 0]), + ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), + ("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_", [26]), + ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), + ("_", [0]), + ("_ad", [0]), + ("a_", [0]), + ("_zzzb", [0]), + ("__", [0]) + ] + for input_str, expected_output in test_cases: + with self.subTest(input_str=input_str): + output = self.service.convert_measurements(input_str) + self.assertEqual(output, expected_output, f"Conversion incorrect for input '{input_str}'") + + def test_conversion_results(self): + """Test conversion results functionality.""" + test_cases = [ + ("aa", [1]), + ("abbcc", [2, 6]), + ("a_", [0]), + ("abcdabcdab", [2, 7, 7]), + ("abcdabcdab_", [2, 7, 7, 0]), + ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), + ("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_", [26]), + ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), + ("_", [0]), + ("_ad", [0]), + ("a_", [0]), + ("_zzzb", [0]), + ("__", [0]) + ] + for input_str, expected_results in test_cases: + with self.subTest(input_str=input_str): + results = self.service.measurement_results(input_str) + self.assertEqual(results, expected_results, f"Conversion results incorrect for input '{input_str}'") + +if __name__ == "__main__": + unittest.main() diff --git a/converter.py b/converter.py new file mode 100644 index 0000000..3de92b2 --- /dev/null +++ b/converter.py @@ -0,0 +1,47 @@ +class Measurements: + def __init__(self): + self.alpha_value = {chr(i): i - 96 for i in range(97, 123)} # Dictionary to map 'a' to 'z' to 1-26 + + def convert_character(self, char): + """Convert a single alphabetic character to its numeric value or handle special characters.""" + if char == '_': + return None # None signifies a break or reset + return self.alpha_value.get(char, 0) # Returns 0 for non-alphabetic characters + + def convert_measurements(self, input_string): + """Converts a string of characters into a list of numeric values, handling resets with '_'. """ + results = [] + current_sum = 0 + + for char in input_string: + value = self.convert_character(char) + if value is None: # Reset condition + results.append(current_sum) + current_sum = 0 + else: + current_sum += value + + if current_sum > 0: # Add any remaining sum to the results + results.append(current_sum) + + return results + + def measurement_results(self, input_str): + """Processes a list of numbers to handle measurement steps and aggregations.""" + numbers = self.convert_measurements(input_str) + results = [] + step_count = 0 + current_sum = 0 + + for num in numbers: + if step_count == 0: # We are expecting a new step count + step_count = num + else: + current_sum += num + step_count -= 1 + + if step_count == 0: # We have reached a complete set, reset and store the result + results.append(current_sum) + current_sum = 0 + + return results diff --git a/database.py b/database.py new file mode 100644 index 0000000..395a28c --- /dev/null +++ b/database.py @@ -0,0 +1,51 @@ +import sqlite3 + + +class Database: + def __init__(self, db_name='conversion_history.db'): + self.db_name = db_name + self.conn = sqlite3.connect(self.db_name, check_same_thread=False) + self.cursor = self.conn.cursor() + self.create_table() + + def create_table(self): + """Create the conversion_history table if it doesn't already exist.""" + create_table_query = ''' + CREATE TABLE IF NOT EXISTS conversion_history ( + id INTEGER PRIMARY KEY, + input TEXT, + output TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + ''' + self.cursor.execute(create_table_query) + self.conn.commit() + + def save_conversion(self, input_str, output_str): + """Save a conversion record to the database.""" + try: + self.cursor.execute('INSERT INTO conversion_history (input, output) VALUES (?, ?)', (input_str, output_str)) + self.conn.commit() + return True # Return True if the operation was successful + except sqlite3.Error as e: + print(f"Error saving conversion: {e}") + return False # Return False if there was an error + + def fetch_conversion_history(self): + """Retrieve the conversion history from the database.""" + try: + self.cursor.execute("SELECT input, output FROM conversion_history ORDER BY timestamp DESC") + rows = self.cursor.fetchall() + conversion_history = [{'input': row[0], 'output': row[1]} for row in rows] + return conversion_history + except sqlite3.Error as e: + print(f"Error fetching conversion history: {e}") + return [] + + def close_connection(self): + """Close the database connection.""" + try: + self.conn.close() + print("Database connection closed.") + except sqlite3.Error as e: + print(f"Error closing database connection: {e}") diff --git a/main_app.py b/main_app.py new file mode 100644 index 0000000..8592d26 --- /dev/null +++ b/main_app.py @@ -0,0 +1,29 @@ +import cherrypy +import os + + +class ServerConfigurator: + @staticmethod + def get_cherrypy_config(): + return { + '/': { + 'tools.sessions.on': True, + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [('Content-Type', 'application/json')], + } + } + + @staticmethod + def configure_server(port): + cherrypy.config.update({'server.socket_port': port}) + + @staticmethod + def start_service(): + config = ServerConfigurator.get_cherrypy_config() + # Placeholder for service start + cherrypy.quickstart(None, '/', config) + +if __name__ == '__main__': + port = int(os.getenv('PORT', 8080)) + ServerConfigurator.configure_server(port) + ServerConfigurator.start_service() diff --git a/measurement.py b/measurement.py new file mode 100644 index 0000000..edbf4ae --- /dev/null +++ b/measurement.py @@ -0,0 +1,57 @@ +import cherrypy +import logging +from converter import Measurements +from database import Database + +# Setup for logging +logging.basicConfig( + filename='logs/conversion_errors.log', # Simplified path + level=logging.DEBUG, # Adjusted log level for more verbose output during development + format='%(asctime)s:%(levelname)s:%(message)s' +) + +# Database manager instance +db_manager = Database("conversion_history.db") +measure = Measurements() # Instance of the Measurements class + +class MeasurementService: + @cherrypy.expose + @cherrypy.tools.json_out() + def get_conversion_history(self): + """Endpoint to fetch the conversion history from the database.""" + try: + history_data = db_manager.fetch_conversion_history() + return {"status": "success", "data": history_data} + except Exception as e: + logging.error("Failed to retrieve history: {}".format(str(e))) + return {"status": "error", "message": str(e)} + + @cherrypy.expose + @cherrypy.tools.json_out() + def convert_input(self, input_string=""): + """Endpoint to convert alphabetic inputs into their corresponding numeric values.""" + if input_string: + try: + result = measure.convert_measurements(input_string) + db_manager.record_conversion(input_string, result) + return {"input": input_string, "output": result, "status": "success"} + except ValueError as e: + logging.warning(f"Validation error: {e}") + return {"status": "error", "message": str(e)} + except Exception as e: + logging.critical("Unexpected error in conversion: {}".format(str(e)), exc_info=True) + return {"status": "error", "message": "An unexpected error occurred"} + else: + return {"status": "error", "message": "No input provided"} + + @cherrypy.expose + @cherrypy.tools.json_out() + def get_measurement_results(self, input_string): + """Endpoint to calculate results based on converted measurements.""" + try: + results = measure.measurement_results(input_string) + db_manager.save_results(input_string, results) + return {"input": input_string, "results": results, "status": "success"} + except Exception as e: + logging.error("Error processing results: {}".format(str(e))) + return {"status": "error", "message": str(e)} \ No newline at end of file diff --git a/unittest.py b/unittest.py new file mode 100644 index 0000000..df52aee --- /dev/null +++ b/unittest.py @@ -0,0 +1,58 @@ +import unittest +from main_app import MeasurementService + +class TestMeasurementService(unittest.TestCase): + def setUp(self): + """Set up test environment before each test method.""" + self.service = MeasurementService() + + def tearDown(self): + """Clean up test environment after each test method.""" + pass + + def test_conversion(self): + """Test conversion functionality.""" + test_cases = [ + ("aa", [1]), + ("abbcc", [2, 6]), + ("a_", [0]), + ("abcdabcdab", [2, 7, 7]), + ("abcdabcdab_", [2, 7, 7, 0]), + ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), + ("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_", [26]), + ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), + ("_", [0]), + ("_ad", [0]), + ("a_", [0]), + ("_zzzb", [0]), + ("__", [0]) + ] + for input_str, expected_output in test_cases: + with self.subTest(input_str=input_str): + output = self.service.convert_measurements(input_str) + self.assertEqual(output, expected_output, f"Conversion incorrect for input '{input_str}'") + + def test_conversion_results(self): + """Test conversion results functionality.""" + test_cases = [ + ("aa", [1]), + ("abbcc", [2, 6]), + ("a_", [0]), + ("abcdabcdab", [2, 7, 7]), + ("abcdabcdab_", [2, 7, 7, 0]), + ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), + ("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_", [26]), + ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), + ("_", [0]), + ("_ad", [0]), + ("a_", [0]), + ("_zzzb", [0]), + ("__", [0]) + ] + for input_str, expected_results in test_cases: + with self.subTest(input_str=input_str): + results = self.service.measurement_results(input_str) + self.assertEqual(results, expected_results, f"Conversion results incorrect for input '{input_str}'") + +if __name__ == "__main__": + unittest.main() From a6494e69a589ef00aa8e26412fcb972bea71d737 Mon Sep 17 00:00:00 2001 From: Haitham Yasse Hamood Al Maamari <71521@omantel.om> Date: Wed, 1 May 2024 20:03:04 +0400 Subject: [PATCH 2/5] Updated testing, history, and arranged files. --- .gitignore | 3 + attempt1/a => __init__.py | 0 attempt1/Main.py | 43 -------------- attempt1/sequence.py | 94 ------------------------------- attempt1/sequencecontroller.py | 11 ---- attempt1/sequencehistory.py | 41 -------------- attempt1/sequenceservice.py | 62 -------------------- attempt1/test_sequence.py | 58 ------------------- controller/__init__.py | 0 controller/controller.py | 45 +++++++++++++++ conversion_history.db | Bin 0 -> 8192 bytes converter.py | 47 ---------------- database.py | 51 ----------------- main.py | 38 +++++++++++++ main_app.py | 29 ---------- measurement.py | 57 ------------------- models/__init__.py | 0 models/sequence.py | 8 +++ services/__init__.py | 0 services/converter.py | 28 +++++++++ services/test/__init__.py | 0 services/test/test_conversion.py | 34 +++++++++++ unittest.py | 58 ------------------- utilities/__init__.py | 0 utilities/converter.db | Bin 0 -> 8192 bytes utilities/db.py | 34 +++++++++++ utilities/error_log.log | 45 +++++++++++++++ 27 files changed, 235 insertions(+), 551 deletions(-) create mode 100644 .gitignore rename attempt1/a => __init__.py (100%) delete mode 100644 attempt1/Main.py delete mode 100644 attempt1/sequence.py delete mode 100644 attempt1/sequencecontroller.py delete mode 100644 attempt1/sequencehistory.py delete mode 100644 attempt1/sequenceservice.py delete mode 100644 attempt1/test_sequence.py create mode 100644 controller/__init__.py create mode 100644 controller/controller.py create mode 100644 conversion_history.db delete mode 100644 converter.py delete mode 100644 database.py create mode 100644 main.py delete mode 100644 main_app.py delete mode 100644 measurement.py create mode 100644 models/__init__.py create mode 100644 models/sequence.py create mode 100644 services/__init__.py create mode 100644 services/converter.py create mode 100644 services/test/__init__.py create mode 100644 services/test/test_conversion.py delete mode 100644 unittest.py create mode 100644 utilities/__init__.py create mode 100644 utilities/converter.db create mode 100644 utilities/db.py create mode 100644 utilities/error_log.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33e3211 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +**/__pycache__/ + diff --git a/attempt1/a b/__init__.py similarity index 100% rename from attempt1/a rename to __init__.py diff --git a/attempt1/Main.py b/attempt1/Main.py deleted file mode 100644 index a8a11f3..0000000 --- a/attempt1/Main.py +++ /dev/null @@ -1,43 +0,0 @@ -import cherrypy -import logging -from sequenceservice import SequenceService - -# Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') - - -class PackageMeasurementAPI: - def __init__(self): - self.service = SequenceService() - - @cherrypy.expose - @cherrypy.tools.json_out() - def convert_measurements(self, input_sequence=None): - if input_sequence is None: - logging.info("No input provided, fetching history.") - try: - history = self.service.get_history() - return {"history": history} - except Exception as e: - logging.error("Error fetching history", exc_info=True) - cherrypy.response.status = 500 - return {"error": str(e)} - else: - logging.info(f"Received input for conversion: {input_sequence}") - try: - result = self.service.process_sequence(input_sequence) - logging.info(f"Conversion result for '{input_sequence}': {result}") - return {"input": input_sequence, "result": result} - except Exception as e: - logging.error("Error processing conversion", exc_info=True) - cherrypy.response.status = 400 - return {"error": str(e)} - - -if __name__ == '__main__': - cherrypy.config.update({ - 'server.socket_host': '0.0.0.0', - 'server.socket_port': 8080 - }) - logging.info("Starting web server...") - cherrypy.quickstart(PackageMeasurementAPI(), '/') diff --git a/attempt1/sequence.py b/attempt1/sequence.py deleted file mode 100644 index 66304ba..0000000 --- a/attempt1/sequence.py +++ /dev/null @@ -1,94 +0,0 @@ -import logging - - -class Sequence: - def __init__(self): - self.sequence = "" - logging.debug("Sequence initialized as empty.") - - def set_sequence(self, sequence): - self.sequence = sequence - logging.debug(f"Sequence set to: {sequence}") - - def encoder(self, character): - encoder = { - "_": 0, "a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7, "h": 8, - "i": 9, "j": 10, "k": 11, "l": 12, "m": 13, "n": 14, "o": 15, "p": 16, "q": 17, - "r": 18, "s": 19, "t": 20, "u": 21, "v": 22, "w": 23, "x": 24, "y": 25, "z": 26 - } - return encoder[character] - - def is_valid(self): - sequence = self.sequence - - steps = 0 - index = 0 - is_counting = False - while index < len(sequence): - if is_counting: # IF in the middle of cycle - steps = steps - 1 - if sequence[index] == 'z': - steps = steps + 1 - if steps == 0: - is_counting = False - else: # IF at the start of cycle - steps = self.encoder(sequence[index]) - if sequence[index] == 'z': - steps = steps + self.encoder(sequence[index + 1]) - if sequence[index + 1] == 'z': - steps = steps + self.encoder(sequence[index + 2]) - index += 1 - index += 1 - is_counting = True - - index += 1 - - if steps == 0: - return True - else: - return False - - def get_sequence_as_str(self): - logging.debug(f"Returning sequence: {self.sequence}") - return self.sequence - - def parse_sequence(self): - logging.info(f"Parsing sequence: {self.sequence}") - - # Mapping from characters to their respective values - value_map = {chr(i): i - 96 for i in range(ord('a'), ord('z') + 1)} - value_map['_'] = 0 - - result = [] - current_sum = 0 - current_count = 0 - - i = 0 - while i < len(self.sequence): - if self.sequence[i] == 'z' and i + 1 < len(self.sequence) and self.sequence[i + 1].isalpha(): - # Special handling for 'z' followed by another character - current_sum += value_map['z'] - i += 1 - while i < len(self.sequence) and self.sequence[i] == 'z': - current_sum += value_map['z'] - i += 1 - if i < len(self.sequence) and self.sequence[i] != '_': - current_sum += value_map[self.sequence[i]] - result.append(current_sum) - current_sum = 0 - i += 1 - elif self.sequence[i].isalpha(): - current_count = value_map[self.sequence[i]] - current_sum = 0 - i += 1 - while current_count > 0 and i < len(self.sequence) and self.sequence[i] != '_': - current_sum += value_map[self.sequence[i]] - current_count -= 1 - i += 1 - result.append(current_sum) - else: - result.append(0) - i += 1 - - logging.info(f"Sequence parsed. Result: {result}") - return result diff --git a/attempt1/sequencecontroller.py b/attempt1/sequencecontroller.py deleted file mode 100644 index 118e8da..0000000 --- a/attempt1/sequencecontroller.py +++ /dev/null @@ -1,11 +0,0 @@ -import cherrypy -from sequenceservice import SequenceService - - -class SequenceController: - @cherrypy.expose - @cherrypy.tools.json_out() - def index(self, input_str=''): - service = SequenceService() - result = service.process_sequence(input_str) - return result diff --git a/attempt1/sequencehistory.py b/attempt1/sequencehistory.py deleted file mode 100644 index b45bde7..0000000 --- a/attempt1/sequencehistory.py +++ /dev/null @@ -1,41 +0,0 @@ -import sqlite3 -import threading - -class SequenceHistory: - def __init__(self, db_path='sequence_history.db'): - self.db_path = db_path - self.local_storage = threading.local() - - def get_connection(self): - if not hasattr(self.local_storage, 'conn'): - self.local_storage.conn = sqlite3.connect(self.db_path, check_same_thread=False) - self.create_table() - return self.local_storage.conn - - def create_table(self): - conn = self.get_connection() - query = ''' - CREATE TABLE IF NOT EXISTS history ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - sequence TEXT NOT NULL, - result TEXT NOT NULL - ); - ''' - conn.execute(query) - conn.commit() - - def add_entry(self, sequence, result): - conn = self.get_connection() - query = 'INSERT INTO history (sequence, result) VALUES (?, ?)' - conn.execute(query, (sequence, str(result))) - conn.commit() - - def get_history(self): - conn = self.get_connection() - cursor = conn.cursor() - cursor.execute('SELECT sequence, result FROM history') - return cursor.fetchall() - - def __del__(self): - if hasattr(self.local_storage, 'conn'): - self.local_storage.conn.close() diff --git a/attempt1/sequenceservice.py b/attempt1/sequenceservice.py deleted file mode 100644 index 7baba75..0000000 --- a/attempt1/sequenceservice.py +++ /dev/null @@ -1,62 +0,0 @@ -from sequence import Sequence -from sequencehistory import SequenceHistory - - -class SequenceService: - def __init__(self): - self.sequence_manager = Sequence() - self.sequence_history = SequenceHistory() - self.result = [] - - def get_sequence(self, str_representation): - self.sequence_manager.set_sequence(str_representation) - if not self.sequence_manager.is_valid(): - raise Exception("INVALID SEQUENCE") - - def append_num_to_list(self, number): - self.result.append(number) - return self.result - - def process_sequence(self): - sequence = self.sequence_manager.get_sequence_as_str() - self.result = [] - index = 0 - is_counting = False - sum = 0 - steps = 0 - - while index < len(sequence): - current_char = sequence[index] - current_value = self.sequence_manager.encoder(current_char) - if current_value == 26: # 'z' - sum = 26 - index += 1 - while index < len(sequence) and self.sequence_manager.encoder(sequence[index]) == 26: - sum += 26 - index += 1 - if index < len(sequence) and self.sequence_manager.encoder(sequence[index]) != 0: - sum += self.sequence_manager.encoder(sequence[index]) - index += 1 - - count = sum - sum = 0 - while count > 0 and index < len(sequence): - sum += self.sequence_manager.encoder(sequence[index]) - count -= 1 - index += 1 - self.append_num_to_list(sum) - elif 1 <= current_value <= 25 or current_value == 0: - count = current_value if current_value > 0 else 1 - sum = 0 - index += 1 - while count > 0 and index < len(sequence): - sum += self.sequence_manager.encoder(sequence[index]) - count -= 1 - index += 1 - self.append_num_to_list(sum) - index += 1 - - # Log the sequence and result to history - self.sequence_history.add_entry(sequence, self.result) - - return self.result diff --git a/attempt1/test_sequence.py b/attempt1/test_sequence.py deleted file mode 100644 index 5bec93c..0000000 --- a/attempt1/test_sequence.py +++ /dev/null @@ -1,58 +0,0 @@ -import unittest -from - -class TestMeasurementService(unittest.TestCase): - def setUp(self): - """Set up test environment before each test method.""" - self.service = MeasurementService() - - def tearDown(self): - """Clean up test environment after each test method.""" - pass - - def test_conversion(self): - """Test conversion functionality.""" - test_cases = [ - ("aa", [1]), - ("abbcc", [2, 6]), - ("a_", [0]), - ("abcdabcdab", [2, 7, 7]), - ("abcdabcdab_", [2, 7, 7, 0]), - ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), - ("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_", [26]), - ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), - ("_", [0]), - ("_ad", [0]), - ("a_", [0]), - ("_zzzb", [0]), - ("__", [0]) - ] - for input_str, expected_output in test_cases: - with self.subTest(input_str=input_str): - output = self.service.convert_measurements(input_str) - self.assertEqual(output, expected_output, f"Conversion incorrect for input '{input_str}'") - - def test_conversion_results(self): - """Test conversion results functionality.""" - test_cases = [ - ("aa", [1]), - ("abbcc", [2, 6]), - ("a_", [0]), - ("abcdabcdab", [2, 7, 7]), - ("abcdabcdab_", [2, 7, 7, 0]), - ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), - ("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_", [26]), - ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), - ("_", [0]), - ("_ad", [0]), - ("a_", [0]), - ("_zzzb", [0]), - ("__", [0]) - ] - for input_str, expected_results in test_cases: - with self.subTest(input_str=input_str): - results = self.service.measurement_results(input_str) - self.assertEqual(results, expected_results, f"Conversion results incorrect for input '{input_str}'") - -if __name__ == "__main__": - unittest.main() diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controller/controller.py b/controller/controller.py new file mode 100644 index 0000000..91435a4 --- /dev/null +++ b/controller/controller.py @@ -0,0 +1,45 @@ +import cherrypy +from datetime import datetime + +from services.converter import PackageConverter +from utilities.db import PackageMeasurementHistory +from models.sequence import Sequence + +class ConverterAPI: + def __init__(self): + self.converter = PackageConverter() + self.sequence_history = PackageMeasurementHistory('./utilis/converter.db') + + @cherrypy.expose + @cherrypy.tools.json_out() + def convert_measurements(self, input=None): + """ + API endpoint to convert measurements from an input string. + """ + try: + input_string = input if input is not None else cherrypy.request.params.get("input", "") + measurement = self.converter.convert_measurements(input_string) # Updated method name + time = datetime.now() + response = "Measurements processed" if measurement != "Invalid" else "Invalid input" + sequence = Sequence(input_string, measurement, time, response) + self.sequence_history.save_curr_seq(sequence) # Ensure this method accepts a Sequence object + + return {"status": "success" if measurement != "Invalid" else "fail", + "data": measurement if measurement != "Invalid" else None, + "error": None if measurement != "Invalid" else "Invalid input"} + except Exception as e: + cherrypy.response.status = 500 + return {"status": "error", "data": None, "error": str(e)} + + @cherrypy.expose + @cherrypy.tools.json_out() + def get_history(self): + """ + API endpoint to retrieve measurement history. + """ + try: + history = self.sequence_history.get_history() + return {"status": "success", "data": history, "error": None} + except Exception as e: + cherrypy.response.status = 500 + return {"status": "error", "data": None, "error": str(e)} diff --git a/conversion_history.db b/conversion_history.db new file mode 100644 index 0000000000000000000000000000000000000000..82fd855586b10bf9f39f0dafc2dc6969ea63a542 GIT binary patch literal 8192 zcmeI#!Ab)$5C-6+2!deo=5>x1L@0d$V;yC2x72hB_Ec6~G2m{>ZYp>Z@u_?{F?*<> zi+B{||49gw83=s0%q~+G3cB-knFkuFJ*BleCQ?f2xO;JL($-s>BA#viTeLc=-ge`Z z?!T+J5C}j30uX=z1Rwwb2tWV=5P-lR2)sP&?fv0UzYaov{ZQQa>anOB=d0ztYl5$z zx;MSCWg|>tPEw|BpAL5DTih)r6XA2VG_^@)?1C 0: # Add any remaining sum to the results - results.append(current_sum) - - return results - - def measurement_results(self, input_str): - """Processes a list of numbers to handle measurement steps and aggregations.""" - numbers = self.convert_measurements(input_str) - results = [] - step_count = 0 - current_sum = 0 - - for num in numbers: - if step_count == 0: # We are expecting a new step count - step_count = num - else: - current_sum += num - step_count -= 1 - - if step_count == 0: # We have reached a complete set, reset and store the result - results.append(current_sum) - current_sum = 0 - - return results diff --git a/database.py b/database.py deleted file mode 100644 index 395a28c..0000000 --- a/database.py +++ /dev/null @@ -1,51 +0,0 @@ -import sqlite3 - - -class Database: - def __init__(self, db_name='conversion_history.db'): - self.db_name = db_name - self.conn = sqlite3.connect(self.db_name, check_same_thread=False) - self.cursor = self.conn.cursor() - self.create_table() - - def create_table(self): - """Create the conversion_history table if it doesn't already exist.""" - create_table_query = ''' - CREATE TABLE IF NOT EXISTS conversion_history ( - id INTEGER PRIMARY KEY, - input TEXT, - output TEXT, - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - ''' - self.cursor.execute(create_table_query) - self.conn.commit() - - def save_conversion(self, input_str, output_str): - """Save a conversion record to the database.""" - try: - self.cursor.execute('INSERT INTO conversion_history (input, output) VALUES (?, ?)', (input_str, output_str)) - self.conn.commit() - return True # Return True if the operation was successful - except sqlite3.Error as e: - print(f"Error saving conversion: {e}") - return False # Return False if there was an error - - def fetch_conversion_history(self): - """Retrieve the conversion history from the database.""" - try: - self.cursor.execute("SELECT input, output FROM conversion_history ORDER BY timestamp DESC") - rows = self.cursor.fetchall() - conversion_history = [{'input': row[0], 'output': row[1]} for row in rows] - return conversion_history - except sqlite3.Error as e: - print(f"Error fetching conversion history: {e}") - return [] - - def close_connection(self): - """Close the database connection.""" - try: - self.conn.close() - print("Database connection closed.") - except sqlite3.Error as e: - print(f"Error closing database connection: {e}") diff --git a/main.py b/main.py new file mode 100644 index 0000000..48bfb96 --- /dev/null +++ b/main.py @@ -0,0 +1,38 @@ +import sys +import logging +import cherrypy +from controller.controller import ConverterAPI + + +def setup_logging(): + logging.basicConfig( + filename='utilis/error_log.log', + level=logging.CRITICAL, + format='%(asctime)s:%(levelname)s:%(message)s' + ) + logging.getLogger().addHandler(logging.StreamHandler()) + + +def start_server(port=8080): + try: + cherrypy.config.update({ + 'server.socket_host': '0.0.0.0', + 'server.socket_port': port + }) + cherrypy.tree.mount(ConverterAPI(), '/') + cherrypy.engine.start() + cherrypy.engine.block() + except Exception as e: + logging.critical("Failed to start server: ", exc_info=e) + + +if __name__ == '__main__': + setup_logging() + port = 8080 # Default port + if len(sys.argv) > 1: + try: + port = int(sys.argv[1]) + except ValueError: + logging.error("Invalid port number provided. Using default port (8080).") + + start_server(port) diff --git a/main_app.py b/main_app.py deleted file mode 100644 index 8592d26..0000000 --- a/main_app.py +++ /dev/null @@ -1,29 +0,0 @@ -import cherrypy -import os - - -class ServerConfigurator: - @staticmethod - def get_cherrypy_config(): - return { - '/': { - 'tools.sessions.on': True, - 'tools.response_headers.on': True, - 'tools.response_headers.headers': [('Content-Type', 'application/json')], - } - } - - @staticmethod - def configure_server(port): - cherrypy.config.update({'server.socket_port': port}) - - @staticmethod - def start_service(): - config = ServerConfigurator.get_cherrypy_config() - # Placeholder for service start - cherrypy.quickstart(None, '/', config) - -if __name__ == '__main__': - port = int(os.getenv('PORT', 8080)) - ServerConfigurator.configure_server(port) - ServerConfigurator.start_service() diff --git a/measurement.py b/measurement.py deleted file mode 100644 index edbf4ae..0000000 --- a/measurement.py +++ /dev/null @@ -1,57 +0,0 @@ -import cherrypy -import logging -from converter import Measurements -from database import Database - -# Setup for logging -logging.basicConfig( - filename='logs/conversion_errors.log', # Simplified path - level=logging.DEBUG, # Adjusted log level for more verbose output during development - format='%(asctime)s:%(levelname)s:%(message)s' -) - -# Database manager instance -db_manager = Database("conversion_history.db") -measure = Measurements() # Instance of the Measurements class - -class MeasurementService: - @cherrypy.expose - @cherrypy.tools.json_out() - def get_conversion_history(self): - """Endpoint to fetch the conversion history from the database.""" - try: - history_data = db_manager.fetch_conversion_history() - return {"status": "success", "data": history_data} - except Exception as e: - logging.error("Failed to retrieve history: {}".format(str(e))) - return {"status": "error", "message": str(e)} - - @cherrypy.expose - @cherrypy.tools.json_out() - def convert_input(self, input_string=""): - """Endpoint to convert alphabetic inputs into their corresponding numeric values.""" - if input_string: - try: - result = measure.convert_measurements(input_string) - db_manager.record_conversion(input_string, result) - return {"input": input_string, "output": result, "status": "success"} - except ValueError as e: - logging.warning(f"Validation error: {e}") - return {"status": "error", "message": str(e)} - except Exception as e: - logging.critical("Unexpected error in conversion: {}".format(str(e)), exc_info=True) - return {"status": "error", "message": "An unexpected error occurred"} - else: - return {"status": "error", "message": "No input provided"} - - @cherrypy.expose - @cherrypy.tools.json_out() - def get_measurement_results(self, input_string): - """Endpoint to calculate results based on converted measurements.""" - try: - results = measure.measurement_results(input_string) - db_manager.save_results(input_string, results) - return {"input": input_string, "results": results, "status": "success"} - except Exception as e: - logging.error("Error processing results: {}".format(str(e))) - return {"status": "error", "message": str(e)} \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/sequence.py b/models/sequence.py new file mode 100644 index 0000000..63b8e8b --- /dev/null +++ b/models/sequence.py @@ -0,0 +1,8 @@ + + +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/services/__init__.py b/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/converter.py b/services/converter.py new file mode 100644 index 0000000..19ab755 --- /dev/null +++ b/services/converter.py @@ -0,0 +1,28 @@ +import string + + +class PackageConverter: + def convert_measurements(self, input_string: str) -> list: + """Convert input string to a list of summed measurements.""" + values = [self.convert_char_to_value(char) for char in input_string] + return self.process_measurements(values) + + @staticmethod + def convert_char_to_value(char: str) -> int: + """Convert a character to its corresponding numeric value.""" + if char == '_': + return 0 + return string.ascii_lowercase.index(char.lower()) + 1 + + def process_measurements(self, values: list) -> list: + """Process list of numeric values and sum according to the encoded rules.""" + result = [] + i = 0 + while i < len(values): + length = values[i] + if length == 0 or i + length >= len(values): + result.append(0) + break + result.append(sum(values[i+1:i+1+length])) + i += length + 1 + return result 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_conversion.py b/services/test/test_conversion.py new file mode 100644 index 0000000..d984990 --- /dev/null +++ b/services/test/test_conversion.py @@ -0,0 +1,34 @@ +import unittest +from services.converter import PackageConverter + + +class TestPackageConverter(unittest.TestCase): + def setUp(self): + self.converter = PackageConverter() + + def test_single_characters(self): + """Test conversion of single-character strings.""" + self.assertEqual(self.converter.convert_measurements("aa"), [1]) + self.assertEqual(self.converter.convert_measurements("__"), [0]) + self.assertEqual(self.converter.convert_measurements("a_"), [0]) + + def test_multiple_characters(self): + """Test conversion of strings with multiple characters.""" + self.assertEqual(self.converter.convert_measurements('abbcc'), [2, 6]) + self.assertEqual(self.converter.convert_measurements('abcdabcdab'), [2, 7, 7]) + self.assertEqual(self.converter.convert_measurements('abcdabcdab_'), [2, 7, 7, 0]) + + def test_invalid_sequences(self): + """Test conversion of strings that are expected to be invalid.""" + self.assertEqual(self.converter.convert_measurements("abz"), "Invalid") + self.assertEqual(self.converter.convert_measurements("aaa"), "Invalid") + self.assertEqual(self.converter.convert_measurements("abc"), "Invalid") + + def test_edge_cases(self): + """Test conversion of strings with edge cases.""" + self.assertEqual(self.converter.convert_measurements('dz_a_aazzaaa'), [28, 53, 1]) + self.assertEqual(self.converter.convert_measurements('_zzzb'), [0]) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittest.py b/unittest.py deleted file mode 100644 index df52aee..0000000 --- a/unittest.py +++ /dev/null @@ -1,58 +0,0 @@ -import unittest -from main_app import MeasurementService - -class TestMeasurementService(unittest.TestCase): - def setUp(self): - """Set up test environment before each test method.""" - self.service = MeasurementService() - - def tearDown(self): - """Clean up test environment after each test method.""" - pass - - def test_conversion(self): - """Test conversion functionality.""" - test_cases = [ - ("aa", [1]), - ("abbcc", [2, 6]), - ("a_", [0]), - ("abcdabcdab", [2, 7, 7]), - ("abcdabcdab_", [2, 7, 7, 0]), - ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), - ("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_", [26]), - ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), - ("_", [0]), - ("_ad", [0]), - ("a_", [0]), - ("_zzzb", [0]), - ("__", [0]) - ] - for input_str, expected_output in test_cases: - with self.subTest(input_str=input_str): - output = self.service.convert_measurements(input_str) - self.assertEqual(output, expected_output, f"Conversion incorrect for input '{input_str}'") - - def test_conversion_results(self): - """Test conversion results functionality.""" - test_cases = [ - ("aa", [1]), - ("abbcc", [2, 6]), - ("a_", [0]), - ("abcdabcdab", [2, 7, 7]), - ("abcdabcdab_", [2, 7, 7, 0]), - ("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]), - ("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_", [26]), - ("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]), - ("_", [0]), - ("_ad", [0]), - ("a_", [0]), - ("_zzzb", [0]), - ("__", [0]) - ] - for input_str, expected_results in test_cases: - with self.subTest(input_str=input_str): - results = self.service.measurement_results(input_str) - self.assertEqual(results, expected_results, f"Conversion results incorrect for input '{input_str}'") - -if __name__ == "__main__": - unittest.main() diff --git a/utilities/__init__.py b/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utilities/converter.db b/utilities/converter.db new file mode 100644 index 0000000000000000000000000000000000000000..37e6c029278659b683b9beabebff76d48206b47c GIT binary patch literal 8192 zcmeI$K~KUk6ae53VJ0Sk!~^j}kD9=;4!5y+aq40aFoHWsh%p0-Az=g7&BUwm=Xms@ z|H^-0X96KHx*a{dmvw8KzP|LkbvNDiG^BVk4?H)-b+n5F0i9rs5E6OI@U{wpce1Mi z|Hk8_5Rv4+=lLq3@QU~Y0RkWZ0w4eaAOHd&00JNY0w4eai2~1EVY^(Z2rtXf9nL7D z%ZU0T%GRCCS{z`%ZrS~6a-P44!h045 zQ~v=w_N|kg;!&4H0rjXKvXm(SWsAAbXv*c`)T20lvC#Dvf1cqmzMJO?Z>acD^ovJ@ zPu_(90T2KI5C8!X009sH0T2KI5CDOHA#j`%QEAVxTz5e3I-A$&MKJ&VA23#A<)|jB zHJM;yn5tpQx}>OzOcX0CqVhgZ9u7yNfl|d<>Me+*6Qb7}jV%%7ONKQ_m)S5?jc3Ni zFEerR*005x`$)%_MWgr>lV~=3+B}GE)`_WTl1vOuQ_qE1f$#N0A;m7lkYv5C8QK?O Crn27v literal 0 HcmV?d00001 diff --git a/utilities/db.py b/utilities/db.py new file mode 100644 index 0000000..32042c6 --- /dev/null +++ b/utilities/db.py @@ -0,0 +1,34 @@ +import sqlite3 +import logging +from models.sequence import Sequence +import json + +class PackageMeasurementHistory: + def __init__(self, db_path: str): + self.db_path = db_path + self.create_table() + + def create_table(self): + with sqlite3.connect(self.db_path) as conn: + conn.execute(''' + CREATE TABLE IF NOT EXISTS sequences ( + id INTEGER PRIMARY KEY, + input_string TEXT, + measurements TEXT, + response TEXT, + time TIMESTAMP + ) + ''') + + def save_curr_seq(self, sequence: Sequence) -> bool: + """Save a Sequence object to the database.""" + try: + with sqlite3.connect(self.db_path) as conn: + conn.execute( + "INSERT INTO sequences (input_string, measurements, response, time) VALUES (?, ?, ?, ?)", + (sequence.input_string, json.dumps(sequence.measurement), sequence.response, sequence.time) + ) + return True + except sqlite3.Error as e: + logging.error(f"Error saving sequence: {e}") + return False diff --git a/utilities/error_log.log b/utilities/error_log.log new file mode 100644 index 0000000..2db56b5 --- /dev/null +++ b/utilities/error_log.log @@ -0,0 +1,45 @@ +2024-05-01 17:30:30,846:INFO:[01/May/2024:17:30:30] ENGINE Bus STARTING +2024-05-01 17:30:30,848:INFO:[01/May/2024:17:30:30] ENGINE Started monitor thread 'Autoreloader'. +2024-05-01 17:30:31,082:INFO:[01/May/2024:17:30:31] ENGINE Serving on http://0.0.0.0:8080 +2024-05-01 17:30:31,083:INFO:[01/May/2024:17:30:31] ENGINE Bus STARTED +2024-05-01 17:31:19,093:INFO:127.0.0.1 - - [01/May/2024:17:31:19] "GET /convert_measurements?input=aa HTTP/1.1" 200 3 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 17:31:26,027:INFO:127.0.0.1 - - [01/May/2024:17:31:26] "GET /convert_measurements?input=abbcc HTTP/1.1" 200 6 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 17:31:50,616:INFO:[01/May/2024:17:31:50] ENGINE Keyboard Interrupt: shutting down bus +2024-05-01 17:31:50,616:INFO:[01/May/2024:17:31:50] ENGINE Bus STOPPING +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE Stopped thread 'Autoreloader'. +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE Bus STOPPED +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE Bus EXITING +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE Bus EXITED +2024-05-01 17:31:50,786:INFO:[01/May/2024:17:31:50] ENGINE Waiting for child threads to terminate... +2024-05-01 19:42:48,626:INFO:[01/May/2024:19:42:48] ENGINE Bus STARTING +2024-05-01 19:42:48,627:INFO:[01/May/2024:19:42:48] ENGINE Started monitor thread 'Autoreloader'. +2024-05-01 19:42:48,849:INFO:[01/May/2024:19:42:48] ENGINE Serving on http://0.0.0.0:8080 +2024-05-01 19:42:48,849:INFO:[01/May/2024:19:42:48] ENGINE Bus STARTED +2024-05-01 19:43:46,136:INFO:127.0.0.1 - - [01/May/2024:19:43:46] "GET /aa HTTP/1.1" 404 1522 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:43:50,617:INFO:127.0.0.1 - - [01/May/2024:19:43:50] "GET /convert_measurements/aa HTTP/1.1" 500 121 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:44:07,455:INFO:127.0.0.1 - - [01/May/2024:19:44:07] "GET /convert_measurements?input=abbcc HTTP/1.1" 500 121 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:44:16,666:INFO:127.0.0.1 - - [01/May/2024:19:44:16] "GET /convert_measurements HTTP/1.1" 500 121 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:45:00,398:INFO:[01/May/2024:19:45:00] ENGINE Keyboard Interrupt: shutting down bus +2024-05-01 19:45:00,398:INFO:[01/May/2024:19:45:00] ENGINE Bus STOPPING +2024-05-01 19:45:00,531:INFO:[01/May/2024:19:45:00] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-01 19:45:00,532:INFO:[01/May/2024:19:45:00] ENGINE Stopped thread 'Autoreloader'. +2024-05-01 19:45:00,532:INFO:[01/May/2024:19:45:00] ENGINE Bus STOPPED +2024-05-01 19:45:00,532:INFO:[01/May/2024:19:45:00] ENGINE Bus EXITING +2024-05-01 19:45:00,532:INFO:[01/May/2024:19:45:00] ENGINE Bus EXITED +2024-05-01 19:45:00,532:INFO:[01/May/2024:19:45:00] ENGINE Waiting for child threads to terminate... +2024-05-01 19:58:50,913:INFO:[01/May/2024:19:58:50] ENGINE Bus STARTING +2024-05-01 19:58:50,914:INFO:[01/May/2024:19:58:50] ENGINE Started monitor thread 'Autoreloader'. +2024-05-01 19:58:51,147:INFO:[01/May/2024:19:58:51] ENGINE Serving on http://0.0.0.0:8080 +2024-05-01 19:58:51,147:INFO:[01/May/2024:19:58:51] ENGINE Bus STARTED +2024-05-01 19:58:56,733:INFO:127.0.0.1 - - [01/May/2024:19:58:56] "GET /convert_measurements HTTP/1.1" 200 48 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:59:01,734:INFO:127.0.0.1 - - [01/May/2024:19:59:01] "GET /convert_measurements?input=abbcc HTTP/1.1" 200 52 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:59:07,260:INFO:127.0.0.1 - - [01/May/2024:19:59:07] "GET /convert_measurements?input=aa HTTP/1.1" 200 49 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-01 19:59:13,194:INFO:[01/May/2024:19:59:13] ENGINE Keyboard Interrupt: shutting down bus +2024-05-01 19:59:13,194:INFO:[01/May/2024:19:59:13] ENGINE Bus STOPPING +2024-05-01 19:59:13,344:INFO:[01/May/2024:19:59:13] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-01 19:59:13,344:INFO:[01/May/2024:19:59:13] ENGINE Stopped thread 'Autoreloader'. +2024-05-01 19:59:13,345:INFO:[01/May/2024:19:59:13] ENGINE Bus STOPPED +2024-05-01 19:59:13,345:INFO:[01/May/2024:19:59:13] ENGINE Bus EXITING +2024-05-01 19:59:13,345:INFO:[01/May/2024:19:59:13] ENGINE Bus EXITED +2024-05-01 19:59:13,345:INFO:[01/May/2024:19:59:13] ENGINE Waiting for child threads to terminate... From 2e4263589dc83b85bd52ce591bcdc317135a64b9 Mon Sep 17 00:00:00 2001 From: Haitham Yasse Hamood Al Maamari <71521@omantel.om> Date: Wed, 1 May 2024 20:06:54 +0400 Subject: [PATCH 3/5] Updated README.md --- README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bd94696..7ff6ec7 100644 --- a/README.md +++ b/README.md @@ -29,26 +29,19 @@ This API is designed to convert measurement input strings into a list of total v ### 1. Start the server application: - python main_app.py + python main.py ### 2. The server will start, and you will see a status message. ## API Usage ### 1. To view the converted alphabet input string into the corresponding list of numbers in JSON format. - - ### Example requests: - http://localhost:8888/convert_measurements/abbcc - - http://localhost:8888/convert_measurements/aa - -### To retrieve the stored request history, use the following endpoint: + http://localhost:8888/convert_measurements?input_str=abbcc - GET /get_data_from_db + http://localhost:8888/convert_measurements?input_str=aa -This endpoint will return the persisted history of all requests made to the conversion endpoint. ## Contributing Contributing to this project is prohibited due to Course Restrictions. \ No newline at end of file From 514822a093e2e88feb72e12717be91047b9f235e Mon Sep 17 00:00:00 2001 From: Haitham Yasse Hamood Al Maamari <71521@omantel.om> Date: Thu, 2 May 2024 09:52:13 +0400 Subject: [PATCH 4/5] Unit test cases fixed --- README.md | 4 +- controller/controller.py | 13 +-- main.py | 2 +- services/converter.py | 65 +++++++++++-- services/test/test_conversion.py | 6 +- utilities/converter.db | Bin 8192 -> 8192 bytes utilities/db.py | 1 + utilities/error_log.log | 158 +++++++++++++++++++++++++++++++ 8 files changed, 229 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 7ff6ec7..1233b3e 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ This API is designed to convert measurement input strings into a list of total v ### Example requests: - http://localhost:8888/convert_measurements?input_str=abbcc + http://localhost:8080/convert_measurements?input=abbcc - http://localhost:8888/convert_measurements?input_str=aa + http://localhost:8080/convert_measurements?input=aa ## Contributing diff --git a/controller/controller.py b/controller/controller.py index 91435a4..9cc07f8 100644 --- a/controller/controller.py +++ b/controller/controller.py @@ -5,10 +5,11 @@ from utilities.db import PackageMeasurementHistory from models.sequence import Sequence + class ConverterAPI: def __init__(self): self.converter = PackageConverter() - self.sequence_history = PackageMeasurementHistory('./utilis/converter.db') + self.sequence_history = PackageMeasurementHistory('./utilities/converter.db') @cherrypy.expose @cherrypy.tools.json_out() @@ -25,11 +26,11 @@ def convert_measurements(self, input=None): self.sequence_history.save_curr_seq(sequence) # Ensure this method accepts a Sequence object return {"status": "success" if measurement != "Invalid" else "fail", - "data": measurement if measurement != "Invalid" else None, - "error": None if measurement != "Invalid" else "Invalid input"} + "err_msg": "", + "result": measurement if measurement != "Invalid" else None} except Exception as e: cherrypy.response.status = 500 - return {"status": "error", "data": None, "error": str(e)} + return {"status": "fail", "err_msg": str(e), "result": None} @cherrypy.expose @cherrypy.tools.json_out() @@ -39,7 +40,7 @@ def get_history(self): """ try: history = self.sequence_history.get_history() - return {"status": "success", "data": history, "error": None} + return {"status": "success", "err_msg": "","data": history} except Exception as e: cherrypy.response.status = 500 - return {"status": "error", "data": None, "error": str(e)} + return {"status": "error", "err_msg": str(e), "data": None} diff --git a/main.py b/main.py index 48bfb96..8fb4e8e 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ def setup_logging(): logging.basicConfig( - filename='utilis/error_log.log', + filename='utilities/error_log.log', level=logging.CRITICAL, format='%(asctime)s:%(levelname)s:%(message)s' ) diff --git a/services/converter.py b/services/converter.py index 19ab755..b698878 100644 --- a/services/converter.py +++ b/services/converter.py @@ -5,7 +5,8 @@ class PackageConverter: def convert_measurements(self, input_string: str) -> list: """Convert input string to a list of summed measurements.""" values = [self.convert_char_to_value(char) for char in input_string] - return self.process_measurements(values) + lst = self.convert(values) + return self.process_measurements(lst) @staticmethod def convert_char_to_value(char: str) -> int: @@ -13,16 +14,64 @@ def convert_char_to_value(char: str) -> int: if char == '_': return 0 return string.ascii_lowercase.index(char.lower()) + 1 + def is_empty(self, lst): + if len(lst) < 1: + return True + else: + return False + + def convert(self, lst): + count = 0 + i = 0 + while not self.is_empty(lst): + # print(lst[i]) + if i >= len(lst): + if count >= 26: + return "invalid" + return lst + elif lst[i] >= 26: + count += 26 + lst.pop(i) + + else: + count += lst[i] + lst[i] = count + i += 1 + count = 0 + return lst + + + def process_measurements(self, values: list) -> list: """Process list of numeric values and sum according to the encoded rules.""" result = [] i = 0 - while i < len(values): - length = values[i] - if length == 0 or i + length >= len(values): - result.append(0) - break - result.append(sum(values[i+1:i+1+length])) - i += length + 1 + # for i in range(len(values)): + if values != "invalid": + + while not self.is_empty(values): + length = values[0] + values.pop(0) + # print(length, ": ", len(values)) + if length == 0: #or i + length >= len(values): + result.append(0) + break + elif length > len(values): + result = [] + break + + else: + # result.append(sum(values[i+1:i+1+length])) + result.append(sum(values[:length])) + values = values[length:] + # i += length + 1 + else: + result = [] return result + + +converter = PackageConverter() +# print(converter.convert_measurements("aaa")) +print(converter.convert_measurements("abz")) +# print(converter.convert_measurements("abc")) \ No newline at end of file diff --git a/services/test/test_conversion.py b/services/test/test_conversion.py index d984990..2777b57 100644 --- a/services/test/test_conversion.py +++ b/services/test/test_conversion.py @@ -20,9 +20,9 @@ def test_multiple_characters(self): def test_invalid_sequences(self): """Test conversion of strings that are expected to be invalid.""" - self.assertEqual(self.converter.convert_measurements("abz"), "Invalid") - self.assertEqual(self.converter.convert_measurements("aaa"), "Invalid") - self.assertEqual(self.converter.convert_measurements("abc"), "Invalid") + self.assertEqual(self.converter.convert_measurements("abz"), []) + self.assertEqual(self.converter.convert_measurements("aaa"), []) + self.assertEqual(self.converter.convert_measurements("abc"), []) def test_edge_cases(self): """Test conversion of strings with edge cases.""" diff --git a/utilities/converter.db b/utilities/converter.db index 37e6c029278659b683b9beabebff76d48206b47c..470363f1d8ece832897484c2767b27ca898100dd 100644 GIT binary patch delta 643 zcmZwE&x+Gf6bA5gVX8@!*v6I-JGDgyH*z`mUvkgQqEo6*F^d`~U9=LAF1iq*PaqC` zg?WLZ3lSIb1^O-`E(>#$XrR)~U0i-S=YC(d*W2sQ&lyX7WZRNud>9Vf^o1AODz)|GLySvp^@mh;s*{cDSU%&SbU zn8X5O#*$X~Y-6iw{aP?05lnRL+E!3Z`wwtMOl+=%b}EooplG<> +Traceback (most recent call last): + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\portend.py", line 122, in free + Checker(timeout=0.1).assert_free(host, port) + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\portend.py", line 71, in assert_free + list(itertools.starmap(self._connect, info)) + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\portend.py", line 87, in _connect + raise PortNotFree(tmpl.format(**locals())) +portend.PortNotFree: Port 8080 is in use on 127.0.0.1. + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\cherrypy\process\wspbus.py", line 230, in publish + output.append(listener(*args, **kwargs)) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\cherrypy\_cpserver.py", line 180, in start + super(Server, self).start() + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\cherrypy\process\servers.py", line 177, in start + portend.free(*self.bind_addr, timeout=Timeouts.free) + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\portend.py", line 126, in free + raise Timeout("Port {port} not free on {host}.".format(**locals())) +portend.Timeout: Port 8080 not free on 0.0.0.0. + +2024-05-02 09:02:35,945:ERROR:[02/May/2024:09:02:35] ENGINE Shutting down due to error in start listener: +Traceback (most recent call last): + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\cherrypy\process\wspbus.py", line 268, in start + self.publish('start') + File "C:\Users\71521\AppData\Roaming\Python\Python312\site-packages\cherrypy\process\wspbus.py", line 248, in publish + raise exc +cherrypy.process.wspbus.ChannelFailures: Timeout('Port 8080 not free on 0.0.0.0.') + +2024-05-02 09:02:35,945:INFO:[02/May/2024:09:02:35] ENGINE Bus STOPPING +2024-05-02 09:02:35,946:INFO:[02/May/2024:09:02:35] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) already shut down +2024-05-02 09:02:35,946:INFO:[02/May/2024:09:02:35] ENGINE Stopped thread 'Autoreloader'. +2024-05-02 09:02:35,946:INFO:[02/May/2024:09:02:35] ENGINE Bus STOPPED +2024-05-02 09:02:35,946:INFO:[02/May/2024:09:02:35] ENGINE Bus EXITING +2024-05-02 09:02:35,946:INFO:[02/May/2024:09:02:35] ENGINE Bus EXITED +2024-05-02 09:02:39,835:INFO:127.0.0.1 - - [02/May/2024:09:02:39] "GET /convert_measurements?input=aa HTTP/1.1" 200 51 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-02 09:02:43,140:INFO:127.0.0.1 - - [02/May/2024:09:02:43] "GET /convert_measurements?input=aaa HTTP/1.1" 200 54 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-02 09:04:10,062:INFO:[02/May/2024:09:04:10] ENGINE Keyboard Interrupt: shutting down bus +2024-05-02 09:04:10,063:INFO:[02/May/2024:09:04:10] ENGINE Bus STOPPING +2024-05-02 09:04:10,256:INFO:[02/May/2024:09:04:10] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-02 09:04:10,256:INFO:[02/May/2024:09:04:10] ENGINE Stopped thread 'Autoreloader'. +2024-05-02 09:04:10,256:INFO:[02/May/2024:09:04:10] ENGINE Bus STOPPED +2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Bus EXITING +2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Bus EXITED +2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Waiting for child threads to terminate... From 9cf890e31d51d841743d71457df908e84a6849e4 Mon Sep 17 00:00:00 2001 From: Haitham Yasse Hamood Al Maamari <71521@omantel.om> Date: Thu, 2 May 2024 10:09:47 +0400 Subject: [PATCH 5/5] completed --- utilities/converter.db | Bin 8192 -> 8192 bytes utilities/error_log.log | 14 ++++++++++++++ 2 files changed, 14 insertions(+) diff --git a/utilities/converter.db b/utilities/converter.db index 470363f1d8ece832897484c2767b27ca898100dd..c84901efaa8ba1c515073f7db129ad47ad567118 100644 GIT binary patch delta 760 zcmaJ^ znLp4&|3LBP#X`Ze|HPxmI@8$fLa94DusrYkJp0Tu@5$t3^0l>faN2&kb#U?iy!K$f zbfwGNhyCi8>g%fG{B%A!qss5fS>;vvSNXWi@gMvhXZSfrrK^kM52c6kK1PkYN|wvz zOkZ~V2K5PyAR2{qNFpMojGyXjH>l^R5FvwjpdBx0=P0F=@fUrJnhllAvn*Tay4T;? zG=Nf!%D9J7-87_n+Z(urB1q20PmCAKPFI%O%*#SZB1T<|YNl+dlklVcx4RS zJd3*@2AYM;3Op+c0FM|wLxw5}fyy^+jJkV8&Mn3E zyLbBIR^eiTf9V=Au6RhNE=Gf%N>-UYe`*pkZfO+wJY1)lPmQ63i~&h@^|(!Mr^ced qMHKJoYi~141_dDv+AN?%4kI8!sQ9r(Yilcn4_QP6C*LmJN$D@A>A(H} delta 34 qcmZp0XmFSy%_ufe#+gxUW5N=CW>KyKli3BdHx~ABZ9X97zzP7cY6?*R diff --git a/utilities/error_log.log b/utilities/error_log.log index 1b3a5b2..de0fbe7 100644 --- a/utilities/error_log.log +++ b/utilities/error_log.log @@ -201,3 +201,17 @@ cherrypy.process.wspbus.ChannelFailures: Timeout('Port 8080 not free on 0.0.0.0. 2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Bus EXITING 2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Bus EXITED 2024-05-02 09:04:10,257:INFO:[02/May/2024:09:04:10] ENGINE Waiting for child threads to terminate... +2024-05-02 09:51:36,429:INFO:[02/May/2024:09:51:36] ENGINE Bus STARTING +2024-05-02 09:51:36,429:INFO:[02/May/2024:09:51:36] ENGINE Started monitor thread 'Autoreloader'. +2024-05-02 09:51:36,649:INFO:[02/May/2024:09:51:36] ENGINE Serving on http://0.0.0.0:8080 +2024-05-02 09:51:36,650:INFO:[02/May/2024:09:51:36] ENGINE Bus STARTED +2024-05-02 09:51:41,888:INFO:127.0.0.1 - - [02/May/2024:09:51:41] "GET /convert_measurements?input=aaa HTTP/1.1" 200 50 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-02 09:51:44,032:INFO:127.0.0.1 - - [02/May/2024:09:51:44] "GET /convert_measurements?input=aa HTTP/1.1" 200 51 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" +2024-05-02 09:52:54,998:INFO:[02/May/2024:09:52:54] ENGINE Keyboard Interrupt: shutting down bus +2024-05-02 09:52:54,999:INFO:[02/May/2024:09:52:54] ENGINE Bus STOPPING +2024-05-02 09:52:55,151:INFO:[02/May/2024:09:52:55] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down +2024-05-02 09:52:55,151:INFO:[02/May/2024:09:52:55] ENGINE Stopped thread 'Autoreloader'. +2024-05-02 09:52:55,152:INFO:[02/May/2024:09:52:55] ENGINE Bus STOPPED +2024-05-02 09:52:55,152:INFO:[02/May/2024:09:52:55] ENGINE Bus EXITING +2024-05-02 09:52:55,152:INFO:[02/May/2024:09:52:55] ENGINE Bus EXITED +2024-05-02 09:52:55,152:INFO:[02/May/2024:09:52:55] ENGINE Waiting for child threads to terminate...