diff --git a/.gitignore b/.gitignore index a8d6d00..ed33d42 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,5 @@ docs/_build/ target/ .eric4project -*.e4p \ No newline at end of file +*.e4p +*.bak \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2dcb6ff --- /dev/null +++ b/.travis.yml @@ -0,0 +1,46 @@ +language: python +sudo: false + +env: + global: + - MINICONDA_VERSION="latest" + - MINICONDA_LINUX="Linux-x86_64" + - MINICONDA_OSX="MacOSX-x86_64" + +matrix: + include: + # Python3 + # Windows using pip packages + - python: "3.5" + env: USE_QT_API=PyQt5 USE_CONDA=false + os: windows + - python: "3.5" + env: USE_QT_API=PyQt4 USE_CONDA=false + os: windows + # Linux using pip packages + - python: "3.5" + env: USE_QT_API=PyQt5 USE_CONDA=false + os: linux + - python: "3.5" + env: USE_QT_API=PyQt4 USE_CONDA=false + os: linux + # Windows using conda packages + - python: "3.5" + env: USE_QT_API=PyQt5 USE_CONDA=true + os: windows + - python: "3.5" + env: USE_QT_API=PyQt4 USE_CONDA=true + os: windows + # Linux using conda packages + - python: "2.7" + env: USE_QT_API=PyQt4 USE_CONDA=true + os: linux + - python: "2.7" + env: USE_QT_API=PyQt5 USE_CONDA=true + os: linux + - python: "3.5" + env: USE_QT_API=PyQt4 USE_CONDA=true + os: linux + - python: "3.5" + env: USE_QT_API=PyQt5 USE_CONDA=true + os: linux diff --git a/README b/README deleted file mode 100644 index 3f68a6a..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -Utilities to use pandas (the data analysis / manipulation library for Python) with Qt. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2bb6a5 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# QtPandas + +### Utilities to use pandas (the data analysis/manipulation library for Python) with Qt. + +## Project Information + +[![Join the chat at https://gitter.im/qtpandas/Lobby#](https://badges.gitter.im/qtpandas/lobby.svg)](https://gitter.im/qtpandas/Lobby#) + +## Build Status + +[![Travis status](https://travis-ci.org/draperjames/QtPandas.svg?branch=master)](https://travis-ci.org/draperjames/QtPandas) + +Requirements; +> Python 3.x +> Pandas 20.0 +> PyQt 4.7.8 + +To install run the following in the command prompt; +``` +pip install git+https://github.com/zbarge/QtPandas.git +pip install --upgrade git+https://github.com/robertlugg/easygui.git +``` + +To use, create a new Python script containing the following: +``` +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from pandasqt.views.CSVDialogs import CSVImportDialog + +if __name__ == "__main__": + from sys import argv, exit + + app = QApplication(argv) + dialog = CSVImportDialog() + dialog.show() + app.exec_() +``` +Several examples can also be found in the exmples directory. + +# Development + +## Wanna contribute? + +Join us on [gitter](https://gitter.im/qtpandas/Lobby#) + +### TO DO: +- [x] Reach out to @kaotika and @datalyze-solutions +- [ ] Secure qtpandas namespace on pip. +- [ ] Wait for reply + - [ ] If no reply create new repo for QtPandas. +- [x] Create .travis.yml file. + - [x] integrate into README.md +- [ ] Add documentation. +- [ ] Add screen shots +- [ ] Create Wiki +- [ ] Make verison agnostic. +- [ ] Create specific Python version tests. +- [ ] Add Windows, Apple, and Linux tests. +- [ ] Consider adding functions seen in [Spyder IDE's dataframeeditor](https://github.com/spyder-ide/spyder/blob/f2b36f00f873cf4080087bfb529e6256b3e24792/spyder/widgets/variableexplorer/dataframeeditor.py) + - [ ] Sort + - [ ] Color coding + + +Forked from @datalyze-solutions's [master](https://github.com/datalyze-solutions/pandas-qt). diff --git a/doc/source/conf.py b/doc/source/conf.py index 8f7fb65..0f7cd88 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -53,8 +53,8 @@ master_doc = 'index' # General information about the project. -project = u'pandas-qt' -copyright = u'2014, Matthias Ludwig - Datalyze Solutions' +project = 'pandas-qt' +copyright = '2014, Matthias Ludwig - Datalyze Solutions' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -225,8 +225,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'pandas-qt.tex', u'pandas-qt Documentation', - u'Matthias Ludwig - Datalyze Solutions', 'manual'), + ('index', 'pandas-qt.tex', 'pandas-qt Documentation', + 'Matthias Ludwig - Datalyze Solutions', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -255,8 +255,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pandas-qt', u'pandas-qt Documentation', - [u'Matthias Ludwig - Datalyze Solutions'], 1) + ('index', 'pandas-qt', 'pandas-qt Documentation', + ['Matthias Ludwig - Datalyze Solutions'], 1) ] # If true, show URL addresses after external links. @@ -269,8 +269,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pandas-qt', u'pandas-qt Documentation', - u'Matthias Ludwig - Datalyze Solutions', 'pandas-qt', 'One line description of project.', + ('index', 'pandas-qt', 'pandas-qt Documentation', + 'Matthias Ludwig - Datalyze Solutions', 'pandas-qt', 'One line description of project.', 'Miscellaneous'), ] diff --git a/examples/TestApp.py b/examples/TestApp.py index ea0a888..bdc0d88 100644 --- a/examples/TestApp.py +++ b/examples/TestApp.py @@ -46,7 +46,7 @@ def dropEvent(self, event): """ super(DropLineEdit, self).dropEvent(event) mimeDataPayload = event.mimeData().data() - self.setText(u"dropped column: {0}".format(mimeDataPayload.column)) + self.setText("dropped column: {0}".format(mimeDataPayload.column)) class ComplexDropWidget(QtGui.QLineEdit): @@ -217,13 +217,13 @@ def setModelColumn(self, index): self.dataComboBox.setModelColumn(index) def goToColumn(self): - print "go to column 7" + print("go to column 7") index = self.dataTableView.view().model().index(7, 0) self.dataTableView.view().setCurrentIndex(index) def changeColumnValue(self, columnName, index, dtype): - print "failed to change", columnName, "to", dtype - print index.data(), index.isValid() + print("failed to change", columnName, "to", dtype) + print(index.data(), index.isValid()) self.dataTableView.view().setCurrentIndex(index) def setFilter(self): diff --git a/examples/ThreadedExample.py b/examples/ThreadedExample.py index 3573624..83d04c7 100644 --- a/examples/ThreadedExample.py +++ b/examples/ThreadedExample.py @@ -66,7 +66,7 @@ def initUI(self): @Slot() def debugPrint(self): - print 'THREAD %s ended' % (self.sender().name, ) + print('THREAD %s ended' % (self.sender().name, )) if __name__ == '__main__': diff --git a/examples/util.py b/examples/util.py index 1828be0..cb51dd9 100644 --- a/examples/util.py +++ b/examples/util.py @@ -46,7 +46,7 @@ def getCsvData(): return df def getRandomData(rows=100, columns=5): - columns = [u"column {}".format(column) for column in range(columns) ] + columns = ["column {}".format(column) for column in range(columns) ] data = {} for column in columns: data[column] = numpy.random.rand(rows) diff --git a/pandasqt/compat.py b/pandasqt/compat.py index c75f805..835ea35 100644 --- a/pandasqt/compat.py +++ b/pandasqt/compat.py @@ -10,14 +10,14 @@ sip.setapi('QTextStream', 2) sip.setapi('QTime', 2) sip.setapi('QUrl', 2) -except ValueError, e: +except ValueError as e: log.error(e) try: from PyQt4 import QtCore as QtCore_ from PyQt4 import QtGui as QtGui_ from PyQt4.QtCore import pyqtSlot as Slot, pyqtSignal as Signal -except ImportError, e: +except ImportError as e: from PySide import QtCore as QtCore_ from PySide import QtGui as QtGui_ from PySide.QtCore import Slot, Signal diff --git a/pandasqt/encoding.py b/pandasqt/encoding.py index a5e345a..011990a 100644 --- a/pandasqt/encoding.py +++ b/pandasqt/encoding.py @@ -1,6 +1,6 @@ import sys import os - +import warnings BASEDIR = os.path.dirname(os.path.abspath(__file__)) if sys.platform == 'win32': @@ -12,9 +12,13 @@ try: import magic AUTODETECT = True -except ImportError, e: +except ImportError as e: #if sys.platform == 'darwin': - raise ImportError('Please install libmagic') + #raise ImportError('Please install libmagic') + warnings.warn("Please install libmagic - got an error: {}".format(e)) + AUTODETECT = False +except OSError as e: + warnings.warn("Detector.Issues importing libmagic - got an error: {}".format(e)) AUTODETECT = False diff --git a/pandasqt/excepthook.py b/pandasqt/excepthook.py index 1661b89..4ca0921 100644 --- a/pandasqt/excepthook.py +++ b/pandasqt/excepthook.py @@ -1,7 +1,7 @@ # copied and modified from Eric IDE ( credits goes to author ) import time -import cStringIO +import io import traceback from pandasqt.compat import QtGui import codecs @@ -18,14 +18,14 @@ def excepthook(excType, excValue, tracebackobj): @param excValue exception value @param tracebackobj traceback object """ - separator = u'-' * 80 + separator = '-' * 80 logFile = os.path.join(tempfile.gettempdir(), "error.log") notice = """An unhandled exception occurred. Please report the problem.\n""" notice += """A log has been written to "{}".\n\nError information:""".format(logFile) timeString = time.strftime("%Y-%m-%d, %H:%M:%S") - tbinfofile = cStringIO.StringIO() + tbinfofile = io.StringIO() traceback.print_tb(tracebackobj, None, tbinfofile) tbinfofile.seek(0) tbinfo = tbinfofile.read() @@ -33,26 +33,26 @@ def excepthook(excType, excValue, tracebackobj): try: excValueStr = str(excValue).decode('utf-8') - except UnicodeEncodeError, e: - excValueStr = unicode(excValue) + except UnicodeEncodeError as e: + excValueStr = str(excValue) - errmsg = u'{0}: \n{1}'.format(excType, excValueStr) - sections = [u'\n', separator, timeString, separator, errmsg, separator, tbinfo] - msg = u'\n'.join(sections) + errmsg = '{0}: \n{1}'.format(excType, excValueStr) + sections = ['\n', separator, timeString, separator, errmsg, separator, tbinfo] + msg = '\n'.join(sections) try: f = codecs.open(logFile, "a+", encoding='utf-8') f.write(msg) f.close() - except IOError, e: - msgbox(u"unable to write to {0}".format(logFile), u"Writing error") + except IOError as e: + msgbox("unable to write to {0}".format(logFile), "Writing error") # always show an error message try: if not _isQAppRunning(): app = QtGui.QApplication([]) - _showMessageBox(unicode(notice) + unicode(msg)) + _showMessageBox(str(notice) + str(msg)) except: - msgbox(unicode(notice) + unicode(msg), u"Error") + msgbox(str(notice) + str(msg), "Error") def _isQAppRunning(): if QtGui.QApplication.instance() is None: diff --git a/pandasqt/models/ColumnDtypeModel.py b/pandasqt/models/ColumnDtypeModel.py index 372398f..6eb825b 100644 --- a/pandasqt/models/ColumnDtypeModel.py +++ b/pandasqt/models/ColumnDtypeModel.py @@ -206,7 +206,7 @@ def setData(self, index, value, role=DTYPE_CHANGE_ROLE): try: if dtype == np.dtype('".format(selectionEvent), @@ -1097,7 +1097,7 @@ def __put_buttons_in_buttonframe(choices, default_choice, cancel_choice): buttons[default_choice]['default_choice'] = True buttons[default_choice]['widget'].focus_force() # Bind hotkeys - for hk in [button['hotkey'] for button in buttons.values() if button['hotkey']]: + for hk in [button['hotkey'] for button in list(buttons.values()) if button['hotkey']]: boxRoot.bind_all(hk, lambda e: __buttonEvent(e, buttons), add=True) return diff --git a/pandasqt/ui/fallback/easygui/boxes/derived_boxes.py b/pandasqt/ui/fallback/easygui/boxes/derived_boxes.py index 66e6828..4fe6851 100644 --- a/pandasqt/ui/fallback/easygui/boxes/derived_boxes.py +++ b/pandasqt/ui/fallback/easygui/boxes/derived_boxes.py @@ -203,7 +203,7 @@ def msgbox(msg="(Your message goes here)", title=" ", :param tk_widget root: Top-level Tk widget :return: the text of the ok_button """ - if not isinstance(ok_button, ut.basestring): + if not isinstance(ok_button, ut.str): raise AssertionError( "The 'ok_button' argument to msgbox must be a string.") diff --git a/pandasqt/ui/fallback/easygui/boxes/egstore.py b/pandasqt/ui/fallback/easygui/boxes/egstore.py index 23efd44..d7ff228 100644 --- a/pandasqt/ui/fallback/easygui/boxes/egstore.py +++ b/pandasqt/ui/fallback/easygui/boxes/egstore.py @@ -145,7 +145,7 @@ def __str__(self): # find the length of the longest attribute name longest_key_length = 0 keys = list() - for key in self.__dict__.keys(): + for key in list(self.__dict__.keys()): keys.append(key) longest_key_length = max(longest_key_length, len(key)) diff --git a/pandasqt/ui/fallback/easygui/boxes/text_box.py b/pandasqt/ui/fallback/easygui/boxes/text_box.py index 01d1fcd..922eae9 100644 --- a/pandasqt/ui/fallback/easygui/boxes/text_box.py +++ b/pandasqt/ui/fallback/easygui/boxes/text_box.py @@ -174,7 +174,7 @@ def __textboxOK(event): def to_string(something): - if isinstance(something, ut.basestring): + if isinstance(something, ut.str): return something try: text = "".join(something) # convert a list or a tuple to a string @@ -196,4 +196,4 @@ def demo_textbox(): title = "Demo of textbox" msg = "Here is some sample text. " * 16 reply = textbox(msg, title, text_snippet) - print("Reply was: {!s}".format(reply)) + print(("Reply was: {!s}".format(reply))) diff --git a/pandasqt/ui/fallback/easygui/boxes/updatable_text_box.py b/pandasqt/ui/fallback/easygui/boxes/updatable_text_box.py index b6fa594..e15d46e 100644 --- a/pandasqt/ui/fallback/easygui/boxes/updatable_text_box.py +++ b/pandasqt/ui/fallback/easygui/boxes/updatable_text_box.py @@ -32,22 +32,22 @@ import tkinter.filedialog as tk_FileDialog from io import StringIO else: - from Tkinter import * - import tkFileDialog as tk_FileDialog - from StringIO import StringIO + from tkinter import * + import tkinter.filedialog as tk_FileDialog + from io import StringIO # Set up basestring appropriately if runningPython3: - basestring = str + str = str if TkVersion < 8.0: stars = "*" * 75 - print("""\n\n\n""" + stars + """ + print(("""\n\n\n""" + stars + """ You are running Tk version: """ + str(TkVersion) + """ You must be using Tk version 8.0 or greater to use EasyGui. Terminating. -""" + stars + """\n\n\n""") +""" + stars + """\n\n\n""")) sys.exit(0) rootWindowPosition = "+300+200" @@ -223,7 +223,7 @@ def __update_myself(event): # ----------------- the action begins ------------------------------------ try: # load the text into the textArea - if isinstance(text, basestring): + if isinstance(text, str): pass else: try: @@ -267,4 +267,4 @@ def _demo_textbox(): text_snippet = (( "Update button!!!. " * 5) + "\n\n") * 10 reply = textbox(msg, title, text_snippet, get_updated_text=update) - print("Reply was: {!s}".format(reply)) \ No newline at end of file + print(("Reply was: {!s}".format(reply))) \ No newline at end of file diff --git a/pandasqt/ui/fallback/easygui/boxes/utils.py b/pandasqt/ui/fallback/easygui/boxes/utils.py index 3f8fbb7..7694dc4 100644 --- a/pandasqt/ui/fallback/easygui/boxes/utils.py +++ b/pandasqt/ui/fallback/easygui/boxes/utils.py @@ -29,9 +29,9 @@ import tkinter.filedialog as tk_FileDialog except ImportError: try: - import Tkinter as tk # python2 - from Tkinter import * - import tkFileDialog as tk_FileDialog + import tkinter as tk # python2 + from tkinter import * + import tkinter.filedialog as tk_FileDialog except ImportError: raise ImportError("Unable to find tkinter package.") @@ -49,9 +49,9 @@ # Code should use 'basestring' anywhere you might think to use the system 'str'. This is all to support # Python 2. If 2 ever goes away, this logic can go away and uses of utils.basestring should be changed to just str if runningPython27: - basestring = basestring + str = str if runningPython34: - basestring = str + str = str def lower_case_sort(things): if runningPython34: diff --git a/pandasqt/utils.py b/pandasqt/utils.py index de5a26c..2d42477 100644 --- a/pandasqt/utils.py +++ b/pandasqt/utils.py @@ -1,6 +1,8 @@ from random import randint from pandas import to_datetime +import pandas as pd import numpy as np +import os def fillNoneValues(column): """Fill all NaN/NaT values of a column with an empty string @@ -36,4 +38,164 @@ def convertTimestamps(column): tempColumn = column.apply(to_datetime) except: pass - return tempColumn \ No newline at end of file + return tempColumn + + + + +def superReadCSV(filepath, first_codec='UTF_8', usecols=None, + low_memory=False, dtype=None, parse_dates=True, + sep=',', chunksize=None, verbose=False, **kwargs): + """ A wrap to pandas read_csv with mods to accept a dataframe or filepath. returns dataframe untouched, reads filepath and returns dataframe based on arguments.""" + if isinstance(filepath, pd.DataFrame): return filepath + assert isinstance(first_codec, str), "first_codec parameter must be a string" + + codecs = list(set([first_codec] + ['UTF_8','ISO-8859-1','ASCII','UTF_16','UTF_32'])) + errors = [] + for c in codecs: + try: + + return pd.read_csv(filepath, + usecols=usecols, + low_memory=low_memory, + encoding=c, + dtype=dtype, + parse_dates=parse_dates, + sep=sep, + chunksize=chunksize, + **kwargs) + + except (UnicodeDecodeError, UnboundLocalError) as e: + errors.append(e) + except Exception as e: + errors.append(e) + if 'tokenizing' in str(e): + pass + else: + raise + if verbose: + [print(e) for e in errors] + raise UnicodeDecodeError("Tried {} codecs and failed on all: \n CODECS: {} \n FILENAME: {}".format( + len(codecs), codecs, os.path.basename(filepath)) ) + +def _count(item,string): + if len(item) == 1: + return len(''.join(x for x in string if x == item)) + return len(str(string.split(item))) + +def identify_sep(filepath): + """Identifies the separator of data in a filepath. + It reads the first line of the file and counts supported separators. + Currently supported separators: ['|', ';', ',','\t',':']""" + ext = os.path.splitext(filepath)[1].lower() + allowed_exts = ['.csv', '.txt', '.tsv'] + assert ext in ['.csv', '.txt'], "Unexpected file extension {}. \ + Supported extensions {}\n filename: {}".format( + ext, allowed_exts, os.path.basename(filepath)) + maybe_seps = ['|', + ';', + ',', + '\t', + ':'] + + with open(filepath,'r') as fp: + header = fp.__next__() + + count_seps_header = {sep:_count(sep,header) for sep in maybe_seps} + count_seps_header = {sep:count for sep,count in count_seps_header.items() if count > 0} + + if count_seps_header: + return max(count_seps_header.__iter__(), + key=(lambda key: count_seps_header[key])) + else: + raise Exception("Couldn't identify the sep from the header... here's the information:\n HEADER: {}\n SEPS SEARCHED: {}".format(header,maybe_seps)) + +def superReadText(filepath,**kwargs): + """ + A wrapper to superReadCSV which wraps pandas.read_csv(). + The benefit of using this function is that it automatically identifies the column separator. + .tsv files are assumed to have a \t (tab) separation + .csv files are assumed to have a comma separation. + .txt (or any other type) get the first line of the file opened + and get tested for various separators as defined in the identify_sep function. + """ + if isinstance(filepath,pd.DataFrame): + return filepath + sep = kwargs.get('sep',None) + ext = os.path.splitext(filepath)[1].lower() + + if sep is None: + if ext == '.tsv': + kwargs['sep'] = '\t' + + elif ext == '.csv': + kwargs['sep'] = ',' + + else: + found_sep = identify_sep(filepath) + print(found_sep) + kwargs['sep'] = found_sep + + return superReadCSV(filepath,**kwargs) + +def superReadFile(filepath,**kwargs): + """ + Uses pandas.read_excel (on excel files) and returns a dataframe of the first sheet (unless sheet is specified in kwargs) + Uses superReadText (on .txt,.tsv, or .csv files) and returns a dataframe of the data. + One function to read almost all types of data files. + """ + if isinstance(filepath,pd.DataFrame): return filepath + + excels = ['.xlsx','.xls'] + texts = ['.txt','.tsv','.csv'] + ext = os.path.splitext(filepath)[1].lower() + + if ext in excels: + return pd.read_excel(filepath,**kwargs) + elif ext in texts: + return superReadText(filepath,**kwargs) + else: + raise Exception("Unsupported filetype: {}\n Supported filetypes: {}".format(ext,excels + texts)) + + +def dedupe_cols(frame): + """Need to dedupe columns that have the same name. """ + cols = list(frame.columns) + for i,item in enumerate(frame.columns): + if item in frame.columns[:i]: + cols[i] = "toDROP" + frame.columns = cols + return frame.drop("toDROP", 1, errors='ignore') + +def rename_dupe_cols(cols): + """Takes a list of strings and appends 2,3,4 etc to duplicates. Never appends a 0 or 1. + Appended #s are not always in order...but if you wrap this in a dataframe.to_sql function you're guaranteed + to not have dupe column name errors importing data to SQL...you'll just have to check yourself to see which fields were renamed.""" + counts = {} + positions = {pos:fld for pos,fld in enumerate(cols)} + + for c in cols: + if c in counts.keys(): + counts[c] +=1 + else: + counts[c] = 1 + + fixed_cols = {} + + for pos,col in positions.items(): + if counts[col] > 1: + fix_cols = {pos:fld for pos,fld in positions.items() if fld == col} + keys = [p for p in fix_cols.keys()] + min_pos = min(keys) + cnt = 1 + for p,c in fix_cols.items(): + if not p == min_pos: + cnt += 1 + c = c + str(cnt) + fixed_cols.update({p:c}) + + positions.update(fixed_cols) + + cols = [x for x in positions.values()] + + return cols \ No newline at end of file diff --git a/pandasqt/views/BigIntSpinbox.py b/pandasqt/views/BigIntSpinbox.py index 9d1bb16..80c6111 100644 --- a/pandasqt/views/BigIntSpinbox.py +++ b/pandasqt/views/BigIntSpinbox.py @@ -53,11 +53,11 @@ def setValue(self, value): True if all went fine. """ if value >= self.minimum() and value <= self.maximum(): - self._lineEdit.setText(unicode(value)) + self._lineEdit.setText(str(value)) elif value < self.minimum(): - self._lineEdit.setText(unicode(self.minimum())) + self._lineEdit.setText(str(self.minimum())) elif value > self.maximum(): - self._lineEdit.setText(unicode(self.maximum())) + self._lineEdit.setText(str(self.maximum())) return True def stepBy(self, steps): @@ -116,7 +116,7 @@ def setMinimum(self, minimum): Raises: TypeError: If the given argument is not an integer. """ - if not isinstance(minimum, (int, long)): + if not isinstance(minimum, int): raise TypeError("Argument is not of type int or long") self._minimum = minimum @@ -130,6 +130,6 @@ def setMaximum(self, maximum): Args: maximum (int or long): new _maximum value """ - if not isinstance(maximum, (int, long)): + if not isinstance(maximum, int): raise TypeError("Argument is not of type int or long") self._maximum = maximum \ No newline at end of file diff --git a/pandasqt/views/CSVDialogs.py b/pandasqt/views/CSVDialogs.py index fa33a0c..6e21ae6 100644 --- a/pandasqt/views/CSVDialogs.py +++ b/pandasqt/views/CSVDialogs.py @@ -2,7 +2,6 @@ import os from encodings.aliases import aliases as _encodings - import pandas from pandasqt.compat import Qt, QtCore, QtGui, Slot, Signal @@ -11,7 +10,7 @@ from pandasqt.views.CustomDelegates import DtypeComboDelegate from pandasqt.views._ui import icons_rc -from pandasqt.utils import fillNoneValues, convertTimestamps +from pandasqt.utils import fillNoneValues, convertTimestamps, superReadFile class DelimiterValidator(QtGui.QRegExpValidator): """A Custom RegEx Validator. @@ -90,13 +89,14 @@ def _initUI(self): """ #layout = QtGui.QHBoxLayout(self) - self.semicolonRadioButton = QtGui.QRadioButton(u'Semicolon') - self.commaRadioButton = QtGui.QRadioButton(u'Comma') - self.tabRadioButton = QtGui.QRadioButton(u'Tab') - self.otherRadioButton = QtGui.QRadioButton(u'Other') - self.semicolonRadioButton.setChecked(True) + self.semicolonRadioButton = QtGui.QRadioButton('Semicolon') + self.commaRadioButton = QtGui.QRadioButton('Comma') + self.tabRadioButton = QtGui.QRadioButton('Tab') + self.otherRadioButton = QtGui.QRadioButton('Other') + self.commaRadioButton.setChecked(True) self.otherSeparatorLineEdit = QtGui.QLineEdit(self) + #TODO: Enable this or add BAR radio and option. self.otherSeparatorLineEdit.setEnabled(False) self.semicolonRadioButton.toggled.connect(self._delimiter) @@ -195,7 +195,7 @@ def __init__(self, parent=None): """ super(CSVImportDialog, self).__init__(parent) self._modal = True - self._windowTitle = u'Import CSV' + self._windowTitle = 'Import CSV' self._encodingKey = None self._filename = None self._delimiter = None @@ -212,7 +212,7 @@ def _initUI(self): layout = QtGui.QGridLayout() - self._filenameLabel = QtGui.QLabel(u'Choose File', self) + self._filenameLabel = QtGui.QLabel('Choose File', self) self._filenameLineEdit = QtGui.QLineEdit(self) self._filenameLineEdit.textEdited.connect(self._updateFilename) chooseFileButtonIcon = QtGui.QIcon(QtGui.QPixmap(':/icons/document-open.png')) @@ -227,9 +227,9 @@ def _initUI(self): layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2) layout.addWidget(self._chooseFileButton, 0, 3) - self._encodingLabel = QtGui.QLabel(u'File Encoding', self) + self._encodingLabel = QtGui.QLabel('File Encoding', self) - encoding_names = map(lambda x: x.upper(), sorted(list(set(_encodings.viewvalues())))) + encoding_names = list([x.upper() for x in sorted(list(set(_encodings.values())))]) self._encodingComboBox = QtGui.QComboBox(self) self._encodingComboBox.addItems(encoding_names) self._encodingComboBox.activated.connect(self._updateEncoding) @@ -237,14 +237,15 @@ def _initUI(self): layout.addWidget(self._encodingLabel, 1, 0) layout.addWidget(self._encodingComboBox, 1, 1, 1, 1) - self._hasHeaderLabel = QtGui.QLabel(u'Header Available?', self) + self._hasHeaderLabel = QtGui.QLabel('Header Available?', self) self._headerCheckBox = QtGui.QCheckBox(self) self._headerCheckBox.toggled.connect(self._updateHeader) + layout.addWidget(self._hasHeaderLabel, 2, 0) layout.addWidget(self._headerCheckBox, 2, 1) - self._delimiterLabel = QtGui.QLabel(u'Column Delimiter', self) + self._delimiterLabel = QtGui.QLabel('Column Delimiter', self) self._delimiterBox = DelimiterSelectionWidget(self) self._delimiter = self._delimiterBox.currentSelected() self._delimiterBox.delimiter.connect(self._updateDelimiter) @@ -255,18 +256,18 @@ def _initUI(self): self._tabWidget = QtGui.QTabWidget(self) self._previewTableView = QtGui.QTableView(self) self._datatypeTableView = QtGui.QTableView(self) - self._tabWidget.addTab(self._previewTableView, u'Preview') - self._tabWidget.addTab(self._datatypeTableView, u'Change Column Types') + self._tabWidget.addTab(self._previewTableView, 'Preview') + self._tabWidget.addTab(self._datatypeTableView, 'Change Column Types') layout.addWidget(self._tabWidget, 4, 0, 3, 4) self._datatypeTableView.horizontalHeader().setDefaultSectionSize(200) self._datatypeTableView.setItemDelegateForColumn(1, DtypeComboDelegate(self._datatypeTableView)) - self._loadButton = QtGui.QPushButton(u'Load Data', self) + self._loadButton = QtGui.QPushButton('Load Data', self) #self.loadButton.setAutoDefault(False) - self._cancelButton = QtGui.QPushButton(u'Cancel', self) + self._cancelButton = QtGui.QPushButton('Cancel', self) # self.cancelButton.setDefault(False) # self.cancelButton.setAutoDefault(True) @@ -281,6 +282,7 @@ def _initUI(self): self._statusBar = QtGui.QStatusBar(self) self._statusBar.setSizeGripEnabled(False) + self._headerCheckBox.setChecked(True) layout.addWidget(self._statusBar, 8, 0, 1, 4) self.setLayout(layout) @@ -304,7 +306,15 @@ def _openFile(self): This method is also a `SLOT`. """ - ret = QtGui.QFileDialog.getOpenFileName(self, self.tr(u'open file'), filter='Comma Separated Values (*.csv)') + + file_types = "Comma Separated Values (*.csv);;Text files (*.txt);;All Files (*)" + ret = QtGui.QFileDialog.getOpenFileName(self, + self.tr('open file'), + filter=file_types) + + if isinstance(ret, tuple): + ret = ret[0] #PySide compatibility maybe? + if ret: self._filenameLineEdit.setText(ret) self._updateFilename() @@ -402,7 +412,7 @@ def _previewFile(self): """ dataFrame = self._loadCSVDataFrame() - dataFrameModel = DataFrameModel(dataFrame) + dataFrameModel = DataFrameModel(dataFrame, filePath=self._filename) dataFrameModel.enableEditing(True) self._previewTableView.setModel(dataFrameModel) columnModel = dataFrameModel.columnDtypeModel() @@ -422,22 +432,23 @@ def _loadCSVDataFrame(self): information of the csv file. """ - if self._filename and os.path.exists(self._filename) and self._filename.endswith('.csv'): + if self._filename and os.path.exists(self._filename): # default fallback if no encoding was found/selected - encoding = self._encodingKey or 'uft8' + encoding = self._encodingKey or 'UTF_8' try: - dataFrame = pandas.read_csv(self._filename, - sep=self._delimiter, encoding=encoding, + dataFrame = superReadFile(self._filename, + sep=self._delimiter, first_codec=encoding, header=self._header) dataFrame = dataFrame.apply(fillNoneValues) dataFrame = dataFrame.apply(convertTimestamps) - except Exception, err: + except Exception as err: self.updateStatusBar(str(err)) + print(err) return pandas.DataFrame() self.updateStatusBar('Preview generated.') return dataFrame - self.updateStatusBar('File does not exists or does not end with .csv') + self.updateStatusBar('File could not be read.') return pandas.DataFrame() def _resetWidgets(self): @@ -466,6 +477,7 @@ def accepted(self): df = model.dataFrame().copy() dfModel = DataFrameModel(df) self.load.emit(dfModel, self._filename) + print(("Emitted model for {}".format(self._filename))) self._resetWidgets() self.accept() @@ -491,7 +503,7 @@ def __init__(self, model=None, parent=None): super(CSVExportDialog, self).__init__(parent) self._model = model self._modal = True - self._windowTitle = u'Export to CSV' + self._windowTitle = 'Export to CSV' self._idx = -1 self._initUI() @@ -504,7 +516,7 @@ def _initUI(self): layout = QtGui.QGridLayout() - self._filenameLabel = QtGui.QLabel(u'Output File', self) + self._filenameLabel = QtGui.QLabel('Output File', self) self._filenameLineEdit = QtGui.QLineEdit(self) chooseFileButtonIcon = QtGui.QIcon(QtGui.QPixmap(':/icons/document-save-as.png')) self._chooseFileAction = QtGui.QAction(self) @@ -518,9 +530,11 @@ def _initUI(self): layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2) layout.addWidget(self._chooseFileButton, 0, 3) - self._encodingLabel = QtGui.QLabel(u'File Encoding', self) + self._encodingLabel = QtGui.QLabel('File Encoding', self) + + + encoding_names = list(map(lambda x: x.upper(), sorted(list(set(_encodings.values()))))) - encoding_names = map(lambda x: x.upper(), sorted(list(set(_encodings.viewvalues())))) self._encodingComboBox = QtGui.QComboBox(self) self._encodingComboBox.addItems(encoding_names) self._idx = encoding_names.index('UTF_8') @@ -530,21 +544,21 @@ def _initUI(self): layout.addWidget(self._encodingLabel, 1, 0) layout.addWidget(self._encodingComboBox, 1, 1, 1, 1) - self._hasHeaderLabel = QtGui.QLabel(u'Header Available?', self) + self._hasHeaderLabel = QtGui.QLabel('Header Available?', self) self._headerCheckBox = QtGui.QCheckBox(self) #self._headerCheckBox.toggled.connect(self._updateHeader) layout.addWidget(self._hasHeaderLabel, 2, 0) layout.addWidget(self._headerCheckBox, 2, 1) - self._delimiterLabel = QtGui.QLabel(u'Column Delimiter', self) + self._delimiterLabel = QtGui.QLabel('Column Delimiter', self) self._delimiterBox = DelimiterSelectionWidget(self) layout.addWidget(self._delimiterLabel, 3, 0) layout.addWidget(self._delimiterBox, 3, 1, 1, 3) - self._exportButton = QtGui.QPushButton(u'Export Data', self) - self._cancelButton = QtGui.QPushButton(u'Cancel', self) + self._exportButton = QtGui.QPushButton('Export Data', self) + self._cancelButton = QtGui.QPushButton('Cancel', self) self._buttonBox = QtGui.QDialogButtonBox(self) self._buttonBox.addButton(self._exportButton, QtGui.QDialogButtonBox.AcceptRole) @@ -572,6 +586,8 @@ def setExportModel(self, model): @Slot() def _createFile(self): ret = QtGui.QFileDialog.getSaveFileName(self, 'Save File', filter='Comma Separated Value (*.csv)') + if isinstance(ret, tuple): + ret = ret[0] self._filenameLineEdit.setText(ret) def _saveModel(self): @@ -586,14 +602,14 @@ def _saveModel(self): try: dataFrame = self._model.dataFrame() - except AttributeError, err: + except AttributeError as err: raise AttributeError('No data loaded to export.') else: try: dataFrame.to_csv(filename, encoding=encoding, header=header, index=index, sep=delimiter) - except IOError, err: + except IOError as err: raise IOError('No filename given') - except UnicodeError, err: + except UnicodeError as err: raise UnicodeError('Could not encode all data. Choose a different encoding') except Exception: raise @@ -620,7 +636,7 @@ def accepted(self): """ try: self._saveModel() - except Exception, err: + except Exception as err: self._statusBar.showMessage(str(err)) else: self._resetWidgets() @@ -654,8 +670,8 @@ def _calculateEncodingKey(comparator): """ encodingName = None - for k, v in _encodings.viewitems(): + for k, v in list(_encodings.items()): if v == comparator: encodingName = k break - return encodingName \ No newline at end of file + return encodingName diff --git a/pandasqt/views/CustomDelegates.py b/pandasqt/views/CustomDelegates.py index c9e472b..6c693e4 100644 --- a/pandasqt/views/CustomDelegates.py +++ b/pandasqt/views/CustomDelegates.py @@ -75,7 +75,7 @@ def createEditor(self, parent, option, index): editor.setMinimum(self.minimum) editor.setMaximum(self.maximum) editor.setSingleStep(self.singleStep) - except TypeError, err: + except TypeError as err: # initiate the editor with default values pass return editor @@ -157,7 +157,7 @@ def createEditor(self, parent, option, index): editor.setMaximum(self.maximum) editor.setSingleStep(self.singleStep) editor.setDecimals(self.decimals) - except TypeError, err: + except TypeError as err: # initiate the spinbox with default values. pass return editor @@ -225,7 +225,7 @@ def setEditorData(self, editor, index): """ if index.isValid(): value = index.model().data(index, QtCore.Qt.EditRole) - editor.setText(unicode(value)) + editor.setText(str(value)) def setModelData(self, editor, model, index): """Gets data from the editor widget and stores it in the specified model at the item index. diff --git a/pandasqt/views/DataTableView.py b/pandasqt/views/DataTableView.py index f3a827f..5c6ab8a 100644 --- a/pandasqt/views/DataTableView.py +++ b/pandasqt/views/DataTableView.py @@ -101,40 +101,40 @@ def initUi(self): self.editButton = QtGui.QToolButton(self.buttonFrame) self.editButton.setObjectName('editbutton') - self.editButton.setText(self.tr(u'edit')) - self.editButton.setToolTip(self.tr(u'toggle editing mode')) + self.editButton.setText(self.tr('edit')) + self.editButton.setToolTip(self.tr('toggle editing mode')) icon = QtGui.QIcon(QtGui.QPixmap(_fromUtf8(':/icons/document-edit.png'))) self.editButton.setIcon(icon) self.addColumnButton = QtGui.QToolButton(self.buttonFrame) self.addColumnButton.setObjectName('addcolumnbutton') - self.addColumnButton.setText(self.tr(u'+col')) - self.addColumnButton.setToolTip(self.tr(u'add new column')) + self.addColumnButton.setText(self.tr('+col')) + self.addColumnButton.setToolTip(self.tr('add new column')) icon = QtGui.QIcon(QtGui.QPixmap(_fromUtf8(':/icons/edit-table-insert-column-right.png'))) self.addColumnButton.setIcon(icon) self.addRowButton = QtGui.QToolButton(self.buttonFrame) self.addRowButton.setObjectName('addrowbutton') - self.addRowButton.setText(self.tr(u'+row')) - self.addRowButton.setToolTip(self.tr(u'add new row')) + self.addRowButton.setText(self.tr('+row')) + self.addRowButton.setToolTip(self.tr('add new row')) icon = QtGui.QIcon(QtGui.QPixmap(_fromUtf8(':/icons/edit-table-insert-row-below.png'))) self.addRowButton.setIcon(icon) self.removeColumnButton = QtGui.QToolButton(self.buttonFrame) self.removeColumnButton.setObjectName('removecolumnbutton') - self.removeColumnButton.setText(self.tr(u'-col')) - self.removeColumnButton.setToolTip(self.tr(u'remove a column')) + self.removeColumnButton.setText(self.tr('-col')) + self.removeColumnButton.setToolTip(self.tr('remove a column')) icon = QtGui.QIcon(QtGui.QPixmap(_fromUtf8(':/icons/edit-table-delete-column.png'))) self.removeColumnButton.setIcon(icon) self.removeRowButton = QtGui.QToolButton(self.buttonFrame) self.removeRowButton.setObjectName('removerowbutton') - self.removeRowButton.setText(self.tr(u'-row')) - self.removeRowButton.setToolTip(self.tr(u'remove selected rows')) + self.removeRowButton.setText(self.tr('-row')) + self.removeRowButton.setToolTip(self.tr('remove selected rows')) icon = QtGui.QIcon(QtGui.QPixmap(_fromUtf8(':/icons/edit-table-delete-row.png'))) self.removeRowButton.setIcon(icon) diff --git a/pandasqt/views/EditDialogs.py b/pandasqt/views/EditDialogs.py index c25b38e..972bde7 100644 --- a/pandasqt/views/EditDialogs.py +++ b/pandasqt/views/EditDialogs.py @@ -7,6 +7,7 @@ import numpy from pandas import Timestamp from pandas.tslib import NaTType +import warnings class DefaultValueValidator(QtGui.QValidator): def __init__(self, parent=None): @@ -44,7 +45,7 @@ def validate(self, s, pos): elif self.dtype in SupportedDtypes.datetimeTypes(): try: ts = Timestamp(s) - except ValueError, e: + except ValueError as e: return (QtGui.QValidator.Intermediate, s, pos) return (QtGui.QValidator.Acceptable, s, pos) @@ -55,7 +56,7 @@ def validate(self, s, pos): if match: try: value = int(match.string) - except ValueError, e: + except ValueError as e: return (QtGui.QValidator.Invalid, s, pos) dtypeInfo = numpy.iinfo(self.dtype) @@ -65,18 +66,18 @@ def validate(self, s, pos): if match: try: value = int(match.string) - except ValueError, e: + except ValueError as e: return (QtGui.QValidator.Invalid, s, pos) dtypeInfo = numpy.iinfo(self.dtype) elif self.dtype in SupportedDtypes.floatTypes(): match = re.search(self.floatPattern, s) - print match + print(match) if match: try: value = float(match.string) - except ValueError, e: + except ValueError as e: return (QtGui.QValidator.Invalid, s, pos) dtypeInfo = numpy.finfo(self.dtype) @@ -169,7 +170,7 @@ def accept(self): defaultValue = Timestamp('') else: defaultValue = dtype.type() - except ValueError, e: + except ValueError as e: defaultValue = dtype.type() self.accepted.emit(newColumn, dtype, defaultValue) @@ -232,4 +233,4 @@ def accept(self): names.append((position, index.data(QtCore.Qt.DisplayRole))) super(RemoveAttributesDialog, self).accept() - self.accepted.emit(names) \ No newline at end of file + self.accepted.emit(names) diff --git a/pandasqt/views/MultiFileDialogs.py b/pandasqt/views/MultiFileDialogs.py new file mode 100644 index 0000000..765f8be --- /dev/null +++ b/pandasqt/views/MultiFileDialogs.py @@ -0,0 +1,105 @@ +import os +from pandasqt.compat import QtCore +from pandasqt.views.CSVDialogs import (CSVExportDialog, CSVImportDialog, + _calculateEncodingKey, Slot, + DataFrameModel) + + + +class DataFrameExportDialog(CSVExportDialog): + """ + Extends the CSVExportDialog with support for + exporting to .txt and .xlsx + """ + signalExportFilenames = QtCore.Signal(str, str) + signalModelChanged = QtCore.Signal(DataFrameModel) + + def __init__(self, model=None, parent=None): + CSVExportDialog.__init__(self, model=None, parent=None) + if model is not None: + self._filename = model.filePath + else: + self._filename = None + self._windowTitle = "Export Data" + self.setWindowTitle(self._windowTitle) + + @Slot(DataFrameModel) + def swapModel(self, model): + good = self.setExportModel(model) + if good: + self.signalModelChanged.emit(model) + + @Slot() + def accepted(self): + """Successfully close the widget and emit an export signal. + + This method is also a `SLOT`. + The dialog will be closed, when the `Export Data` button is + pressed. If errors occur during the export, the status bar + will show the error message and the dialog will not be closed. + + """ + #return super(DataFrameExportDialog, self).accepted + try: + self._saveModel() + except Exception as err: + self._statusBar.showMessage(str(err)) + raise + else: + self._resetWidgets() + self.exported.emit(True) + self.accept() + + @Slot() + def rejected(self): + """Close the widget and reset its inital state. + + This method is also a `SLOT`. + The dialog will be closed and all changes reverted, when the + `cancel` button is pressed. + + """ + self._resetWidgets() + self.exported.emit(False) + self.reject() + + def _saveModel(self): + """ + Reimplements _saveModel to utilize all of the + Pandas export options based on file extension. + :return: None + """ + delimiter = self._delimiterBox.currentSelected() + header = self._headerCheckBox.isChecked() # column labels + if self._filename is None: + filename = self._filenameLineEdit.text() + else: + filename = self._filename + ext = os.path.splitext(filename)[1].lower() + index = False # row labels + + encodingIndex = self._encodingComboBox.currentIndex() + encoding = self._encodingComboBox.itemText(encodingIndex) + encoding = _calculateEncodingKey(encoding.lower()) + + try: + dataFrame = self._model.dataFrame() + except AttributeError as err: + raise AttributeError('No data loaded to export.') + else: + print("Identifying export type for {}".format(filename)) + try: + if ext in ['.txt','.csv']: + dataFrame.to_csv(filename, encoding=encoding, header=header, index=index, sep=delimiter) + elif ext == '.tsv': + sep = '\t' + dataFrame.to_csv(filename, encoding=encoding, header=header, index=index, sep=delimiter) + elif ext in ['.xlsx','.xls']: + dataFrame.to_excel(filename, encoding=encoding, header=header, index=index, sep=delimiter) + except IOError as err: + raise IOError('No filename given') + except UnicodeError as err: + raise UnicodeError('Could not encode all data. Choose a different encoding') + except Exception: + raise + self.signalExportFilenames.emit(self._model._filePath, filename) diff --git a/pandasqt/views/OverlayProgressView.py b/pandasqt/views/OverlayProgressView.py index 2ebce7d..958faac 100644 --- a/pandasqt/views/OverlayProgressView.py +++ b/pandasqt/views/OverlayProgressView.py @@ -95,7 +95,7 @@ def _addProgressBar(self, worker): worker.progressChanged.connect(self.debugProgressChanged) def debugProgressChanged(self, value): - print "debugProgressChanged", value + print(("debugProgressChanged", value)) def addWorker(self, worker): self._workers.append(worker) diff --git a/pandasqt/views/_ui/icons_rc.py b/pandasqt/views/_ui/icons_rc.py index 3a8c982..e60d90e 100644 --- a/pandasqt/views/_ui/icons_rc.py +++ b/pandasqt/views/_ui/icons_rc.py @@ -7,7 +7,7 @@ # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore +from pandasqt.compat import QtCore qt_resource_data = "\ \x00\x00\x0a\x69\ @@ -2026,11 +2026,12 @@ \x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x69\x47\ \x00\x00\x01\x46\x00\x00\x00\x00\x00\x01\x00\x00\x42\xbf\ " - +rsc_args = [qt_resource_struct, qt_resource_name, qt_resource_data] +rsc_args = [str.encode(s) for s in rsc_args] def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData(int(0x01), *rsc_args) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(int(0x01), *rsc_args) qInitResources() diff --git a/setup.py b/setup.py index 5c3aac4..6e4d194 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ -from __future__ import print_function + from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import io @@ -8,17 +8,25 @@ import re import sys -# TODO: sip is only needed for PyQt4, they should be imported together. +has_qt4 = True try: + # TODO: sip is only needed for PyQt4, they should be imported together. + import PyQt4 import sip except ImportError as e: - raise ImportError, "install sip first (comming with PyQt4)" + has_qt4 = False try: - import PyQt4 + import PySide except ImportError as e: # TODO: try to import PySide. - raise ImportError, "install PyQt4 or PySide" + if not has_qt4: + #We know we failed to import PyQt4/sip... + #And we failed to import pyside. + raise ImportError( "\n\ninstall PyQt4 and sip or PySide") + else: + print("Using PyQt4") + here = os.path.abspath(os.path.dirname(__file__)) @@ -40,7 +48,7 @@ def read(*filenames, **kwargs): buf.append(f.read()) return sep.join(buf) -long_description = read('README') +long_description = read('README.md') class PyTest(TestCommand): def finalize_options(self): @@ -67,10 +75,10 @@ def run_tests(self): author_email='m.Ludwig@datalyze-solutions.com', description='Utilities to use pandas (the data analysis / manipulation library for Python) with Qt.', long_description=long_description, - + include_package_data=True, packages=['pandasqt'], - + platforms='any', test_suite='tests', classifiers = [ @@ -87,4 +95,4 @@ def run_tests(self): extras_require={ 'testing': tests_require, } -) \ No newline at end of file +) diff --git a/tests/test_BigIntSpinbox.py b/tests/test_BigIntSpinbox.py index 97eefa7..0373173 100644 --- a/tests/test_BigIntSpinbox.py +++ b/tests/test_BigIntSpinbox.py @@ -29,21 +29,21 @@ def test_minimumMaximum(self, spinbox): def test_setMinimumMaximum(self, spinbox): spinbox.setMinimum(0) - spinbox.setMinimum(long(0)) + spinbox.setMinimum(int(0)) spinbox.setMinimum(1) - spinbox.setMinimum(long(1)) + spinbox.setMinimum(int(1)) spinbox.setMinimum(-1) - spinbox.setMinimum(long(-1)) + spinbox.setMinimum(int(-1)) with pytest.raises(TypeError) as excinfo: spinbox.setMinimum('') assert "int or long" in str(excinfo.value) spinbox.setMaximum(0) - spinbox.setMaximum(long(0)) + spinbox.setMaximum(int(0)) spinbox.setMaximum(1) - spinbox.setMaximum(long(1)) + spinbox.setMaximum(int(1)) spinbox.setMaximum(-1) - spinbox.setMaximum(long(-1)) + spinbox.setMaximum(int(-1)) with pytest.raises(TypeError) as excinfo: spinbox.setMaximum('') assert "int or long" in str(excinfo.value) diff --git a/tests/test_CSVDialogs.py b/tests/test_CSVDialogs.py index 61f5900..d1fbfe7 100644 --- a/tests/test_CSVDialogs.py +++ b/tests/test_CSVDialogs.py @@ -92,18 +92,18 @@ def test_init(self, qtbot): qtbot.addWidget(csvwidget) csvwidget.show() assert csvwidget.isModal() - assert csvwidget.windowTitle() == u'Import CSV' + assert csvwidget.windowTitle() == 'Import CSV' def test_fileinput(self, qtbot, csv_file): csvwidget = CSVImportDialog() qtbot.addWidget(csvwidget) csvwidget.show() labels = csvwidget.findChildren(QtGui.QLabel) - assert labels[0].text() == u'Choose File' + assert labels[0].text() == 'Choose File' lineedits = csvwidget.findChildren(QtGui.QLineEdit) qtbot.keyClicks(lineedits[0], csv_file) assert csvwidget._previewTableView.model() is not None - assert csvwidget._delimiter == u';' + assert csvwidget._delimiter == ';' assert csvwidget._header is None def test_header(self, qtbot): @@ -180,7 +180,7 @@ def _assert(x, path): assert x assert isinstance(x, DataFrameModel) assert path - assert isinstance(path, basestring) + assert isinstance(path, str) csvwidget.load.connect(_assert) with qtbot.waitSignal(csvwidget.load): @@ -193,14 +193,14 @@ def test_init(self, qtbot): qtbot.addWidget(csvwidget) csvwidget.show() assert csvwidget.isModal() - assert csvwidget.windowTitle() == u'Export to CSV' + assert csvwidget.windowTitle() == 'Export to CSV' def test_fileoutput(self, qtbot, csv_file): csvwidget = CSVExportDialog() qtbot.addWidget(csvwidget) csvwidget.show() labels = csvwidget.findChildren(QtGui.QLabel) - assert labels[0].text() == u'Output File' + assert labels[0].text() == 'Output File' lineedits = csvwidget.findChildren(QtGui.QLineEdit) qtbot.keyClicks(lineedits[0], csv_file) assert csvwidget._filenameLineEdit.text() == csv_file diff --git a/tests/test_DataFrameModel.py b/tests/test_DataFrameModel.py index d1ade75..2dc0b6c 100644 --- a/tests/test_DataFrameModel.py +++ b/tests/test_DataFrameModel.py @@ -34,7 +34,7 @@ def test_setDataFrame(): with pytest.raises(TypeError) as excinfo: model.setDataFrame(None) - assert "pandas.core.frame.DataFrame" in unicode(excinfo.value) + assert "pandas.core.frame.DataFrame" in str(excinfo.value) @pytest.mark.parametrize( "copy, operator", @@ -54,13 +54,13 @@ def test_copyDataFrame(copy, operator): def test_TimestampFormat(): model = DataFrameModel() assert model.timestampFormat == Qt.ISODate - newFormat = u"yy-MM-dd hh:mm" + newFormat = "yy-MM-dd hh:mm" model.timestampFormat = newFormat assert model.timestampFormat == newFormat with pytest.raises(TypeError) as excinfo: model.timestampFormat = "yy-MM-dd hh:mm" - assert "unicode" in unicode(excinfo.value) + assert "unicode" in str(excinfo.value) #def test_signalUpdate(qtbot): #model = DataFrameModel() @@ -194,7 +194,7 @@ def test_unhandledDtype(self, model, index): @pytest.mark.parametrize( "value, dtype", [ ("test", object), - (u"äöü", object), + ("äöü", object), ] ) def test_strAndUnicode(self, model, index, value, dtype): @@ -348,19 +348,19 @@ def test_unhandledDtype(self, model, index): model.enableEditing(True) with pytest.raises(TypeError) as excinfo: model.setData(index, numpy.complex64(92+151j)) - assert "unhandled data type" in unicode(excinfo.value) + assert "unhandled data type" in str(excinfo.value) @pytest.mark.parametrize( "value, dtype", [ ("test", object), - (u"äöü", object), + ("äöü", object), ] ) def test_strAndUnicode(self, model, index, value, dtype): dataFrame = pandas.DataFrame([value], columns=['A']) dataFrame['A'] = dataFrame['A'].astype(dtype) model.setDataFrame(dataFrame) - newValue = u"{}123".format(value) + newValue = "{}123".format(value) model.enableEditing(True) assert model.setData(index, newValue) assert model.data(index) == newValue @@ -611,7 +611,7 @@ def test_add_column(self, model, newColumns): defaultVal = _type() assert model.addDataFrameColumn(desc, _type, defaultVal) - for row in xrange(rowCount): + for row in range(rowCount): idx = model.index(row, columnCount + index) newVal = idx.data(DATAFRAME_ROLE) assert newVal == defaultVal @@ -642,7 +642,7 @@ def test_remove_columns_random(self, dataFrame): columnNames = dataFrame.columns.tolist() columnNames = [(i, n) for i, n in enumerate(columnNames)] - for cycle in xrange(1000): + for cycle in range(1000): elements = random.randint(1, len(columnNames)) names = random.sample(columnNames, elements) df = dataFrame.copy() diff --git a/tests/test_DataTableView.py b/tests/test_DataTableView.py index d0dab75..303e391 100644 --- a/tests/test_DataTableView.py +++ b/tests/test_DataTableView.py @@ -145,7 +145,7 @@ def test_addColumn(self, qtbot, dataModel): dlg_buttons = dlg.findChildren(QtGui.QPushButton) comboBox = dlg.findChildren(QtGui.QComboBox)[-1] - for i in xrange(comboBox.count()): + for i in range(comboBox.count()): columns.append(comboBox.itemText(i)) for b in dlg_buttons: diff --git a/tests/test_SupportedDtypes.py b/tests/test_SupportedDtypes.py index f22871c..4254d34 100644 --- a/tests/test_SupportedDtypes.py +++ b/tests/test_SupportedDtypes.py @@ -43,7 +43,7 @@ def test_description(self, expected_support, obj): for datatype in expected_support: assert obj.description(datatype) is not None - from StringIO import StringIO + from io import StringIO s = StringIO() assert obj.description(s) is None assert obj.description(str) is None diff --git a/tests/test_excepthook.py b/tests/test_excepthook.py index 9db8a1e..fa8049a 100644 --- a/tests/test_excepthook.py +++ b/tests/test_excepthook.py @@ -10,16 +10,16 @@ # TODO write it with pytest... def exception(): - raise ValueError, "Test Test ä" + raise ValueError("Test Test ä") def exception2(): - raise ValueError, u"Test Test ä" + raise ValueError("Test Test ä") def exception3(): - raise ValueError, u"Test Test" + raise ValueError("Test Test") def exception4(): - raise ValueError, "Test Test" + raise ValueError("Test Test") app = QtGui.QApplication([]) sys.excepthook = excepthook