diff --git a/.gitignore b/.gitignore index 40a6e03..6a99654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,13 @@ +# RF / VSCode +output.xml +report.html +log.html +launch.json +01*.csv +02*.csv +03*.csv +04*.csv + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/CSVLibrary/__init__.py b/CSVLibrary/__init__.py index 81c8376..d285d09 100644 --- a/CSVLibrary/__init__.py +++ b/CSVLibrary/__init__.py @@ -1,300 +1 @@ -import csv -import sys - -from robot.api import logger -from robot.utils.dotdict import DotDict - -if sys.version_info.major >= 3: - from io import StringIO as IO -else: - from io import BytesIO as IO - -__version__ = '0.0.5' - -class DotDictReader(csv.DictReader): - def __next__(self): - return DotDict(super().__next__()) - - -class CSVLibrary(object): - - ROBOT_LIBRARY_VERSION = __version__ - ROBOT_LIBRARY_SCOPE = 'GLOBAL' - - @staticmethod - def _reader(to_read, csv_reader=csv.reader, line_numbers=None, **kwargs): - reader = csv_reader(to_read, **kwargs) - try: - for line_number, row in enumerate(reader): - if line_numbers is None: - yield row - elif isinstance(line_numbers, list) and line_number in line_numbers: - yield row - line_numbers.remove(line_number) - if len(line_numbers) == 0: - break - except csv.Error as e: - logger.error('line %d: %s' % (reader.line_num, e)) - - def _read_csv(self, csv_handler, csv_reader=csv.reader, line_numbers=None, **kwargs): - if line_numbers is not None and isinstance(line_numbers, list): - line_numbers = list(map(int, line_numbers)) - - return [row for row in self._reader(csv_handler, csv_reader, line_numbers, **kwargs)] - - def _open_csv_file_for_read(self, filename, csv_reader=csv.reader, line_numbers=None, **kwargs): - with open(filename, 'r') as csv_handler: - return self._read_csv(csv_handler, csv_reader, line_numbers, **kwargs) - - @staticmethod - def _write_csv(csv_handler, data, csv_writer=csv.writer, **kwargs): - if 'fieldnames' not in kwargs.keys() and isinstance(data[0], dict): - kwargs['fieldnames'] = data[0].keys() - - writer = csv_writer(csv_handler, **kwargs) - try: - if isinstance(writer, csv.DictWriter) and csv_handler.tell() == 0: - writer.writeheader() - - if not isinstance(data[0], (list, tuple, dict, set)): - data = [data] - writer.writerows(data) - except csv.Error as e: - logger.error('%s' % e) - - def _open_csv_file_for_write(self, filename, data, csv_writer=csv.writer, **kwargs): - open_args = {'mode': 'ab'} - if sys.version_info >= (3, 2): - open_args['mode'] = 'a' - open_args['newline'] = '' - - with open(filename, **open_args) as csv_handler: - self._write_csv(csv_handler, data, csv_writer, **kwargs) - - @staticmethod - def empty_csv_file(filename): - """This keyword will empty the CSV file. - - - ``filename``: name of csv file - """ - with open(filename, "w") as csv_handler: - csv_handler.truncate() - - def read_csv_file_to_list(self, filename, delimiter=',', **kwargs): - """Read CSV file and return its content as a Python list of tuples. - - - ``filename``: name of csv file - - ``delimiter``: Default: `,` - - ``line_numbers``: List of linenumbers to read. Default None - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - csv_list = self._open_csv_file_for_read( - filename, - csv_reader=csv.reader, - delimiter=str(delimiter), - **kwargs - ) - return csv_list - - def read_csv_string_to_list(self, csv_string, delimiter=',', **kwargs): - """Read CSV string and return its content as a Python list of tuples. - - - ``csv_string``: name of csv file - - ``delimiter``: Default: `,` - - ``line_numbers``: List of linenumbers to read. Default None - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - if sys.version_info.major < 3: - csv_string = csv_string.encode("utf-8") - - with IO(csv_string) as csv_handler: - csv_list = self._read_csv( - csv_handler, - csv_reader=csv.reader, - delimiter=str(delimiter), - **kwargs - ) - return csv_list - - def read_csv_file_to_associative(self, filename, delimiter=',', fieldnames=None, **kwargs): - """Read CSV file and return its content as a Python list of dictionaries. - - - ``filename``: name of csv file - - ``delimiter``: Default: `,` - - ``fieldnames``: list of column names - - ``line_numbers``: List of linenumbers to read. Default None - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - kwargs['fieldnames'] = fieldnames - csv_dict = self._open_csv_file_for_read( - filename, - csv_reader=DotDictReader, - delimiter=str(delimiter), - **kwargs - ) - return csv_dict - - def read_csv_string_to_associative(self, csv_string, delimiter=',', fieldnames=None, **kwargs): - """Read CSV from string and return its content as a Python list of dictionaries. - - - ``csv_string``: csv formatted string - - ``delimiter``: Default: `,` - - ``fieldnames``: list of column names - - ``line_numbers``: List of linenumbers to read. Default None - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - if sys.version_info.major < 3: - csv_string = csv_string.encode("utf-8") - - with IO(csv_string) as csv_handler: - csv_dict = self._read_csv( - csv_handler, - csv_reader=DotDictReader, - delimiter=str(delimiter), - fieldnames=fieldnames, - **kwargs - ) - return csv_dict - - def append_to_csv_file(self, filename, data, **kwargs): - """This keyword will append data to a new or existing CSV file. - - - ``filename``: name of csv file - - ``data``: iterable(e.g. list or tuple) data. - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - - header = self._open_csv_file_for_read( - filename, - csv_reader=csv.reader, - line_numbers=[0], - **kwargs - ) - - if isinstance(data, dict): - data = [data] - - fieldnames = None - if isinstance(data[0], dict): - fieldnames = data[0].keys() - - if isinstance(data[0], dict) and len(header) > 0: - fieldnames = header[0] - - if fieldnames is not None: - kwargs['fieldnames'] = fieldnames - kwargs['csv_writer'] = csv.DictWriter - - self._open_csv_file_for_write( - filename, - data=data, - **kwargs - ) - - def append_to_csv_string(self, csv_string, data, **kwargs): - """This keyword will append data to a new or existing CSV string. - - - ``csv_string``: csv formatted string - - ``data``: iterable(e.g. list or tuple) data. - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - if isinstance(data, dict): - data = [data] - - if 'lineterminator' not in kwargs.keys(): - kwargs['lineterminator'] = '\n' - - if sys.version_info.major < 3: - csv_string = csv_string.encode("utf-8") - - with IO(csv_string) as csv_handler: - header = self._read_csv( - csv_handler, - csv_reader=csv.reader, - **kwargs - ) - - fieldnames = None - if isinstance(data[0], dict): - fieldnames = data[0].keys() - - if isinstance(data[0], dict) and len(header) > 0: - fieldnames = header[0] - - if fieldnames is not None: - kwargs['fieldnames'] = fieldnames - kwargs['csv_writer'] = csv.DictWriter - - self._write_csv(csv_handler, data, **kwargs) - return csv_handler.getvalue() - - def csv_file_from_associative(self, filename, data, fieldnames=None, delimiter=',', **kwargs): - """This keyword will create new file - - - ``filename``: name of csv file - - ``data``: iterable(e.g. list or tuple) data. - - ``fieldnames``: list of column names - - ``delimiter``: Default: `,` - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - kwargs['fieldnames'] = fieldnames or data[0].keys() - - self._open_csv_file_for_write( - filename, - data=data, - csv_writer=csv.DictWriter, - delimiter=str(delimiter), - **kwargs - ) - - def csv_string_from_associative(self, data, fieldnames=None, delimiter=',', **kwargs): - """This keyword will return csv string - - - ``data``: iterable(e.g. list or tuple) data. - - ``fieldnames``: list of column names - - ``delimiter``: Default: `,` - - ``quoting`` (int): - _0_: QUOTE_MINIMAL - _1_: QUOTE_ALL - _2_: QUOTE_NONNUMERIC - _3_: QUOTE_NONE - """ - if isinstance(data, dict): - data = [data] - - kwargs['fieldnames'] = fieldnames or data[0].keys() - if 'lineterminator' not in kwargs.keys(): - kwargs['lineterminator'] = '\n' - kwargs['delimiter'] = delimiter - - with IO() as csv_handler: - self._write_csv(csv_handler, data, csv_writer=csv.DictWriter, **kwargs) - return csv_handler.getvalue() +from .keywords import Keywords as CSVLibrary diff --git a/CSVLibrary/helper.py b/CSVLibrary/helper.py new file mode 100644 index 0000000..e0bc074 --- /dev/null +++ b/CSVLibrary/helper.py @@ -0,0 +1,109 @@ +import csv +import sys +import os +from pathlib import Path + +from robot.api import logger +from robot.utils.dotdict import DotDict + +if sys.version_info.major >= 3: + from io import StringIO as IO +else: + from io import BytesIO as IO + + +class DotDictReader(csv.DictReader): + def __next__(self): + return DotDict(super().__next__()) + + +class Helper(object): + + @staticmethod + def _reader(to_read, csv_reader=csv.reader, line_numbers=None, **kwargs): + reader = csv_reader(to_read, **kwargs) + try: + for line_number, row in enumerate(reader): + if line_numbers is None: + yield row + elif isinstance(line_numbers, list) and line_number in line_numbers: + yield row + line_numbers.remove(line_number) + if len(line_numbers) == 0: + break + except csv.Error as e: + logger.error('line %d: %s' % (reader.line_num, e)) + + def _read_csv(self, csv_handler, csv_reader=csv.reader, line_numbers=None, **kwargs): + if line_numbers is not None and isinstance(line_numbers, list): + line_numbers = list(map(int, line_numbers)) + + return [row for row in self._reader(csv_handler, csv_reader, line_numbers, **kwargs)] + + def _open_csv_file_for_read(self, filename, csv_reader=csv.reader, line_numbers=None, **kwargs): + with open(filename, 'r') as csv_handler: + return self._read_csv(csv_handler, csv_reader, line_numbers, **kwargs) + + @staticmethod + def _write_csv(csv_handler, data, csv_writer=csv.writer, **kwargs): + if 'fieldnames' not in kwargs.keys() and isinstance(data[0], dict): + kwargs['fieldnames'] = data[0].keys() + + writer = csv_writer(csv_handler, **kwargs) + try: + if isinstance(writer, csv.DictWriter) and csv_handler.tell() == 0: + writer.writeheader() + + if not isinstance(data[0], (list, tuple, dict, set)): + data = [data] + writer.writerows(data) + except csv.Error as e: + logger.error('%s' % e) + + def _open_csv_file_for_write(self, filename, data, csv_writer=csv.writer, **kwargs): + open_args = {'mode': 'ab'} + if sys.version_info >= (3, 2): + open_args['mode'] = 'a' + open_args['newline'] = '' + + with open(filename, **open_args) as csv_handler: + self._write_csv(csv_handler, data, csv_writer, **kwargs) + + def _get_io(self): + return IO + + # CSV Merge + def _fetch_matching_files(self, + csv_input: str, + file_filter: str + ): + # If given -> apply file filter + pattern = "*.csv" + if file_filter: + pattern = f"*{file_filter}*.csv" + + # Fetch all given files / files in given directory + if len(csv_input) == 1 and Path(csv_input[0]).resolve().is_dir(): + csv_files = sorted(Path(csv_input[0]).rglob(pattern)) + else: + csv_files = [Path(p) for p in csv_input] + if not csv_files: + raise ValueError(f"No CSV files found for given input: {csv_input}") + return csv_files + + # CSV Merge + def _write_to_csv(self, + output_file: str, + rows: list, + header: list[str] = None, + delelte_file: bool = True + ): + if delelte_file: + os.remove(output_file) if os.path.exists(output_file) else None + + with open(output_file, "w", newline='', encoding="utf-8") as fout: + writer = csv.writer(fout) + if header: + writer.writerow(header) + writer.writerows(rows) + logger.info(f"File '{output_file}' has been written successfully!") diff --git a/CSVLibrary/keywords.py b/CSVLibrary/keywords.py new file mode 100644 index 0000000..d803b53 --- /dev/null +++ b/CSVLibrary/keywords.py @@ -0,0 +1,348 @@ +import csv +import sys +from pathlib import Path +import os + +from robot.api import logger +from robot.api.deco import library, keyword +from .helper import Helper, DotDictReader +from .version import VERSION + + +@library +class Keywords(object): + + ROBOT_LIBRARY_VERSION = VERSION + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + + def __init__(self): + self.helper = None + self.IO = None + + @property + def kw(self): + self.helper = Helper() + return self.helper + + @property + def io(self): + self.IO = self.helper._get_io() + return self.IO + + @staticmethod + @keyword(tags=["CSVLibrary"]) + def empty_csv_file(filename): + """This keyword will empty the CSV file. + + - ``filename``: name of csv file + """ + with open(filename, "w") as csv_handler: + csv_handler.truncate() + + @keyword(tags=["CSVLibrary"]) + def read_csv_file_to_list(self, filename, delimiter=',', **kwargs): + """Read CSV file and return its content as a Python list of tuples. + + - ``filename``: name of csv file + - ``delimiter``: Default: `,` + - ``line_numbers``: List of linenumbers to read. Default None + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + csv_list = self.kw._open_csv_file_for_read( + filename, + csv_reader=csv.reader, + delimiter=str(delimiter), + **kwargs + ) + return csv_list + + @keyword(tags=["CSVLibrary"]) + def read_csv_string_to_list(self, csv_string, delimiter=',', **kwargs): + """Read CSV string and return its content as a Python list of tuples. + + - ``csv_string``: name of csv file + - ``delimiter``: Default: `,` + - ``line_numbers``: List of linenumbers to read. Default None + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + if sys.version_info.major < 3: + csv_string = csv_string.encode("utf-8") + + with self.io(csv_string) as csv_handler: + csv_list = self.kw._read_csv( + csv_handler, + csv_reader=csv.reader, + delimiter=str(delimiter), + **kwargs + ) + return csv_list + + @keyword(tags=["CSVLibrary"]) + def read_csv_file_to_associative(self, filename, delimiter=',', fieldnames=None, **kwargs): + """Read CSV file and return its content as a Python list of dictionaries. + + - ``filename``: name of csv file + - ``delimiter``: Default: `,` + - ``fieldnames``: list of column names + - ``line_numbers``: List of linenumbers to read. Default None + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + kwargs['fieldnames'] = fieldnames + csv_dict = self.kw._open_csv_file_for_read( + filename, + csv_reader=DotDictReader, + delimiter=str(delimiter), + **kwargs + ) + return csv_dict + + @keyword(tags=["CSVLibrary"]) + def read_csv_string_to_associative(self, csv_string, delimiter=',', fieldnames=None, **kwargs): + """Read CSV from string and return its content as a Python list of dictionaries. + + - ``csv_string``: csv formatted string + - ``delimiter``: Default: `,` + - ``fieldnames``: list of column names + - ``line_numbers``: List of linenumbers to read. Default None + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + if sys.version_info.major < 3: + csv_string = csv_string.encode("utf-8") + + with self.io(csv_string) as csv_handler: + csv_dict = self.kw._read_csv( + csv_handler, + csv_reader=DotDictReader, + delimiter=str(delimiter), + fieldnames=fieldnames, + **kwargs + ) + return csv_dict + + @keyword(tags=["CSVLibrary"]) + def append_to_csv_file(self, filename, data, **kwargs): + """This keyword will append data to a new or existing CSV file. + + - ``filename``: name of csv file + - ``data``: iterable(e.g. list or tuple) data. + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + + header = self.kw._open_csv_file_for_read( + filename, + csv_reader=csv.reader, + line_numbers=[0], + **kwargs + ) + + if isinstance(data, dict): + data = [data] + + fieldnames = None + if isinstance(data[0], dict): + fieldnames = data[0].keys() + + if isinstance(data[0], dict) and len(header) > 0: + fieldnames = header[0] + + if fieldnames is not None: + kwargs['fieldnames'] = fieldnames + kwargs['csv_writer'] = csv.DictWriter + + self.kw._open_csv_file_for_write( + filename, + data=data, + **kwargs + ) + + @keyword(tags=["CSVLibrary"]) + def append_to_csv_string(self, csv_string, data, **kwargs): + """This keyword will append data to a new or existing CSV string. + + - ``csv_string``: csv formatted string + - ``data``: iterable(e.g. list or tuple) data. + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + if isinstance(data, dict): + data = [data] + + if 'lineterminator' not in kwargs.keys(): + kwargs['lineterminator'] = '\n' + + if sys.version_info.major < 3: + csv_string = csv_string.encode("utf-8") + + with self.io(csv_string) as csv_handler: + header = self.kw._read_csv( + csv_handler, + csv_reader=csv.reader, + **kwargs + ) + + fieldnames = None + if isinstance(data[0], dict): + fieldnames = data[0].keys() + + if isinstance(data[0], dict) and len(header) > 0: + fieldnames = header[0] + + if fieldnames is not None: + kwargs['fieldnames'] = fieldnames + kwargs['csv_writer'] = csv.DictWriter + + self.kw._write_csv(csv_handler, data, **kwargs) + return csv_handler.getvalue() + + @keyword(tags=["CSVLibrary"]) + def csv_file_from_associative(self, filename, data, fieldnames=None, delimiter=',', **kwargs): + """This keyword will create new file + + - ``filename``: name of csv file + - ``data``: iterable(e.g. list or tuple) data. + - ``fieldnames``: list of column names + - ``delimiter``: Default: `,` + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + kwargs['fieldnames'] = fieldnames or data[0].keys() + + self.kw._open_csv_file_for_write( + filename, + data=data, + csv_writer=csv.DictWriter, + delimiter=str(delimiter), + **kwargs + ) + + @keyword(tags=["CSVLibrary"]) + def csv_string_from_associative(self, data, fieldnames=None, delimiter=',', **kwargs): + """This keyword will return csv string + + - ``data``: iterable(e.g. list or tuple) data. + - ``fieldnames``: list of column names + - ``delimiter``: Default: `,` + - ``quoting`` (int): + _0_: QUOTE_MINIMAL + _1_: QUOTE_ALL + _2_: QUOTE_NONNUMERIC + _3_: QUOTE_NONE + """ + if isinstance(data, dict): + data = [data] + + kwargs['fieldnames'] = fieldnames or data[0].keys() + if 'lineterminator' not in kwargs.keys(): + kwargs['lineterminator'] = '\n' + kwargs['delimiter'] = delimiter + + with self.io() as csv_handler: + self.kw._write_csv(csv_handler, data, csv_writer=csv.DictWriter, **kwargs) + return csv_handler.getvalue() + + @keyword(tags=["CSVLibrary"]) + def merge_csv_files(self, + *csv_input: str, + output_file: str, + file_filter: str = None + ): + """ + Keyword to merge multiple equivalent CSV files to one common CSV output file. + + = Arguments = + ``csv_input (str)`` -> the folder or exact files you want to merge.\n + ``output_file (str)`` -> the path & name of your output file.\n + ``file_filter (str)`` -> regex pattern to merge only files with specific file name.\n + + = Use Case = + A lot of data is downloaded from your data storage & its downloaded into multiple CSV files for reasons + like these files are separated into different folders with their related timestamp.\n + Due to having the same data set & table headers, it makes sense to merge csv files for further csv data validation. + + = Example = + Merge all files in ${DOWN_DIR}: + | Merge Csv Files ${DOWN_DIR}\\ output_file=MergedCustomData.csv + + Merge all files in ${DOWN_DIR} with name *weather_date*: + | Merge Csv Files ${DOWN_DIR}\\ output_file=MergedCustomData.csv file_filter=weather_data + + Merge exactly two CSV files: + | Merge Csv Files + | ... ${DOWN_DIR}\\Folder01\\data.csv + | ... ${DOWN_DIR}\\Folder02\\data.csv + | ... MergedCustomData.csv + """ + # Fetch matching files from file system + csv_files = self.kw._fetch_matching_files(csv_input, file_filter) + + # Merge found files + all_rows = [] + header = None + for csv_file in csv_files: + logger.debug(f"Merging file '{csv_file}'") + with open(csv_file, "r", encoding="utf-8") as fin: + lines = fin.readlines() + + fixed_lines = [] + for line in lines: + # If mistakenly quotes are started but not ended in a linke lik "start & end" + # -> fix it (needed due to occurance in example tests) + quote_count = line.count('"') + if quote_count % 2 != 0: + line = line.replace('"', '""') + fixed_lines.append(line.strip()) + reader = csv.reader(fixed_lines) + + try: + file_header = next(reader) + except Exception as e: + logger.warn(f"Invalid Header: {e}") + continue + + if header is None: + header = file_header + elif header != file_header: + raise ValueError(f"Only CSV Files with same structure / headers can be merged!") + + for row_num, row in enumerate(reader, start=2): + if len(row) != len(header): + logger.info(f"Row Nr. {row_num} in File {csv_file} is skipped - count of columns does not match: {row}") + continue + all_rows.append(row) + + if not header: + raise ValueError("No valid header found!") + + # If continously increasing ID column is used, it will be recongized during merge + if header[0].lower() == "id": + for i, row in enumerate(all_rows, start=1): + row[0] = str(i) + + # Write content to csv file + self.kw._write_to_csv(output_file, all_rows, header) \ No newline at end of file diff --git a/CSVLibrary/version.py b/CSVLibrary/version.py new file mode 100644 index 0000000..9b0a593 --- /dev/null +++ b/CSVLibrary/version.py @@ -0,0 +1 @@ +VERSION = "0.0.6" diff --git a/Examples/data.csv b/Examples/data.csv index 3066d92..ede4695 100644 --- a/Examples/data.csv +++ b/Examples/data.csv @@ -8,4 +8,5 @@ id,first_name,last_name,email,gender,ip_address 7,Rachel,Robinson,rrobinson6@blogger.com,Female,132.66.117.101 8,Phillip,Johnston,pjohnston7@disqus.com,Male,70.152.55.21 9,Craig,Burton,cburton8@toplist.cz,Male,73.117.157.82 -10,Patrick,Fisher,pfisher9@1und1.de,Male,2.36.191.97 \ No newline at end of file +10,Patrick,Fisher,pfisher9@1und1.de,Male,2.36.191.97 +10,Patrick,,,Male,2.36.191.97 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 242e75c..bfc22ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -robotframework >= 2.6.0 \ No newline at end of file +pandas +robotframework >= 2.6.0 +setuptools \ No newline at end of file diff --git a/setup.py b/setup.py index f7309e4..dfd34e9 100644 --- a/setup.py +++ b/setup.py @@ -1,28 +1,18 @@ #!/usr/bin/env python +from setuptools import setup, find_packages +# from CSVLibrary.version import VERSION -from os.path import join, dirname, abspath -from setuptools import setup +with open('CSVLibrary/version.py') as f: + VERSION = f.read().strip().lower().replace(" ", "").replace("\"", "").replace("version=", "") +with open('requirements.txt') as f: + REQUIREMENTS = f.read().splitlines() -def read(rel_path): - here = abspath(dirname(__file__)) - with open(join(here, rel_path)) as fp: - return fp.read() - - -def get_version(rel_path): - for line in read(rel_path).splitlines(): - if line.startswith("__version__"): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - raise RuntimeError("Unable to find version string.") - - -REQUIREMENTS = read('requirements.txt').splitlines() -DESCRIPTION = read('README.md') +with open('README.md') as f: + DESCRIPTION = f.read() setup(name='robotframework-csvlibrary', - version=get_version("CSVLibrary/__init__.py"), + version=VERSION, description='CSV library for Robot Framework', long_description=DESCRIPTION, long_description_content_type='text/markdown', @@ -48,5 +38,5 @@ def get_version(rel_path): 'Programming Language :: Python :: 3.10', ], install_requires=REQUIREMENTS, - packages=['CSVLibrary'], + packages=find_packages(), ) diff --git a/tests/MergeCsvFiles.robot b/tests/MergeCsvFiles.robot new file mode 100644 index 0000000..08bc154 --- /dev/null +++ b/tests/MergeCsvFiles.robot @@ -0,0 +1,26 @@ +*** Settings *** +Library CSVLibrary + + +*** Test Cases *** +Merge Files from a Folder + Merge Csv Files + ... ${CURDIR}\\..\\Examples + ... output_file=01_merged.csv + +Merge Files from a Folder to specific path + Merge Csv Files + ... ${CURDIR}\\..\\Examples + ... output_file=${CURDIR}\\02_merged.csv + +Merge Files from a Folder with File Filter (Pattern) + Merge Csv Files + ... ${CURDIR}\\..\\Examples + ... output_file=03_merged.csv + ... file_filter=data + +Merge Exact two files + Merge Csv Files + ... ${CURDIR}\\..\\Examples\\data_quoting.csv + ... ${CURDIR}\\..\\Examples\\data.csv + ... output_file=04_merged.csv \ No newline at end of file