Skip to content

Commit

Permalink
making generatin csv more robust
Browse files Browse the repository at this point in the history
making generating csv more robust and modify the test for generation
  • Loading branch information
Khaleelhabeeb committed Apr 6, 2024
1 parent a7e6c97 commit 96f585b
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 71 deletions.
45 changes: 30 additions & 15 deletions csv_utilite/generation.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
105 changes: 54 additions & 51 deletions test/test_generation.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion test/test_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
2 changes: 1 addition & 1 deletion test/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
2 changes: 1 addition & 1 deletion test/test_validation.py
Original file line number Diff line number Diff line change
@@ -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):

Expand Down
2 changes: 1 addition & 1 deletion test/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 96f585b

Please sign in to comment.