diff --git a/DB/NEW_KT_DB/DataAccess/ObjectManager.py b/DB/NEW_KT_DB/DataAccess/ObjectManager.py deleted file mode 100644 index b75b2373..00000000 --- a/DB/NEW_KT_DB/DataAccess/ObjectManager.py +++ /dev/null @@ -1,110 +0,0 @@ -from typing import Dict, Any, Optional -import json -import sqlite3 -from DBManager import DBManager - -class ObjectManager: - def __init__(self, db_file: str): - '''Initialize ObjectManager with the database connection.''' - self.db_manager = DBManager(db_file) - - - def create_management_table(self, object_name, table_structure='default', pk_column_data_type='INTEGER'): - - table_name = self._convert_object_name_to_management_table_name(object_name) - pk_constraint = ' AUTOINCREMENT' if pk_column_data_type == 'INTEGER' else '' - - if table_structure == 'default': - table_structure = f'object_id {pk_column_data_type} PRIMARY KEY {pk_constraint},type_object TEXT NOT NULL,metadata TEXT NOT NULL' - self.db_manager.create_table(table_name, table_structure) - - - def _insert_object_to_management_table(self, table_name, object_info, columns_to_populate=None): - - if columns_to_populate is None: - self.db_manager.insert_data_into_table(table_name, object_info) - else: - self.db_manager.insert_data_into_table(table_name, object_info, columns_to_populate) - - - def _update_object_in_management_table_by_criteria(self, table_name, updates, criteria): - self.db_manager.update_records_in_table(table_name, updates, criteria) - - - def _delete_object_from_management_table(self, table_name, criteria) -> None: - '''Delete an object from the database.''' - self.db_manager.delete_data_from_table(table_name, criteria) - - - def _convert_object_name_to_management_table_name(self,object_name): - return f'mng_{object_name}s' - - - def save_in_memory(self, object_name, object_info, columns=None): - # insert object info into management table mng_{object_name}s - # for exmple: object db_instance will be saved in table mng_db_instances - table_name = self._convert_object_name_to_management_table_name(object_name) - - if not self._is_management_table_exist(object_name): - self.create_management_table(object_name) - - if columns is None: - self._insert_object_to_management_table(table_name, object_info) - else: - self._insert_object_to_management_table(table_name, object_info, columns) - - - def _is_management_table_exist(self, object_name): - - table_name = self._convert_object_name_to_management_table_name(object_name) - return self.db_manager.is_table_exist(table_name) - - - def delete_from_memory_by_criteria(self, object_name:str, criteria:str): - - table_name = self._convert_object_name_to_management_table_name(object_name) - - self._delete_object_from_management_table(table_name, criteria) - - - def delete_from_memory_by_pk(self, object_name:str, pk_column:str, pk_value:str): - - criteria = f"{pk_column} = '{pk_value}'" - - table_name = self._convert_object_name_to_management_table_name(object_name) - - self._delete_object_from_management_table(table_name, criteria) - - - def update_in_memory(self, object_name, updates, criteria): - - table_name = self._convert_object_name_to_management_table_name(object_name) - self._update_object_in_management_table_by_criteria(table_name, updates, criteria) - - - def get_from_memory(self, object_name, columns=None, criteria=None): - """get records from memory by criteria or id""" - table_name = self._convert_object_name_to_management_table_name(object_name) - - if columns is None and criteria is None: - return self.db_manager.get_data_from_table(table_name) - elif columns is None: - return self.db_manager.get_data_from_table(table_name, criteria=criteria) - elif criteria is None: - return self.db_manager.get_data_from_table(table_name, columns) - else: - return self.db_manager.get_data_from_table(table_name, columns, criteria) - - - def get_all_objects_from_memory(self, object_name): - table_name = self._convert_object_name_to_management_table_name(object_name) - return self.db_manager.get_all_data_from_table(table_name) - - @staticmethod - def convert_object_attributes_to_dictionary(**kwargs): - - dict = {} - for key, value in kwargs.items(): - dict[key] = value - - return dict diff --git a/Storage/ELTS/GenreSalseDailyELT.py b/Storage/ELTS/GenreSalseDailyELT.py new file mode 100644 index 00000000..494e3b6f --- /dev/null +++ b/Storage/ELTS/GenreSalseDailyELT.py @@ -0,0 +1,204 @@ +from pyspark.sql import SparkSession +import sqlite3 # Assuming you're using sqlite3 +import pandas as pd +from datetime import datetime +import os + +BASE_URL = "C:/Users/jimmy/Desktop/תמר לימודים יד/bootcamp/db_files" + + + + # create_table_time_query = """ + # CREATE TABLE IF NOT EXISTS tableTime AS + # SELECT ci.CustomerId, ci.created_at + # FROM customer_invoice_avg_elt ci + # WHERE EXISTS ( + # SELECT 1 + # FROM Customers_ELT ce + # JOIN Invoices_ELT i ON ce.CustomerId = i.CustomerId + # WHERE ((i.InvoiceDate > ? OR ce.created_at > ?) + # AND ci.customerId = ce.customerId) + # group by ce.CustomerId + # ) + # """ + + + # print("SELECT :",conn.execute("""SELECT c.CustomerId,c.created_at,i.InvoiceDate FROM Customers_ELT c + # JOIN Invoices_ELT i ON c.CustomerId = i.CustomerId + # where i.InvoiceDate > ? or c.created_at > ? + # group by c.CustomerId """ + # ,(latest_timestamp,latest_timestamp,)).fetchall()) + # # print("SELECT :",conn.execute("""SELECT InvoiceDate FROM Invoices_ELT + # # where InvoiceDate > ? """ + # # ,(latest_timestamp,)).fetchall()) + + # conn.execute(create_table_time_query, (latest_timestamp, latest_timestamp)) + # conn.commit() + # conn.execute("""DELETE FROM customer_invoice_avg_elt + # WHERE EXISTS ( + # SELECT 1 + # FROM tableTime t + # WHERE customer_invoice_avg_elt.CustomerId = t.CustomerId + # )""") + + # conn.commit() + # print("customer_invoice_avg_after_del:", conn.execute("SELECT * FROM 'customer_invoice_avg_elt'").fetchall()) + # print("tableTime:", conn.execute("SELECT * FROM 'tableTime'").fetchall()) + + # transform_query = """ + # INSERT INTO customer_invoice_avg_elt (CustomerId,InvoiceMonth, avg_spend, created_at, updated_at, updated_by) + # SELECT c.CustomerId, strftime('%m', i.InvoiceDate) AS InvoiceMonth, + # AVG(i.Total) AS avg_spend, + # COALESCE(t.created_at, CURRENT_DATE) AS created_at, + # CURRENT_DATE AS updated_at, + # 'process:shana_levovitz_' || CURRENT_DATE AS updated_by + # FROM Customers_ELT c + # RIGHT JOIN Invoices_ELT i ON c.CustomerId = i.CustomerId + # LEFT JOIN tableTime t ON c.CustomerId = t.CustomerId + # WHERE i.InvoiceDate > ? OR c.created_at > ? + # GROUP BY c.CustomerId, InvoiceMonth + # """ + # conn.execute(transform_query, (latest_timestamp, latest_timestamp)) + + # # Commit the changes to the database + # conn.execute("""DROP TABLE IF EXISTS tableTime""") + # conn.commit() + # print("customer_invoice_avg:", conn.execute("SELECT * FROM 'customer_invoice_avg_elt'").fetchall()) + # finally: + # # Step 3: Close the SQLite connection and stop Spark session + # conn.close() # Close the SQLite connection + # spark.stop() # Stop the Spark session + + + +def load(): + conn = sqlite3.connect(os.path.join(BASE_URL,"genres_table_ELT.db")) + try: + # EXTRACT (Loading CSVs from S3 or local storage) + # ----------------------------------------------- + genres = pd.read_csv(os.path.join(BASE_URL, "Genre.csv")) + tracks = pd.read_csv(os.path.join(BASE_URL, "Track.csv")) + invoice_lines = pd.read_csv(os.path.join(BASE_URL, "InvoiceLine.csv")) + # LOAD (Save the raw data into SQLite without transformation) + # ----------------------------------------------------------------------- + # Load raw data into SQLite + genres.to_sql("Genres", conn, if_exists="replace", index=False) + tracks.to_sql("Tracks", conn, if_exists="replace", index=False) + invoice_lines.to_sql("InvoiceLines", conn, if_exists="replace", index=False) + # TRANSFORM (Perform transformations with SQL queries using KT_DB functions) + # ------------------------------------------------------------------------- + last_update_date = """Select max(updated_at) from genre_sales_popularity_elt""" + latest_timestamp = conn.execute(last_update_date).fetchone()[0] + + # Handle case where no data exists yet (initial load) + if latest_timestamp is None: + latest_timestamp = '1900-01-01 00:00:00' + print("latest_timestamp:", latest_timestamp) + + transform_query = f""" + CREATE TABLE IF NOT EXISTS genre_sales_popularity_elt AS + SELECT + G.GenreId, + G.Name, + SUM(IL.UnitPrice * IL.Quantity) AS TotalSales, + AVG(IL.UnitPrice) AS AverageSalesPrice, + '{datetime.now()}' AS created_at, + '{datetime.now()}' AS updated_at, + 'Tamar Gavrielov' AS updated_by + FROM + Genres G + LEFT JOIN Tracks T ON G.GenreId = T.GenreId + LEFT JOIN InvoiceLines IL ON T.TrackId = IL.TrackId + WHERE G.Update_at > ? OR T.Update_at > ? + GROUP BY + G.GenreId, G.Name; + """ + # Execute the transformation query + conn.execute(transform_query) + # Commit the changes to the database + conn.commit() + finally: + # Close the SQLite connection and stop Spark session + conn.close() # Close the SQLite connection + + +def after_load_check_answers(): + conn = sqlite3.connect(os.path.join(BASE_URL,"genres_table_ELT.db")) + query = "SELECT * FROM genre_sales_popularity_elt" + result = pd.read_sql( + query, conn + ) # Use pandas to read the SQL result into a DataFrame + print(result.head()) + conn.close() + + +if __name__ == "__main__": + load() + after_load_check_answers() +from pyspark.sql import SparkSession +import sqlite3 # Assuming you're using sqlite3 +import pandas as pd +from datetime import datetime +import os + +BASE_URL = "C:/Users/jimmy/Desktop/תמר לימודים יד/bootcamp/db_files" + + +def load(): + conn = sqlite3.connect(os.path.join(BASE_URL,"genres_table_ELT.db")) + try: + # EXTRACT (Loading CSVs from S3 or local storage) + # ----------------------------------------------- + genres = pd.read_csv(os.path.join(BASE_URL, "Genre.csv")) + tracks = pd.read_csv(os.path.join(BASE_URL, "Track.csv")) + invoice_lines = pd.read_csv(os.path.join(BASE_URL, "InvoiceLine.csv")) + # LOAD (Save the raw data into SQLite without transformation) + # ----------------------------------------------------------------------- + # Load raw data into SQLite + genres.to_sql("Genres", conn, if_exists="replace", index=False) + tracks.to_sql("Tracks", conn, if_exists="replace", index=False) + invoice_lines.to_sql("InvoiceLines", conn, if_exists="replace", index=False) + # TRANSFORM (Perform transformations with SQL queries using KT_DB functions) + # ------------------------------------------------------------------------- + drop_query = """DROP TABLE IF EXISTS genre_sales_popularity_elt""" + conn.execute(drop_query) + conn.commit() + transform_query = f""" + CREATE TABLE genre_sales_popularity_elt AS + SELECT + G.GenreId, + G.Name, + SUM(IL.UnitPrice * IL.Quantity) AS TotalSales, + AVG(IL.UnitPrice) AS AverageSalesPrice, + '{datetime.now()}' AS created_at, + '{datetime.now()}' AS updated_at, + 'Tamar Gavrielov' AS updated_by + FROM + Genres G + LEFT JOIN Tracks T ON G.GenreId = T.GenreId + LEFT JOIN InvoiceLines IL ON T.TrackId = IL.TrackId + GROUP BY + G.GenreId, G.Name; + """ + # Execute the transformation query + conn.execute(transform_query) + # Commit the changes to the database + conn.commit() + finally: + # Close the SQLite connection and stop Spark session + conn.close() # Close the SQLite connection + + +def after_load_check_answers(): + conn = sqlite3.connect(os.path.join(BASE_URL,"genres_table_ELT.db")) + query = "SELECT * FROM genre_sales_popularity_elt" + result = pd.read_sql( + query, conn + ) # Use pandas to read the SQL result into a DataFrame + print(result.head()) + conn.close() + + +if __name__ == "__main__": + load() + after_load_check_answers() diff --git a/Storage/NEW_KT_Storage/Controller/TagObjectController.py b/Storage/NEW_KT_Storage/Controller/TagObjectController.py new file mode 100644 index 00000000..82b72998 --- /dev/null +++ b/Storage/NEW_KT_Storage/Controller/TagObjectController.py @@ -0,0 +1,29 @@ +from typing import Optional, Dict +import sys +import os + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from Models import TagObjectModel +from Service.Classes.TagObjectService import TagObjectService + + +class TagObjctController: + def __init__(self): + self.tag_service = TagObjectService() + + def create_tag(self, key, value): + return self.tag_service.create(key, value) + + def get_tag(self, key:str)->TagObjectModel: + return self.tag_service.get(key) + + def delete_tag(self, key: str): + return self.tag_service.delete(key) + + def modify_tag(self, key: str, changes: Dict): + return self.tag_service.modify(key, changes) + + def describe_tag(self): + return self.tag_service.describe() diff --git a/Storage/NEW_KT_Storage/DataAccess/ObjectManager.py b/Storage/NEW_KT_Storage/DataAccess/ObjectManager.py index 70a1b4f0..1a35507e 100644 --- a/Storage/NEW_KT_Storage/DataAccess/ObjectManager.py +++ b/Storage/NEW_KT_Storage/DataAccess/ObjectManager.py @@ -49,5 +49,4 @@ def convert_object_attributes_to_dictionary(**kwargs): def get_all_objects_from_memory(self, object_name): - return self.object_manager.get_all_objects_from_memory(object_name) - + return self.object_manager.get_all_objects_from_memory(object_name) \ No newline at end of file diff --git a/Storage/NEW_KT_Storage/DataAccess/TagObjectManager.py b/Storage/NEW_KT_Storage/DataAccess/TagObjectManager.py new file mode 100644 index 00000000..3c7f7257 --- /dev/null +++ b/Storage/NEW_KT_Storage/DataAccess/TagObjectManager.py @@ -0,0 +1,85 @@ +from typing import Dict, Any +import sys +import os + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from Models.TagObjectModel import TagObject +from DataAccess.ObjectManager import ObjectManager + + +class TagObjectManager: + def __init__(self, db_file: str = "Tags.db"): + """Initialize ObjectManager with the database connection.""" + self.object_manager = ObjectManager(db_file) + self.object_manager.object_manager.db_manager.create_table("mng_Tags", TagObject.TABLE_STRUCTURE) + + def createInMemoryTagObject(self, tag: TagObject): + """Save a TagObject instance in memory.""" + return self.object_manager.save_in_memory( + object_name=TagObject.OBJECT_NAME, object_info=tag.to_sql() + ) + + def deleteInMemoryTagObject(self, key: str): + """Delete a TagObject from memory based on its primary key (key).""" + return self.object_manager.delete_from_memory_by_pk( + object_name=TagObject.OBJECT_NAME, pk_column=TagObject.PK_COULMN, pk_value=key + ) + + + + def describeTagObject(self) -> list[TagObject]: + """Retrieve a list of TagObjects from memory. + + Returns: + list: A list of TagObject instances. + + Raises: + NoTagObjectsFoundError: If no TagObjects are found in memory. + """ + # Retrieve the list of tuples (key, value) from memory + results = self.object_manager.get_from_memory(object_name=TagObject.OBJECT_NAME) + + if results == []: + return [] + + # Create a list of TagObject instances from the results + tag_objects = [TagObject(key=res[0], value=res[1]) for res in results] + + return tag_objects + + + def putTagObject(self, old_key: str, updates: str = None): + """Update a TagObject in memory based on its primary key.""" + if not updates: + raise ValueError("No fields to update") + + return self.object_manager.update_in_memory( + object_name=TagObject.OBJECT_NAME, + updates=updates, + criteria=f""" {TagObject.PK_COULMN}='{old_key}' """, + ) + + def get_tag_object_from_memory(self, key: str) -> TagObject: + """Retrieve a TagObject from memory based on its primary key (key). + + Args: + key (str): The primary key of the TagObject to retrieve. + + Returns: + TagObject: The retrieved TagObject instance, or None if not found. + """ + # Retrieve data from memory based on the primary key + result = self.object_manager.get_from_memory( + object_name=TagObject.OBJECT_NAME, + criteria=f"{TagObject.PK_COULMN}='{key}'" + ) + + if not result or not result[0]: + raise KeyError("No TagObject.") + + # Create a TagObject from the retrieved data + key, value = result[0] # Unpack key and value from the result + tag_object = TagObject(key=key, value=value) + + return tag_object diff --git a/Storage/NEW_KT_Storage/Models/TagObjectModel.py b/Storage/NEW_KT_Storage/Models/TagObjectModel.py new file mode 100644 index 00000000..8d18adde --- /dev/null +++ b/Storage/NEW_KT_Storage/Models/TagObjectModel.py @@ -0,0 +1,51 @@ +from typing import Dict +import uuid +from DataAccess.ObjectManager import ObjectManager +import json +from datetime import datetime + + +class TagObject: + PK_COULMN = "Key" + OBJECT_NAME = "Tag" + TABLE_STRUCTURE = ", ".join(["Key TEXT PRIMARY KEY", "Value TEXT"]) + + def __init__(self, key: str, value: str): + self.key = key + self.value = value + + def to_dict(self) -> Dict: + """Retrieve the data of the DB cluster as a dictionary.""" + return ObjectManager.convert_object_attributes_to_dictionary( + key=self.key, + value=self.value, + ) + + def to_sql(self) -> str: + """Convert the TagObject instance to a SQL-friendly format.""" + data_dict = self.to_dict() + try: + values = ( + "(" + + ", ".join( + ( + f"'{json.dumps(v)}'" + if isinstance(v, dict) or isinstance(v, list) + else f"'{v}'" if isinstance(v, str) else f"'{str(v)}'" + ) + for v in data_dict.values() + ) + + ")" + ) + return values + except Exception as e: + print(f"Error converting to SQL format: {e}") + return None + + def __str__(self) -> str: + """Convert the TagObject instance to a JSON string.""" + try: + return json.dumps(self.to_dict(), indent=4) + except Exception as e: + print(f"Error converting to JSON string: {e}") + return None diff --git a/Storage/NEW_KT_Storage/Service/Classes/TagObjectService.py b/Storage/NEW_KT_Storage/Service/Classes/TagObjectService.py new file mode 100644 index 00000000..d74bd81d --- /dev/null +++ b/Storage/NEW_KT_Storage/Service/Classes/TagObjectService.py @@ -0,0 +1,120 @@ +import sys +import os + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) + +from DataAccess.TagObjectManager import TagObjectManager +from DataAccess.StorageManager import StorageManager +from Models.TagObjectModel import TagObject +from Validation import TagValidation as validation + + +class TagObjectService: + def __init__( + self, db_file: str = "Tags.db", storage_file: str = "local_storage" + ) -> None: + """Initialize the TagObjectService with a TagObjectManager instance.""" + self.tag_dal = TagObjectManager(db_file) + self.tags = [] + self.load_tags() + + def load_tags(self): + self.tags = self.tag_dal.describeTagObject() + + def validation_for_key(self, key): + if not validation.check_required_params(key): + raise ValueError("key is required") + if not validation.is_valid_key_name(key): + raise ValueError("Incorrect key name") + + def validation_for_value(self, value): + if not validation.is_valid_key_name(value): + raise ValueError("Incorrect key name") + + def create(self, key, value) -> None: + """Create a new TagObject and save it in memory. + + Args: + key (str): The key for the TagObject. + value (str): The value for the TagObject. + """ + self.validation_for_key(key=key) + + self.validation_for_value(value=value) + + if validation.key_exists(tags=self.tags, key=key): + raise KeyError("Duplicate key") + + tag = TagObject(key, value) + create_result = self.tag_dal.createInMemoryTagObject(tag) + self.load_tags() + return create_result + + def get(self, key: str) -> TagObject: + """Retrieve a TagObject from memory by its key. + + Args: + key (str): The key of the TagObject to retrieve. + """ + self.validation_for_key(key=key) + + if not validation.key_exists(tags=self.tags, key=key): + raise KeyError("no such key") + + return self.tag_dal.get_tag_object_from_memory(key) + + def delete(self, key: str): + """Delete a TagObject from memory by its key. + + Args: + key (str): The key of the TagObject to delete. + """ + self.validation_for_key(key=key) + + if not validation.key_exists(tags=self.tags, key=key): + raise KeyError("no such key") + + delete_result = self.tag_dal.deleteInMemoryTagObject(key) + self.load_tags() + return delete_result + + def modify(self, old_key: str, key: str = None, value: str = None): + """Modify an existing TagObject's key and/or value. + + Args: + old_key (str): The original key of the TagObject to modify. + key (str, optional): The new key to set. Defaults to None. + value (str, optional): The new value to set. Defaults to None. + """ + self.validation_for_key(key=old_key) + + if not validation.key_exists(tags=self.tags, key=old_key): + raise KeyError("no such key") + + update_fields = "" + + if key is not None: + self.validation_for_key(key=key) + if validation.key_exists(tags=self.tags, key=key): + raise ValueError("Douplicate key") + update_fields += f"""Key = '{key}' """ + + if value is not None: + self.validation_for_value(value=value) + + if update_fields: + update_fields += ", " + update_fields += f"""Value = '{value}' """ + + put_tag_result = self.tag_dal.putTagObject( + old_key=old_key, updates=update_fields + ) + self.load_tags() + return put_tag_result + + def describe(self): + """Retrieve a list of all TagObjects from memory.""" + return self.tag_dal.describeTagObject() + + diff --git a/Storage/NEW_KT_Storage/Test/TagObject_test.py b/Storage/NEW_KT_Storage/Test/TagObject_test.py new file mode 100644 index 00000000..ce74c006 --- /dev/null +++ b/Storage/NEW_KT_Storage/Test/TagObject_test.py @@ -0,0 +1,295 @@ +import pytest +import os +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +) + +from Storage.NEW_KT_Storage.Service.Classes.TagObjectService import TagObjectService +from Models.TagObjectModel import TagObject + + +@pytest.fixture(scope="function") +def tag_service(): + tag_service = TagObjectService(db_file=":memory:") + return tag_service + + +def test_create_tag(tag_service): + """Test the creation of a new tag.""" + key = "test_key" + value = "test_value" + tag_service.create(key, value) + + # Assert that the tag is created and exists in memory + tag = tag_service.get(key) + assert tag is not None + assert tag.key == key + assert tag.value == value + + +def test_get_tag(tag_service): + """Test retrieving a tag by key.""" + key = "get_test_key" + value = "get_test_value" + tag_service.create(key, value) + + # Retrieve the tag and verify its correctness + tag = tag_service.get(key) + assert tag is not None + assert tag.key == key + assert tag.value == value + + +def test_raise_KeyError_where_get_not_exist_tag(tag_service): + key_that_not_exist = "key_that_not_exist" + with pytest.raises(KeyError): + tag_service.get(key_that_not_exist) + + +def test_modify_tag(tag_service): + """Test modifying an existing tag.""" + key = "modify_test_key" + value = "modify_test_value" + new_key = "new_test_key" + new_value = "new_test_value" + + # Create a tag and modify it + tag_service.create(key, value) + tag_service.modify(old_key=key, key=new_key, value=new_value) + + # Verify the tag was updated + modified_tag = tag_service.get(new_key) + assert modified_tag is not None + assert modified_tag.key == new_key + assert modified_tag.value == new_value + + +def test_modify_tag_key_to_existing_key(tag_service): + """Test modifying a tag's key to an already existing key.""" + key1 = "existing_key_1" + value1 = "value1" + key2 = "existing_key_2" + value2 = "value2" + tag_service.create(key1, value1) + tag_service.create(key2, value2) + + # Attempt to modify key1 to key2, which already exists + with pytest.raises(Exception): # Adjust based on how your system handles this + tag_service.modify(old_key=key1, key=key2) + + +def test_get_after_modify_key(tag_service): + """Test that the original key is no longer accessible after modifying the key.""" + key = "mod_key_test" + value = "mod_value_test" + new_key = "new_mod_key" + tag_service.create(key, value) + + # Modify the key + tag_service.modify(old_key=key, key=new_key) + + # The old key should raise a KeyError + with pytest.raises(KeyError): + tag_service.get(key) + + +def test_delete_tag(tag_service): + """Test deleting an existing tag.""" + key = "delete_test_key" + value = "delete_test_value" + tag_service.create(key, value) + + # Delete the tag and verify it's gone + tag_service.delete(key) + + with pytest.raises(KeyError): + tag_service.get(key) + + +def test_delete_non_existent_tag(tag_service): + """Test attempting to delete a non-existent tag.""" + non_existent_key = "non_existent_key" + + # Attempting to delete should not raise an error but should have no effect + with pytest.raises(KeyError): + tag_service.delete(non_existent_key) + + +def test_modify_non_existent_tag(tag_service): + """Test modifying a tag that does not exist.""" + non_existent_key = "non_existent_key" + + # Modifying a non-existent tag should raise an error + with pytest.raises(KeyError): + tag_service.modify(old_key=non_existent_key, key="new_key", value="new_value") + + +def test_create_duplicate_tag(tag_service): + """Test creating a tag with a duplicate key.""" + key = "duplicate_key" + value1 = "value1" + value2 = "value2" + + # Create the first tag + tag_service.create(key, value1) + + # Creating a second tag with the same key should raise an error or overwrite + with pytest.raises( + Exception + ): # Modify this according to how duplicates are handled + tag_service.create(key, value2) + + +def test_create_with_empty_key(tag_service): + """Test creating a tag with an empty key.""" + with pytest.raises(ValueError): # Adjust based on the actual exception + tag_service.create("", "value_with_empty_key") + + +def test_create_with_empty_value(tag_service): + """Test creating a tag with an empty value.""" + key = "empty_value_key" + tag_service.create(key, "") + + # Verify that the tag was created with an empty value + tag = tag_service.get(key) + assert tag.value == "" + + +def test_get_tag_case_sensitivity(tag_service): + """Test the case sensitivity of tag keys.""" + key_lower = "case_key" + key_upper = "CASE_KEY" + value_lower = "lower_case_value" + value_upper = "upper_case_value" + + # Create both a lowercase and uppercase key + tag_service.create(key_lower, value_lower) + tag_service.create(key_upper, value_upper) + + # Verify that both tags exist and are distinct + tag_lower = tag_service.get(key_lower) + tag_upper = tag_service.get(key_upper) + assert tag_lower.value == value_lower + assert tag_upper.value == value_upper + assert tag_lower.key != tag_upper.key + + +def test_modify_only_key(tag_service): + """Test modifying only the key of an existing tag.""" + key = "original_key" + value = "original_value" + new_key = "new_only_key" + + # Create a tag and modify only its key + tag_service.create(key, value) + tag_service.modify(old_key=key, key=new_key) + + # Verify that the key was updated and value remains the same + modified_tag = tag_service.get(new_key) + assert modified_tag.key == new_key + assert modified_tag.value == value + + +def test_modify_only_value(tag_service): + """Test modifying only the value of an existing tag.""" + key = "modify_value_key" + value = "original_value" + new_value = "new_only_value" + + # Create a tag and modify only its value + tag_service.create(key, value) + tag_service.modify(old_key=key, value=new_value) + + # Verify that the value was updated and the key remains the same + modified_tag = tag_service.get(key) + assert modified_tag.key == key + assert modified_tag.value == new_value + + +def test_describe_empty(tag_service): + """Test describing tags when no tags exist.""" + # Describe should return an empty list when no tags are present + result = tag_service.describe() + assert result == [] + + +def test_create_tag_with_special_characters(tag_service): + """Test creating a tag with special characters in the key and value.""" + key = "key_with_special_!@#$%^&*()" + value = "value_with_special_{}[]<>" + + # Verify that the tag was created with the special characters + with pytest.raises(ValueError): + tag_service.create(key, value) + + +def test_create_tag_with_long_key(tag_service): + """Test creating a tag with a very long key.""" + long_key = "k" * 256 + value = "long_key_value" + with pytest.raises(ValueError): + tag_service.create(long_key, value) + + +def test_create_tag_with_long_value(tag_service): + """Test creating a tag with a very long value.""" + key = "long_value_key" + long_value = "v" * 1024 + tag_service.create(key, long_value) + + # Verify that the tag with the long value was created + tag = tag_service.get(key) + assert tag.key == key + assert tag.value == long_value + + +def test_modify_with_invalid_old_key(tag_service): + """Test modifying a tag with an invalid original key.""" + invalid_key = "invalid_key" + with pytest.raises(KeyError): + tag_service.modify(old_key=invalid_key, key="new_key", value="new_value") + + +def test_delete_all_tags(tag_service): + """Test deleting all tags and ensuring the memory is empty.""" + # Create a few tags + tag_service.create("key1", "value1") + tag_service.create("key2", "value2") + tag_service.create("key3", "value3") + + # Delete all tags + tag_service.delete("key1") + tag_service.delete("key2") + tag_service.delete("key3") + + # Describe should return an empty list + assert tag_service.describe() == [] + + +def test_modify_tag_to_empty_key(tag_service): + """Test modifying a tag's key to an empty key, which should raise an error.""" + key = "non_empty_key" + value = "some_value" + tag_service.create(key, value) + + with pytest.raises(ValueError): # Adjust based on your system's behavior + tag_service.modify(old_key=key, key="") + + +def test_create_tag_with_whitespace_key(tag_service): + """Test creating a tag with a key containing only whitespace.""" + with pytest.raises(ValueError): # Adjust based on your system's behavior + tag_service.create(" ", "value_with_whitespace_key") + + +def test_modify_tag_to_whitespace_key(tag_service): + """Test modifying a tag's key to a key containing only whitespace.""" + key = "valid_key" + value = "some_value" + tag_service.create(key, value) + + with pytest.raises(ValueError): # Adjust based on your system's behavior + tag_service.modify(old_key=key, key=" ") diff --git a/Storage/NEW_KT_Storage/Validation/TagValidation.py b/Storage/NEW_KT_Storage/Validation/TagValidation.py new file mode 100644 index 00000000..135eefab --- /dev/null +++ b/Storage/NEW_KT_Storage/Validation/TagValidation.py @@ -0,0 +1,13 @@ +import re +import Storage.NEW_KT_Storage.Validation.GeneralValidations as GeneralValidations + +def check_required_params(key): + return GeneralValidations.check_required_params(["key"],{'key': key}) + +def key_exists(tags, key): + '''Check if a bucket with the given name already exists.''' + return any(tag.key == key for tag in tags) + +def is_valid_key_name(key): + return re.match(r'^[a-zA-Z0-9._-]{2,63}$', key) +