From d344cc8b9414ac935bd725ec016977b3e1b76455 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sat, 27 Apr 2024 19:04:06 +0400 Subject: [PATCH 01/11] Added Sequence Processing Logic --- .gitignore | 8 +++ main_app.py | 41 +++++++++++++++ src/controllers/SequenceController.py | 25 +++++++++ src/controllers/__init__.py | 0 src/models/Sequence.py | 27 ++++++++++ src/models/__init__.py | 0 src/services/SequenceService.py | 74 +++++++++++++++++++++++++++ src/services/__init__.py | 0 8 files changed, 175 insertions(+) create mode 100644 .gitignore create mode 100644 main_app.py create mode 100644 src/controllers/SequenceController.py create mode 100644 src/controllers/__init__.py create mode 100644 src/models/Sequence.py create mode 100644 src/models/__init__.py create mode 100644 src/services/SequenceService.py create mode 100644 src/services/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09d6ca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +*.png +*.jpg +*.gif +*.txt +*.csv +*.pdf +**/__pycache__/ diff --git a/main_app.py b/main_app.py new file mode 100644 index 0000000..e2a3d94 --- /dev/null +++ b/main_app.py @@ -0,0 +1,41 @@ +import cherrypy +import os + + +from src.controllers.SequenceController import SequenceRecordsV1 + + +class Root(object): + @cherrypy.expose() + def index(self): + """ + This function is going to return index.html files present in the static directory. + We don't need to implement it since it configured and cherrypy is going to handle it. + """ + pass + + +cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 8080}) + +# Mount the application +cherrypy.tree.mount(SequenceRecordsV1(), '/convert-measurements', { + '/': { + 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), # Use method-based dispatching + 'tools.sessions.on': False, + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [('Content-Type', 'text/plain')] + } +}) + +cherrypy.tree.mount(Root(), '/', { + '/': { + 'tools.staticdir.root': os.path.abspath(os.path.dirname(__file__)), + 'tools.staticdir.on': True, + 'tools.staticdir.dir': 'static', + 'tools.staticdir.index': 'index.html', + } +}) + +# Start the CherryPy server +cherrypy.engine.start() +cherrypy.engine.block() diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py new file mode 100644 index 0000000..703731d --- /dev/null +++ b/src/controllers/SequenceController.py @@ -0,0 +1,25 @@ +import cherrypy + +from src.services.SequenceService import SequenceService + + +service = SequenceService() + +class SequenceRecordsV1(): + exposed = True + + + @cherrypy.tools.json_out() + def GET(self, input: str = ""): + """ + Handles the GET request and return a JSON response. + """ + + try: + service.get_sequence(input) + res_msg = {"status": "success", "err_msg": "", "result": service.process_sequence()} + except: + res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + + + return res_msg diff --git a/src/controllers/__init__.py b/src/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/Sequence.py b/src/models/Sequence.py new file mode 100644 index 0000000..b362817 --- /dev/null +++ b/src/models/Sequence.py @@ -0,0 +1,27 @@ + +class Sequence: + + def __init__(self): + self.value = "" + + def set_value(self, value): + self.value = value + + def get_value_as_str(self): + return str(self.value) + + def is_valid(self): + # Implement validation logic + steps = 0 + + value = "" + + + for index in value: + pass + + + if steps == 0: + return True + else: + return False diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py new file mode 100644 index 0000000..1e09da5 --- /dev/null +++ b/src/services/SequenceService.py @@ -0,0 +1,74 @@ +from src.models.Sequence import Sequence + +sequence = Sequence() + +class SequenceService: + # def __init__(self): + # self.sequence_history = SequenceHistory() + + def get_sequence(self, str_representation): + sequence.set_value(str_representation) + + if sequence.is_valid(): + pass + else: + raise Exception + + def process_sequence(self, sequence = sequence.get_value_as_str()): + # print("sequence = " + sequence) + + sequence = "aa" # [1] + # sequence = "abbcc" # [2, 6] + # sequence = "dz_a_aazzaaa" # [28, 53, 1] + # sequence = "a_" # [0] + # sequence = "abcdabcdab" # [2, 7, 7] + # sequence = "abcdabcdab_" # [2, 7, 7, 0] + # sequence = "zdaaaaaaaabaaaaaaaabaaaaaaaabbaa" # [34] + # sequence = "zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_" # [26] + # sequence = "za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa" # [40, 1] + + # sequence = "_" # [0] + # sequence = "_ad" # [0] + # sequence = "a_" # [0] + # sequence = "_zzzb" # [0] + # sequence = "__________" # [0] + + 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} + + output = [] + index = 0 + is_counting = False + while index < len(sequence): + if is_counting: + if steps != 0: + if sequence[index] == 'z': + sum = sum + encoder[sequence[index]] + else: + sum = sum + encoder[sequence[index]] + steps -= 1 + if (index + 1) == len(sequence): + output.append(sum) + else: + if steps == 0: + index -= 1 + output.append(sum) + is_counting = False + else: + sum = 0 + steps = encoder[sequence[index]] + + if steps == 0 and index + 1 == len(sequence): + output.append(sum) + else: + if sequence[index] == 'z': + steps = steps + encoder[sequence[index + 1]] + if sequence[index + 1] == 'z': + steps = steps + encoder[sequence[index + 2]] + index += 1 + index += 1 + is_counting = True + index += 1 + + return output \ No newline at end of file diff --git a/src/services/__init__.py b/src/services/__init__.py new file mode 100644 index 0000000..e69de29 From a4f5712727d2a075eee3a49394900c042adf94fa Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sat, 27 Apr 2024 19:07:47 +0400 Subject: [PATCH 02/11] Adjust Imports --- src/controllers/SequenceController.py | 2 +- src/services/SequenceService.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index 703731d..ed36c39 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -1,6 +1,6 @@ import cherrypy -from src.services.SequenceService import SequenceService +from ..services import SequenceService service = SequenceService() diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index 1e09da5..b1d9c31 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -1,4 +1,4 @@ -from src.models.Sequence import Sequence +from ..models import Sequence sequence = Sequence() From 674c87652bec6ed0e44dedf4cd1a3cb9476c9d47 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sat, 27 Apr 2024 19:37:11 +0400 Subject: [PATCH 03/11] fixed OOP implementation --- main_app.py | 2 +- src/controllers/SequenceController.py | 19 +++++++++++-------- src/services/SequenceService.py | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/main_app.py b/main_app.py index e2a3d94..8a0760d 100644 --- a/main_app.py +++ b/main_app.py @@ -15,7 +15,7 @@ def index(self): pass -cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 8080}) +cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 8888}) # Mount the application cherrypy.tree.mount(SequenceRecordsV1(), '/convert-measurements', { diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index ed36c39..ae22892 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -1,13 +1,13 @@ import cherrypy -from ..services import SequenceService +from ..services.SequenceService import SequenceService -service = SequenceService() - class SequenceRecordsV1(): exposed = True + def __init__(self): + self.sequence_service = SequenceService() @cherrypy.tools.json_out() def GET(self, input: str = ""): @@ -15,11 +15,14 @@ def GET(self, input: str = ""): Handles the GET request and return a JSON response. """ - try: - service.get_sequence(input) - res_msg = {"status": "success", "err_msg": "", "result": service.process_sequence()} - except: - res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + + self.sequence_service.get_sequence(input) + res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} + # try: + # self.get_sequence(input) + # res_msg = {"status": "success", "err_msg": "", "result": self.process_sequence()} + # except: + # res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} return res_msg diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index b1d9c31..2da8f3f 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -1,23 +1,26 @@ -from ..models import Sequence +from ..models.Sequence import Sequence -sequence = Sequence() class SequenceService: - # def __init__(self): - # self.sequence_history = SequenceHistory() + + def __init__(self): + self.sequence = Sequence() + # self.sequence_history = SequenceHistory() def get_sequence(self, str_representation): - sequence.set_value(str_representation) + self.sequence.set_value(str_representation) - if sequence.is_valid(): + if self.sequence.is_valid(): pass else: raise Exception - def process_sequence(self, sequence = sequence.get_value_as_str()): + def process_sequence(self): + sequence = self.sequence.get_value_as_str() + # print("sequence = " + sequence) - sequence = "aa" # [1] + # sequence = "aa" # [1] # sequence = "abbcc" # [2, 6] # sequence = "dz_a_aazzaaa" # [28, 53, 1] # sequence = "a_" # [0] From bca89ce46e683cd60fac81162facea1f33404226 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sat, 27 Apr 2024 20:22:44 +0400 Subject: [PATCH 04/11] Implemented Sequence Validation --- src/models/Sequence.py | 68 +++++++++++++++++++++++++++------ src/services/SequenceService.py | 4 +- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/models/Sequence.py b/src/models/Sequence.py index b362817..25a5932 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -2,26 +2,72 @@ class Sequence: def __init__(self): - self.value = "" + self.sequence = "" - def set_value(self, value): - self.value = value + def set_sequence(self, sequence): + self.value = sequence - def get_value_as_str(self): - return str(self.value) + def get_sequence_as_str(self): + return str(self.sequence) - def is_valid(self): - # Implement validation logic - steps = 0 + 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] - value = "" + def is_valid(self): + sequence = self.sequence + steps = 0 + index = 0 + is_counting = False + while index < len(sequence): + if is_counting: + steps = steps - 1 + if sequence[index] == 'z': + steps = steps + 1 + if steps == 0: + is_counting = False + else: + 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 - for index in value: - pass + index += 1 if steps == 0: return True else: return False + + + + + + + + + + # value = "aa" # [1] + # value = "abbcc" # [2, 6] + # value = "dz_a_aazzaaa" # [28, 53, 1] + # value = "a_" # [0] + # value = "abcdabcdab" # [2, 7, 7] + # value = "abcdabcdab_" # [2, 7, 7, 0] + # value = "zdaaaaaaaabaaaaaaaabaaaaaaaabbaa" # [34] + # value = "zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_" # [26] + # value = "za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa" # [40, 1] + + # value = "_" # [0] + # value = "_ad" # [0] + # value = "a_" # [0] + # value = "_zzzb" # [0] + # value = "__________" # [0] \ No newline at end of file diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index 2da8f3f..b08501c 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -8,7 +8,7 @@ def __init__(self): # self.sequence_history = SequenceHistory() def get_sequence(self, str_representation): - self.sequence.set_value(str_representation) + self.sequence.set_sequence(str_representation) if self.sequence.is_valid(): pass @@ -16,7 +16,7 @@ def get_sequence(self, str_representation): raise Exception def process_sequence(self): - sequence = self.sequence.get_value_as_str() + sequence = self.sequence.get_sequence_as_str() # print("sequence = " + sequence) From 3d4feed26361bd410f9dc9815e2e1ae6f9f3f11c Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sat, 27 Apr 2024 20:29:37 +0400 Subject: [PATCH 05/11] fixed bug with SequenceService --- src/models/Sequence.py | 2 +- src/services/SequenceService.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/models/Sequence.py b/src/models/Sequence.py index 25a5932..27259b6 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -5,7 +5,7 @@ def __init__(self): self.sequence = "" def set_sequence(self, sequence): - self.value = sequence + self.sequence = sequence def get_sequence_as_str(self): return str(self.sequence) diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index b08501c..e6fd334 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -4,19 +4,19 @@ class SequenceService: def __init__(self): - self.sequence = Sequence() + self.sequence_manager = Sequence() # self.sequence_history = SequenceHistory() def get_sequence(self, str_representation): - self.sequence.set_sequence(str_representation) + self.sequence_manager.set_sequence(str_representation) - if self.sequence.is_valid(): + if self.sequence_manager.is_valid(): pass else: - raise Exception + raise Exception def process_sequence(self): - sequence = self.sequence.get_sequence_as_str() + sequence = self.sequence_manager.get_sequence_as_str() # print("sequence = " + sequence) From 932d0a8da655080986a7d050905875e4cbd5dbc7 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sun, 28 Apr 2024 08:51:45 +0400 Subject: [PATCH 06/11] added Special Case when string starts with "_" --- src/controllers/SequenceController.py | 16 ++++++++-------- src/models/Sequence.py | 2 +- src/services/SequenceService.py | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index ae22892..933ef91 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -15,14 +15,14 @@ def GET(self, input: str = ""): Handles the GET request and return a JSON response. """ - - self.sequence_service.get_sequence(input) - res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - # try: - # self.get_sequence(input) - # res_msg = {"status": "success", "err_msg": "", "result": self.process_sequence()} - # except: - # res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + # self.sequence_service.get_sequence(input) + # res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} + + try: + self.sequence_service.get_sequence(input) + res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} + except: + res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} return res_msg diff --git a/src/models/Sequence.py b/src/models/Sequence.py index 27259b6..fb6e673 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -42,7 +42,7 @@ def is_valid(self): index += 1 - + # Probably where the validation bug is if steps == 0: return True else: diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index e6fd334..2d35a79 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -36,10 +36,10 @@ def process_sequence(self): # sequence = "_zzzb" # [0] # sequence = "__________" # [0] - 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} - + # Special Case + if sequence[0] == "_": + return [0] + output = [] index = 0 is_counting = False @@ -47,9 +47,9 @@ def process_sequence(self): if is_counting: if steps != 0: if sequence[index] == 'z': - sum = sum + encoder[sequence[index]] + sum = sum + self.sequence_manager.encoder(sequence[index]) else: - sum = sum + encoder[sequence[index]] + sum = sum + self.sequence_manager.encoder(sequence[index]) steps -= 1 if (index + 1) == len(sequence): output.append(sum) @@ -60,15 +60,15 @@ def process_sequence(self): is_counting = False else: sum = 0 - steps = encoder[sequence[index]] + steps = self.sequence_manager.encoder(sequence[index]) if steps == 0 and index + 1 == len(sequence): output.append(sum) else: if sequence[index] == 'z': - steps = steps + encoder[sequence[index + 1]] + steps = steps + self.sequence_manager.encoder(sequence[index + 1]) if sequence[index + 1] == 'z': - steps = steps + encoder[sequence[index + 2]] + steps = steps + self.sequence_manager.encoder(sequence[index + 2]) index += 1 index += 1 is_counting = True From ca895623426dcfc5db2c8656d666aeabe05690a4 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sun, 28 Apr 2024 09:49:09 +0400 Subject: [PATCH 07/11] added new function to add numbers to list SRP + Special Case for "_" in GET function --- src/controllers/SequenceController.py | 16 +++++++---- src/models/Sequence.py | 31 ++------------------- src/services/SequenceService.py | 40 ++++++++------------------- 3 files changed, 24 insertions(+), 63 deletions(-) diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index 933ef91..ba304ba 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -15,14 +15,18 @@ def GET(self, input: str = ""): Handles the GET request and return a JSON response. """ - # self.sequence_service.get_sequence(input) - # res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - - try: + # Special Case + if input[0] == "_": + res_msg = {"status": "success", "err_msg": "", "result": [0]} + else: self.sequence_service.get_sequence(input) res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - except: - res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + + # try: + # self.sequence_service.get_sequence(input) + # res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} + # except: + # res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} return res_msg diff --git a/src/models/Sequence.py b/src/models/Sequence.py index fb6e673..e2be9cb 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -24,13 +24,13 @@ def is_valid(self): index = 0 is_counting = False while index < len(sequence): - if is_counting: + 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: + else: # IF at the start of cycle steps = self.encoder(sequence[index]) if sequence[index] == 'z': steps = steps + self.encoder(sequence[index + 1]) @@ -42,32 +42,7 @@ def is_valid(self): index += 1 - # Probably where the validation bug is if steps == 0: return True else: - return False - - - - - - - - - - # value = "aa" # [1] - # value = "abbcc" # [2, 6] - # value = "dz_a_aazzaaa" # [28, 53, 1] - # value = "a_" # [0] - # value = "abcdabcdab" # [2, 7, 7] - # value = "abcdabcdab_" # [2, 7, 7, 0] - # value = "zdaaaaaaaabaaaaaaaabaaaaaaaabbaa" # [34] - # value = "zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_" # [26] - # value = "za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa" # [40, 1] - - # value = "_" # [0] - # value = "_ad" # [0] - # value = "a_" # [0] - # value = "_zzzb" # [0] - # value = "__________" # [0] \ No newline at end of file + return False \ No newline at end of file diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index 2d35a79..e267d30 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -5,6 +5,7 @@ class SequenceService: def __init__(self): self.sequence_manager = Sequence() + self.result = [] # self.sequence_history = SequenceHistory() def get_sequence(self, str_representation): @@ -13,34 +14,15 @@ def get_sequence(self, str_representation): if self.sequence_manager.is_valid(): pass else: - raise Exception + 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() - - # print("sequence = " + sequence) - - # sequence = "aa" # [1] - # sequence = "abbcc" # [2, 6] - # sequence = "dz_a_aazzaaa" # [28, 53, 1] - # sequence = "a_" # [0] - # sequence = "abcdabcdab" # [2, 7, 7] - # sequence = "abcdabcdab_" # [2, 7, 7, 0] - # sequence = "zdaaaaaaaabaaaaaaaabaaaaaaaabbaa" # [34] - # sequence = "zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_" # [26] - # sequence = "za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa" # [40, 1] - - # sequence = "_" # [0] - # sequence = "_ad" # [0] - # sequence = "a_" # [0] - # sequence = "_zzzb" # [0] - # sequence = "__________" # [0] - - # Special Case - if sequence[0] == "_": - return [0] - - output = [] + self.result = [] index = 0 is_counting = False while index < len(sequence): @@ -52,18 +34,18 @@ def process_sequence(self): sum = sum + self.sequence_manager.encoder(sequence[index]) steps -= 1 if (index + 1) == len(sequence): - output.append(sum) + self.append_num_to_list(sum) else: if steps == 0: index -= 1 - output.append(sum) + self.append_num_to_list(sum) is_counting = False else: sum = 0 steps = self.sequence_manager.encoder(sequence[index]) if steps == 0 and index + 1 == len(sequence): - output.append(sum) + self.append_num_to_list(sum) else: if sequence[index] == 'z': steps = steps + self.sequence_manager.encoder(sequence[index + 1]) @@ -74,4 +56,4 @@ def process_sequence(self): is_counting = True index += 1 - return output \ No newline at end of file + return self.result \ No newline at end of file From f900a995b9ff32e77a6a646cfe80bd191f594998 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Sun, 28 Apr 2024 14:30:23 +0400 Subject: [PATCH 08/11] added DB implementation to save records + PORT as arguement --- .gitignore | 9 +---- main_app.py | 58 ++++++++++----------------- src/controllers/SequenceController.py | 24 ++++++++++- src/models/Sequence.py | 1 + src/services/SequenceHistory.py | 49 ++++++++++++++++++++++ src/services/SequenceService.py | 4 +- 6 files changed, 99 insertions(+), 46 deletions(-) create mode 100644 src/services/SequenceHistory.py diff --git a/.gitignore b/.gitignore index 09d6ca6..55ded5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ -.idea/ -*.png -*.jpg -*.gif -*.txt -*.csv -*.pdf +*.log +*.db **/__pycache__/ diff --git a/main_app.py b/main_app.py index 8a0760d..e0c4328 100644 --- a/main_app.py +++ b/main_app.py @@ -1,41 +1,27 @@ import cherrypy -import os - +import logging +import argparse from src.controllers.SequenceController import SequenceRecordsV1 +def parse_command_line(): + parser = argparse.ArgumentParser(description="CherryPy Server Configuration") + parser.add_argument('port', nargs='?', type=int, default=8080, help='Port on which the server will run') + return parser.parse_args() + +if __name__ == '__main__': + # Parse command line arguments + args = parse_command_line() + + # Configure LOGGING to file + logging.basicConfig(filename='error_log.log', level=logging.CRITICAL, + format='%(asctime)s:%(levelname)s:%(message)s') + + + cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': args.port}) + cherrypy.tree.mount(SequenceRecordsV1(), '/') + -class Root(object): - @cherrypy.expose() - def index(self): - """ - This function is going to return index.html files present in the static directory. - We don't need to implement it since it configured and cherrypy is going to handle it. - """ - pass - - -cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 8888}) - -# Mount the application -cherrypy.tree.mount(SequenceRecordsV1(), '/convert-measurements', { - '/': { - 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), # Use method-based dispatching - 'tools.sessions.on': False, - 'tools.response_headers.on': True, - 'tools.response_headers.headers': [('Content-Type', 'text/plain')] - } -}) - -cherrypy.tree.mount(Root(), '/', { - '/': { - 'tools.staticdir.root': os.path.abspath(os.path.dirname(__file__)), - 'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static', - 'tools.staticdir.index': 'index.html', - } -}) - -# Start the CherryPy server -cherrypy.engine.start() -cherrypy.engine.block() + # Start the CherryPy server + cherrypy.engine.start() + cherrypy.engine.block() diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index ba304ba..8a1421e 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -1,16 +1,18 @@ import cherrypy from ..services.SequenceService import SequenceService +from ..services.SequenceHistory import SequenceHistory class SequenceRecordsV1(): - exposed = True def __init__(self): self.sequence_service = SequenceService() + self.sequence_history = SequenceHistory() + @cherrypy.expose() @cherrypy.tools.json_out() - def GET(self, input: str = ""): + def convert_measurements(self, input: str = ""): """ Handles the GET request and return a JSON response. """ @@ -18,15 +20,33 @@ def GET(self, input: str = ""): # Special Case if input[0] == "_": res_msg = {"status": "success", "err_msg": "", "result": [0]} + + self.sequence_history.insert_data(input) + self.sequence_history.close() else: + self.sequence_service.get_sequence(input) res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} + self.sequence_history.insert_data(input) + self.sequence_history.close() + # try: # self.sequence_service.get_sequence(input) # res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} # except: # res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + return res_msg + + @cherrypy.expose() + @cherrypy.tools.json_out() + def get_all(self): + """ + Handles the GET all records from Database and returns a JSON response. + """ + + res_msg = {"status": "success", "err_msg": "", "result": self.sequence_history.fetch_data()} + self.sequence_history.close() return res_msg diff --git a/src/models/Sequence.py b/src/models/Sequence.py index e2be9cb..5dffa5d 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -4,6 +4,7 @@ class Sequence: def __init__(self): self.sequence = "" + def set_sequence(self, sequence): self.sequence = sequence diff --git a/src/services/SequenceHistory.py b/src/services/SequenceHistory.py new file mode 100644 index 0000000..06547e5 --- /dev/null +++ b/src/services/SequenceHistory.py @@ -0,0 +1,49 @@ +# from ..models.Sequence import Sequence +import os +import sqlite3 +import time + +class SequenceHistory: + def __init__(self): + self.db_name = "sequence_history.db" + self.conn = sqlite3.connect(self.db_name, check_same_thread=False) + self.cursor = self.conn.cursor() + self.create_table() + + + def create_table(self): + query = ''' + CREATE TABLE IF NOT EXISTS history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datetime TEXT, + input TEXT + ) + ''' + try: + self.cursor.execute(query) + self.conn.commit() + except sqlite3.Error as e: + print(f"An error occurred: {e}") + + def insert_data(self, input = "test"): + insert_query = 'INSERT INTO history (datetime, input) VALUES (?, ?)' + data_to_insert = (str(time.time()), input) + + try: + self.cursor.execute(insert_query, data_to_insert) + self.conn.commit() + except sqlite3.Error as e: + print(f"An error occurred: {e}") + + def fetch_data(self): + select_query = 'SELECT * FROM history' + + try: + self.cursor.execute(select_query) + return self.cursor.fetchall() + except sqlite3.Error as e: + print(f"An error occurred: {e}") + return None + + def close(self): + self.conn.close() \ No newline at end of file diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index e267d30..5b88f41 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -1,12 +1,14 @@ from ..models.Sequence import Sequence +from ..services.SequenceHistory import SequenceHistory class SequenceService: def __init__(self): self.sequence_manager = Sequence() + self.sequence_history = SequenceHistory() self.result = [] - # self.sequence_history = SequenceHistory() + def get_sequence(self, str_representation): self.sequence_manager.set_sequence(str_representation) From 0e00005c47b897e1ccbaa828d71c068b923b6d6a Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Wed, 1 May 2024 10:54:04 +0400 Subject: [PATCH 09/11] absolute imports + added status in databse + formatted fetched epoch + validation check is not in process function + added unit tests --- src/__init__.py | 0 src/controllers/SequenceController.py | 26 ++++++------------- src/services/SequenceHistory.py | 29 +++++++++++++-------- src/services/SequenceService.py | 18 ++++++++------ src/services/tests/__init__.py | 0 src/services/tests/unitTests.py | 36 +++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/services/tests/__init__.py create mode 100644 src/services/tests/unitTests.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index 8a1421e..f549703 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -1,7 +1,7 @@ import cherrypy -from ..services.SequenceService import SequenceService -from ..services.SequenceHistory import SequenceHistory +from src.services.SequenceService import SequenceService +from src.services.SequenceHistory import SequenceHistory class SequenceRecordsV1(): @@ -17,25 +17,16 @@ def convert_measurements(self, input: str = ""): Handles the GET request and return a JSON response. """ - # Special Case - if input[0] == "_": - res_msg = {"status": "success", "err_msg": "", "result": [0]} - - self.sequence_history.insert_data(input) - self.sequence_history.close() - else: - + try: self.sequence_service.get_sequence(input) res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - self.sequence_history.insert_data(input) - self.sequence_history.close() + self.sequence_history.insert_data("SUCCESS", input) + + except: + self.sequence_history.insert_data("FAILED", input) + res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} - # try: - # self.sequence_service.get_sequence(input) - # res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - # except: - # res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} return res_msg @@ -47,6 +38,5 @@ def get_all(self): """ res_msg = {"status": "success", "err_msg": "", "result": self.sequence_history.fetch_data()} - self.sequence_history.close() return res_msg diff --git a/src/services/SequenceHistory.py b/src/services/SequenceHistory.py index 06547e5..5a0978a 100644 --- a/src/services/SequenceHistory.py +++ b/src/services/SequenceHistory.py @@ -1,7 +1,6 @@ -# from ..models.Sequence import Sequence -import os import sqlite3 import time +import datetime class SequenceHistory: def __init__(self): @@ -16,7 +15,8 @@ def create_table(self): CREATE TABLE IF NOT EXISTS history ( id INTEGER PRIMARY KEY AUTOINCREMENT, datetime TEXT, - input TEXT + input TEXT, + status TEXT ) ''' try: @@ -25,9 +25,9 @@ def create_table(self): except sqlite3.Error as e: print(f"An error occurred: {e}") - def insert_data(self, input = "test"): - insert_query = 'INSERT INTO history (datetime, input) VALUES (?, ?)' - data_to_insert = (str(time.time()), input) + def insert_data(self, status, input = "test"): + insert_query = 'INSERT INTO history (datetime, input, status) VALUES (?, ?, ?)' + data_to_insert = (str(time.time()), input, status) try: self.cursor.execute(insert_query, data_to_insert) @@ -39,11 +39,18 @@ def fetch_data(self): select_query = 'SELECT * FROM history' try: + # Fetch history from database and format it (epoch to human-readable time) self.cursor.execute(select_query) - return self.cursor.fetchall() + fetched_data = self.cursor.fetchall() + filtered_data = [] + for entry in fetched_data: + # Convert the timestamp to a datetime object and format it + timestamp = float(entry[1]) + readable_time = datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') + # Append the converted time and the rest of the data, excluding the ID + filtered_sublist = [readable_time, entry[2], entry[3]] + filtered_data.append(filtered_sublist) + return filtered_data except sqlite3.Error as e: print(f"An error occurred: {e}") - return None - - def close(self): - self.conn.close() \ No newline at end of file + return None \ No newline at end of file diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index 5b88f41..fd2cb53 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -1,5 +1,5 @@ -from ..models.Sequence import Sequence -from ..services.SequenceHistory import SequenceHistory +from src.models.Sequence import Sequence +from src.services.SequenceHistory import SequenceHistory class SequenceService: @@ -12,11 +12,6 @@ def __init__(self): def get_sequence(self, str_representation): self.sequence_manager.set_sequence(str_representation) - - if self.sequence_manager.is_valid(): - pass - else: - raise Exception("INVALID SEQUENCE") def append_num_to_list(self, number): self.result.append(number) @@ -24,6 +19,15 @@ def append_num_to_list(self, number): def process_sequence(self): sequence = self.sequence_manager.get_sequence_as_str() + + # Check if input is valid + if sequence[0] == "_": + return [0] + elif self.sequence_manager.is_valid(): + pass + else: + raise Exception("INVALID SEQUENCE") + self.result = [] index = 0 is_counting = False diff --git a/src/services/tests/__init__.py b/src/services/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/services/tests/unitTests.py b/src/services/tests/unitTests.py new file mode 100644 index 0000000..14df647 --- /dev/null +++ b/src/services/tests/unitTests.py @@ -0,0 +1,36 @@ +import unittest + +from ..SequenceService import SequenceService + + +CURRENT_VS_EXPECTED_VALUE = ( + ("aa", [1]), + ("abbcc", [2, 6]), + ("dz_a_aazzaaa", [28, 53, 1]), + ("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_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]) +) + + +class TestSequence(unittest.TestCase): + + def test_sequence(self): + for test_string, expected in CURRENT_VS_EXPECTED_VALUE: + sequence_service = SequenceService() + sequence_service.get_sequence(test_string) + result = sequence_service.process_sequence() + print(f"result {result} expected {expected}") + self.assertEqual(result, expected) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 6c1509a4694d08e8006780870f917267dca56b40 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Wed, 1 May 2024 11:07:52 +0400 Subject: [PATCH 10/11] Organized code and added comments --- src/controllers/SequenceController.py | 10 +++++----- src/models/Sequence.py | 2 ++ src/services/SequenceHistory.py | 1 + src/services/SequenceService.py | 6 ++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index f549703..e8e16ec 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -14,18 +14,18 @@ def __init__(self): @cherrypy.tools.json_out() def convert_measurements(self, input: str = ""): """ - Handles the GET request and return a JSON response. + Handles the GET request and return a JSON response. The API will convert measurement input + string from a sequence of characters into a result list of the total values of measured + inflows for each package. """ try: self.sequence_service.get_sequence(input) res_msg = {"status": "success", "err_msg": "", "result": self.sequence_service.process_sequence()} - self.sequence_history.insert_data("SUCCESS", input) - - except: - self.sequence_history.insert_data("FAILED", input) + except: # If Invalid Sequence res_msg = {"status": "fail", "err_msg": "invalid sequence", "result": []} + self.sequence_history.insert_data("FAILED", input) return res_msg diff --git a/src/models/Sequence.py b/src/models/Sequence.py index 5dffa5d..8fb30fb 100644 --- a/src/models/Sequence.py +++ b/src/models/Sequence.py @@ -11,6 +11,7 @@ def set_sequence(self, sequence): def get_sequence_as_str(self): return str(self.sequence) + # Takes character as input and returns it's numerical representation. 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, @@ -18,6 +19,7 @@ def encoder(self, character): return encoder[character] + # Logic to check if input sequence is valid. def is_valid(self): sequence = self.sequence diff --git a/src/services/SequenceHistory.py b/src/services/SequenceHistory.py index 5a0978a..6582015 100644 --- a/src/services/SequenceHistory.py +++ b/src/services/SequenceHistory.py @@ -2,6 +2,7 @@ import time import datetime +# Manages Database Operations class SequenceHistory: def __init__(self): self.db_name = "sequence_history.db" diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index fd2cb53..fb87bbd 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -9,14 +9,16 @@ def __init__(self): self.sequence_history = SequenceHistory() self.result = [] - + # Receives the input sequenece from the API endpoint and stores it as a Sequence object. def get_sequence(self, str_representation): self.sequence_manager.set_sequence(str_representation) - + + # Adds integer to result list (sum of each cycle) and returns it. def append_num_to_list(self, number): self.result.append(number) return self.result + # Processes the Sequence object and returns the result list. def process_sequence(self): sequence = self.sequence_manager.get_sequence_as_str() From 4cc639a21706ac872bbad87be19340969a0e5d44 Mon Sep 17 00:00:00 2001 From: Ziyad Al Hashar <71534@omantel.om> Date: Wed, 1 May 2024 11:35:01 +0400 Subject: [PATCH 11/11] added README.md --- READMe.md | 49 +++++++++++++++++++++++++++ src/controllers/SequenceController.py | 2 +- src/services/SequenceService.py | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 READMe.md diff --git a/READMe.md b/READMe.md new file mode 100644 index 0000000..0a4da7a --- /dev/null +++ b/READMe.md @@ -0,0 +1,49 @@ +# 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/ziyadabd/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_measurements= + + +- Get history of all conversions: + + Default: http://localhost:8080/get_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 ziyadalhashar@gmail.com + +## Acknowledgements +Project by Ziyad Al Hashar \ No newline at end of file diff --git a/src/controllers/SequenceController.py b/src/controllers/SequenceController.py index e8e16ec..01af28c 100644 --- a/src/controllers/SequenceController.py +++ b/src/controllers/SequenceController.py @@ -32,7 +32,7 @@ def convert_measurements(self, input: str = ""): @cherrypy.expose() @cherrypy.tools.json_out() - def get_all(self): + def get_history(self): """ Handles the GET all records from Database and returns a JSON response. """ diff --git a/src/services/SequenceService.py b/src/services/SequenceService.py index fb87bbd..aa2f7b5 100644 --- a/src/services/SequenceService.py +++ b/src/services/SequenceService.py @@ -23,7 +23,7 @@ def process_sequence(self): sequence = self.sequence_manager.get_sequence_as_str() # Check if input is valid - if sequence[0] == "_": + if sequence[0] == "_": # Special Case for "_" return [0] elif self.sequence_manager.is_valid(): pass