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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
*.log
*.db
**/__pycache__
50 changes: 50 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -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 <enter_port>```

### Usage
- Call the API using the following URLs:

"http://localhost:8080/convert?input_str=<enter_string_here>


- 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
Empty file added controller/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions controller/measurement_controller.py
Original file line number Diff line number Diff line change
@@ -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()}

Empty file added db_utils/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions db_utils/db_crud_operations.py
Original file line number Diff line number Diff line change
@@ -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 []
11 changes: 11 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -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(), '/')
Empty file added models/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions models/processed_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class ProcessResult:
def __init__(self) -> None:
self.given_seq: str = ""
self.generated_seq: list[int] = []
Empty file added services/__init__.py
Empty file.
80 changes: 80 additions & 0 deletions services/measurement_service.py
Original file line number Diff line number Diff line change
@@ -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
Empty file added services/test/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions services/test/test_measurement_service.py
Original file line number Diff line number Diff line change
@@ -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)