Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
**/__pycache__/

47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.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:8080/convert_measurements?input=abbcc

http://localhost:8080/convert_measurements?input=aa


## Contributing
Contributing to this project is prohibited due to Course Restrictions.
Empty file added __init__.py
Empty file.
Empty file added controller/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions controller/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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('./utilities/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",
"err_msg": "",
"result": measurement if measurement != "Invalid" else None}
except Exception as e:
cherrypy.response.status = 500
return {"status": "fail", "err_msg": str(e), "result": None}

@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", "err_msg": "","data": history}
except Exception as e:
cherrypy.response.status = 500
return {"status": "error", "err_msg": str(e), "data": None}
Binary file added conversion_history.db
Binary file not shown.
38 changes: 38 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import sys
import logging
import cherrypy
from controller.controller import ConverterAPI


def setup_logging():
logging.basicConfig(
filename='utilities/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)
Empty file added models/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions models/sequence.py
Original file line number Diff line number Diff line change
@@ -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
Empty file added services/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions services/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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]
lst = self.convert(values)
return self.process_measurements(lst)

@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 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
# 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"))
Empty file added services/test/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions services/test/test_conversion.py
Original file line number Diff line number Diff line change
@@ -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"), [])
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."""
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()
Empty file added utilities/__init__.py
Empty file.
Binary file added utilities/converter.db
Binary file not shown.
35 changes: 35 additions & 0 deletions utilities/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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
Loading