diff --git a/csv_utilite/generation.py b/csv_utilite/generation.py index 5f633d4..9e348e0 100644 --- a/csv_utilite/generation.py +++ b/csv_utilite/generation.py @@ -1,39 +1,53 @@ import csv from typing import Iterable, Any, Union, List, Dict, Optional -def generate_from_dict(data: Union[Dict[str, Any], List[Dict[str, Any]]], output_path: str, headers: Optional[List[str]] = None): + +def generate_csv_rows(data: Union[Dict[str, Any], List[Dict[str, Any]]]) -> List[List[str]]: """ - Generate a CSV file from a dictionary or a list of dictionaries. + Generates a list of CSV rows from a dictionary or a list of dictionaries. Args: - data (Union[Dict[str, Any], List[Dict[str, Any]]]): A dictionary or a list of dictionaries containing the data. - output_path (str): The file path for the output CSV file. - headers (Optional[List[str]]): An optional list of headers to use for the CSV file. - If not provided, the keys from the first dictionary in the data will be used. + data (Union[Dict[str, Any], List[Dict[str, Any]]]): The data to convert to CSV rows. - Raises: - ValueError: If the data is not a dictionary or a list of dictionaries. + Returns: + List[List[str]]: A list of CSV rows, where each row is a list of strings. """ + if isinstance(data, dict): data = [data] if not all(isinstance(item, dict) for item in data): raise ValueError("Data must be a dictionary or a list of dictionaries.") - rows = [] - if headers is None: - headers = list(data[0].keys()) - rows.append(headers) + headers = list(data[0].keys()) + rows = [headers] for item in data: - row = [item.get(header, '') for header in headers] + row = [item.get(header, "") for header in headers] rows.append(row) + return rows + + +def generate_from_dict(data: List[Dict[str, Any]], output_path: str, headers: Optional[List[str]] = None) -> None: + """ + Generate a CSV file from a dictionary or a list of dictionaries. + + Args: + data (List[Dict[str, Any]]): The data as a list of dictionaries. + output_path (str): The file path for the output CSV file. + headers (Optional[List[str]]): An optional list of headers to use for the CSV file. + If not provided, the keys from the first dictionary in the data will be used. + """ + + rows = generate_csv_rows(data) + with open(output_path, 'w', newline='') as file: writer = csv.writer(file) writer.writerows(rows) -def generate_from_db(query: str, db_connection, output_path: str, headers: Optional[List[str]] = None): + +def generate_from_db(query: str, db_connection, output_path: str, headers: Optional[List[str]] = None) -> None: """ Generate a CSV file from a database query. @@ -42,11 +56,12 @@ def generate_from_db(query: str, db_connection, output_path: str, headers: Optio db_connection: The database connection object. output_path (str): The file path for the output CSV file. headers (Optional[List[str]]): An optional list of headers to use for the CSV file. - If not provided, the column names from the query result will be used. + If not provided, the column names from the query result will be used. Raises: ValueError: If the database connection or the query result is invalid. """ + try: cursor = db_connection.cursor() cursor.execute(query) diff --git a/main.py b/main.py new file mode 100644 index 0000000..f0ae342 --- /dev/null +++ b/main.py @@ -0,0 +1,12 @@ +from csv_utilite.generation import generate_from_dict + +# Generate CSV from a dictionary +data = {'Name': 'John', 'Age': 25, 'City': 'New York'} +output_path = 'output.csv' +generate_from_dict(data, output_path, headers=['Name', 'Age', 'City']) + +# Generate CSV from a list of dictionaries +data = [{'Name': 'John', 'Age': 25, 'City': 'New York'}, + {'Name': 'Jane', 'Age': 30, 'City': 'London'}] +output_path = 'output.csv' +generate_from_dict(data, output_path) \ No newline at end of file diff --git a/setup.py b/setup.py index 90cc56a..39ea690 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='csv_utilite', - version='1.0.0', + version='1.0.1', description='csv-util is a Python package designed to facilitate working with CSV files in a more convenient and Pythonic manner compared to the built-in csv module.', long_description=open('README.md').read(), long_description_content_type='text/markdown', diff --git a/test/test_generation.py b/test/test_generation.py index f99f77b..ed02863 100644 --- a/test/test_generation.py +++ b/test/test_generation.py @@ -1,56 +1,59 @@ import unittest +from unittest.mock import patch import os -import tempfile -from unittest.mock import patch, MagicMock -from csv_utilities.generation import generate_from_dict, generate_from_db - -class TestGenerationModule(unittest.TestCase): - def setUp(self): - self.data_dict = {'Name': 'John', 'Age': 25, 'City': 'New York'} - self.data_list = [{'Name': 'John', 'Age': 25, 'City': 'New York'}, - {'Name': 'Jane', 'Age': 30, 'City': 'London'}] - self.headers = ['Name', 'Age', 'City'] - self.temp_file = tempfile.NamedTemporaryFile(mode='w+', delete=False) - self.temp_file_path = self.temp_file.name - self.temp_file.close() - - def tearDown(self): - if os.path.exists(self.temp_file_path): - os.unlink(self.temp_file_path) - - def test_generate_from_dict(self): - generate_from_dict(self.data_dict, self.temp_file_path, headers=self.headers) - with open(self.temp_file_path, 'r') as file: - lines = file.readlines() - self.assertEqual(len(lines), 2) - self.assertEqual(lines[0].strip(), ','.join(self.headers)) - self.assertEqual(lines[1].strip(), ','.join([self.data_dict[header] for header in self.headers])) - - generate_from_dict(self.data_list, self.temp_file_path) - with open(self.temp_file_path, 'r') as file: - lines = file.readlines() - self.assertEqual(len(lines), 3) - self.assertEqual(lines[0].strip(), ','.join(self.data_list[0].keys())) - self.assertEqual(lines[1].strip(), ','.join([str(value) for value in self.data_list[0].values()])) - self.assertEqual(lines[2].strip(), ','.join([str(value) for value in self.data_list[1].values()])) - - with self.assertRaises(ValueError): - generate_from_dict([1, 2, 3], self.temp_file_path) - - @patch('csv_utils.generation.csv') - def test_generate_from_db(self, mock_csv): - mock_cursor = MagicMock() - mock_cursor.description = [('Name',), ('Age',), ('City',)] - mock_cursor.fetchall.return_value = [('John', 25, 'New York'), ('Jane', 30, 'London')] - mock_db_connection = MagicMock() - mock_db_connection.cursor.return_value = mock_cursor - - generate_from_db("SELECT name, age, city FROM users", mock_db_connection, self.temp_file_path) - mock_csv.writer.return_value.writerow.assert_any_call(['Name', 'Age', 'City']) - mock_csv.writer.return_value.writerows.assert_called_with([('John', 25, 'New York'), ('Jane', 30, 'London')]) - - with self.assertRaises(ValueError): - generate_from_db("INVALID QUERY", mock_db_connection, self.temp_file_path) +from csv_utilite.generation import generate_from_dict, generate_from_db + + +class TestCSVGeneration(unittest.TestCase): + + def setUp(self) -> None: + self.test_data = [ + {'name': 'Alice', 'age': 30}, + {'name': 'Bob', 'age': 25} + ] + self.test_output_path = 'test_output.csv' + + def tearDown(self) -> None: + if os.path.exists(self.test_output_path): + os.remove(self.test_output_path) + + def test_generate_from_dict_single_dict(self): + generate_from_dict({'name': 'Alice', 'age': 30}, self.test_output_path) + with open(self.test_output_path, 'r') as file: + content = file.read() + self.assertEqual(content, 'name,age\nAlice,30\n') + + def test_generate_from_dict_list_of_dicts(self): + generate_from_dict(self.test_data, self.test_output_path) + with open(self.test_output_path, 'r') as file: + content = file.read() + self.assertEqual(content, 'name,age\nAlice,30\nBob,25\n') + + def test_generate_from_dict_custom_headers(self): + headers = ['First Name', 'Years'] + generate_from_dict(self.test_data, self.test_output_path, headers=headers) + with open(self.test_output_path, 'r') as file: + content = file.read() + self.assertEqual(content, 'First Name,Years\nAlice,30\nBob,25\n') + + @patch('your_file_name.csv.writer') + def test_generate_from_db(self, mock_writer): + mock_cursor = mock_writer.return_value.__enter__.return_value + mock_cursor.fetchall.return_value = [ + ('John', 40), + ('Mary', 35) + ] + mock_cursor.description = [('name',), ('age',)] + + generate_from_db('SELECT * FROM users', mock_cursor, self.test_output_path) + + mock_writer.assert_called_once_with(open(self.test_output_path, 'w', newline='')) + mock_writer.return_value.__enter__.return_value.writerow.assert_called_once_with(['name', 'age']) + mock_writer.return_value.__enter__.return_value.writerows.assert_called_once_with([ + ['John', 40], + ['Mary', 35] + ]) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/test/test_manipulation.py b/test/test_manipulation.py index 03799ef..2f3a838 100644 --- a/test/test_manipulation.py +++ b/test/test_manipulation.py @@ -2,7 +2,7 @@ import csv from unittest.mock import patch, MagicMock from typing import Iterable, Any, Callable, List, Dict, Optional -from csv_utilities.manipulation import filter_rows, sort_rows, merge_files +from csv_utilite.manipulation import filter_rows, sort_rows, merge_files class CSVUtilsTest(unittest.TestCase): diff --git a/test/test_reader.py b/test/test_reader.py index 261d592..b228b73 100644 --- a/test/test_reader.py +++ b/test/test_reader.py @@ -2,7 +2,7 @@ from unittest.mock import patch, MagicMock import csv from typing import Iterator, Optional, Any, Union, List, Dict -from csv_utilities.reader import Reader +from csv_utilite.reader import Reader class Reader(Reader): """ diff --git a/test/test_validation.py b/test/test_validation.py index ba47786..c349fcf 100644 --- a/test/test_validation.py +++ b/test/test_validation.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch, MagicMock from typing import Iterable, Any, Callable, List, Dict, Optional -from csv_utilities.validation import validate_rows, validate_headers +from csv_utilite.validation import validate_rows, validate_headers class CSVUtilsTest(unittest.TestCase): diff --git a/test/test_writer.py b/test/test_writer.py index c6a9bfd..90f8244 100644 --- a/test/test_writer.py +++ b/test/test_writer.py @@ -2,7 +2,7 @@ from unittest.mock import patch, MagicMock import csv from typing import Iterable, Any, Union, Optional -from csv_utilities.writer import Writer +from csv_utilite.writer import Writer class Writer(Writer):