diff --git a/.github/release-drafter-config.yml b/.github/release-drafter-config.yml new file mode 100644 index 00000000..0a045266 --- /dev/null +++ b/.github/release-drafter-config.yml @@ -0,0 +1,6 @@ +name-template: 'v$NEXT_PATCH_VERSION 🌈' +tag-template: 'v$NEXT_PATCH_VERSION' +template: | + ## What’s Changed + + $CHANGES \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..2c8cd923 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: [2.7, 3.5, 3.7, 3.8] + include: + - python-version: 2.7 + pandas: 0.18.1 + numpy: 1.11.3 + - python-version: 3.5 + pandas: 0.18.1 + numpy: 1.11.3 + - python-version: 3.7 + pandas: 1.0.1 + numpy: 1.18.1 + - python-version: 3.8 + pandas: 1.0.1 + numpy: 1.18.1 + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pandas }}-${{ matrix.numpy }}-pip-${{ hashFiles('**/setup.py') }} + - name: Install dependencies + env: + PYTHONWARNINGS: ignore:DEPRECATION::pip._internal.cli.base_command + run: | + python -m pip install --upgrade pip + pip install pandas==${{ matrix.pandas }} numpy==${{ matrix.numpy }} + pip install -e .[test] + - name: Lint with flake8 + run: | + flake8 + - name: Run the tests + run: | + pytest \ No newline at end of file diff --git a/.github/workflows/master-merge.yml b/.github/workflows/master-merge.yml new file mode 100644 index 00000000..d25a5cb1 --- /dev/null +++ b/.github/workflows/master-merge.yml @@ -0,0 +1,18 @@ +name: On Master Merge + +on: + push: + branches: + - master + +jobs: + draft-release-publish: + name: Draft a new release + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-config.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7122e077 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Publish Python 🐍 distributions πŸ“¦ to PyPI +on: + release: + types: [published] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions πŸ“¦ to TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + + - name: Build sdist + run: python setup.py sdist + + - name: Publish distribution πŸ“¦ to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.test_pypi_password }} + repository_url: https://test.pypi.org/legacy/ + + - name: Install from test and test running + run: | + python -m pip install --upgrade pip + pip install --extra-index-url https://test.pypi.org/simple qgrid + python -c 'import qgrid;print(qgrid.__version__)' + pip uninstall -y qgrid + + - name: Publish distribution πŸ“¦ to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.pypi_password }} + + - name: Install and test running + run: | + python -m pip install --upgrade pip + pip install --extra-index-url qgrid + python -c 'import qgrid;print(qgrid.__version__)' diff --git a/qgrid/__init__.py b/qgrid/__init__.py index d5d62b2c..f103c8b6 100644 --- a/qgrid/__init__.py +++ b/qgrid/__init__.py @@ -1,4 +1,4 @@ -from ._version import version_info, __version__ +from ._version import version_info, __version__ # noqa F401 from .grid import ( enable, @@ -9,25 +9,29 @@ set_grid_option, show_grid, QgridWidget, - QGridWidget + QGridWidget, ) + def _jupyter_nbextension_paths(): - return [{ - 'section': 'notebook', - 'src': 'static', - 'dest': 'qgrid', - 'require': 'qgrid/extension' - }] + return [ + { + "section": "notebook", + "src": "static", + "dest": "qgrid", + "require": "qgrid/extension", + } + ] + __all__ = [ - 'enable', - 'disable', - 'set_defaults', - 'on', - 'off', - 'set_grid_option', - 'show_grid', - 'QgridWidget', - 'QGridWidget' + "enable", + "disable", + "set_defaults", + "on", + "off", + "set_grid_option", + "show_grid", + "QgridWidget", + "QGridWidget", ] diff --git a/qgrid/_version.py b/qgrid/_version.py index 5ed46518..6669572a 100644 --- a/qgrid/_version.py +++ b/qgrid/_version.py @@ -1,6 +1,12 @@ -version_info = (1, 2, 0, 'final') +version_info = (1, 3, 0, "final") -_specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''} +_specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""} -__version__ = '%s.%s.%s%s'%(version_info[0], version_info[1], version_info[2], - '' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4])) +__version__ = "%s.%s.%s%s" % ( + version_info[0], + version_info[1], + version_info[2], + "" + if version_info[3] == "final" + else _specifier_[version_info[3]] + str(version_info[4]), +) diff --git a/qgrid/grid.py b/qgrid/grid.py index e2b1364a..66366533 100644 --- a/qgrid/grid.py +++ b/qgrid/grid.py @@ -856,7 +856,7 @@ def _update_table(self, to_index = max(self._viewport_range[0] + PAGE_SIZE, 0) new_df_range = (from_index, to_index) - if triggered_by is 'viewport_changed' and \ + if triggered_by == 'viewport_changed' and \ self._df_range == new_df_range: return @@ -1431,12 +1431,15 @@ def _handle_qgrid_msg_helper(self, content): try: location = (self._df.index[content['row_index']], content['column']) + old_value = self._df.loc[location] val_to_set = content['value'] if col_info['type'] == 'datetime': val_to_set = pd.to_datetime(val_to_set) + # pandas > 18.0 compat + if old_value.tz != val_to_set.tz: + val_to_set = val_to_set.tz_convert(tz=old_value.tz) - old_value = self._df.loc[location] self._df.loc[location] = val_to_set query = self._unfiltered_df[self._index_col_name] == \ diff --git a/qgrid/pd_json/json.py b/qgrid/pd_json/json.py index 7737a6c4..8fb184a7 100644 --- a/qgrid/pd_json/json.py +++ b/qgrid/pd_json/json.py @@ -458,7 +458,7 @@ def _try_convert_data(self, name, data, use_dtypes=True, try: dtype = np.dtype(dtype) return data.astype(dtype), True - except: + except Exception: return data, False if convert_dates: @@ -474,7 +474,7 @@ def _try_convert_data(self, name, data, use_dtypes=True, try: data = data.astype('float64') result = True - except: + except Exception: pass if data.dtype.kind == 'f': @@ -485,7 +485,7 @@ def _try_convert_data(self, name, data, use_dtypes=True, try: data = data.astype('float64') result = True - except: + except Exception: pass # do't coerce 0-len data @@ -497,7 +497,7 @@ def _try_convert_data(self, name, data, use_dtypes=True, if (new_data == data).all(): data = new_data result = True - except: + except Exception: pass # coerce ints to 64 @@ -507,7 +507,7 @@ def _try_convert_data(self, name, data, use_dtypes=True, try: data = data.astype('int64') result = True - except: + except Exception: pass return data, result @@ -526,7 +526,7 @@ def _try_convert_to_date(self, data): if new_data.dtype == 'object': try: new_data = data.astype('int64') - except: + except Exception: pass # ignore numbers that are out of range @@ -543,7 +543,7 @@ def _try_convert_to_date(self, data): unit=date_unit) except ValueError: continue - except: + except Exception: break return new_data, True return data, False @@ -649,12 +649,9 @@ def _parse_no_numpy(self): self.obj = DataFrame( loads(json, precise_float=self.precise_float), dtype=None) - def _process_converter(self, f, filt=None): + def _process_converter(self, f, filt=lambda col, c: True): """ take a conversion function and possibly recreate the frame """ - if filt is None: - filt = lambda col, c: True - needs_new_obj = False new_obj = dict() for i, (col, c) in enumerate(self.obj.iteritems()): diff --git a/qgrid/pd_json/normalize.py b/qgrid/pd_json/normalize.py index e74523f6..211c40b5 100644 --- a/qgrid/pd_json/normalize.py +++ b/qgrid/pd_json/normalize.py @@ -171,7 +171,7 @@ def _pull_field(js, spec): return result - if isinstance(data, list) and len(data) is 0: + if isinstance(data, list) and len(data) == 0: return DataFrame() # A bit of a hackjob diff --git a/qgrid/tests/__init__.py b/qgrid/tests/__init__.py index 8b137891..e69de29b 100644 --- a/qgrid/tests/__init__.py +++ b/qgrid/tests/__init__.py @@ -1 +0,0 @@ - diff --git a/qgrid/tests/test_grid.py b/qgrid/tests/test_grid.py index d244796e..b1cf8867 100644 --- a/qgrid/tests/test_grid.py +++ b/qgrid/tests/test_grid.py @@ -1,9 +1,4 @@ -from qgrid import ( - QgridWidget, - set_defaults, - show_grid, - on as qgrid_on -) +from qgrid import QgridWidget, set_defaults, show_grid, on as qgrid_on from traitlets import All import numpy as np import pandas as pd @@ -11,41 +6,42 @@ def create_df(): - return pd.DataFrame({ - 'A': 1., - 'Date': pd.Timestamp('20130102'), - 'C': pd.Series(1, index=list(range(4)), dtype='float32'), - 'D': np.array([3] * 4, dtype='int32'), - 'E': pd.Categorical(["test", "train", "foo", "bar"]), - 'F': ['foo', 'bar', 'buzz', 'fox'] - }) + return pd.DataFrame( + { + "A": 1.0, + "Date": pd.Timestamp("20130102"), + "C": pd.Series(1, index=list(range(4)), dtype="float32"), + "D": np.array([3] * 4, dtype="int32"), + "E": pd.Categorical(["test", "train", "foo", "bar"]), + "F": ["foo", "bar", "buzz", "fox"], + } + ) def create_large_df(size=10000): - large_df = pd.DataFrame(np.random.randn(size, 4), columns=list('ABCD')) - large_df['B (as str)'] = large_df['B'].map(lambda x: str(x)) + large_df = pd.DataFrame(np.random.randn(size, 4), columns=list("ABCD")) + large_df["B (as str)"] = large_df["B"].map(lambda x: str(x)) return large_df def create_multi_index_df(): - arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], - ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']] + arrays = [ + ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], + ["one", "two", "one", "two", "one", "two", "one", "two"], + ] return pd.DataFrame(np.random.randn(8, 4), index=arrays) def create_interval_index_df(): - td = np.cumsum(np.random.randint(1, 15*60, 1000)) - start = pd.Timestamp('2017-04-17') + td = np.cumsum(np.random.randint(1, 15 * 60, 1000)) + start = pd.Timestamp("2017-04-17") df = pd.DataFrame( [(start + pd.Timedelta(seconds=d)) for d in td], - columns=['time'] + columns=["time"], + dtype="M8[ns]", ) - freq = '15Min' - start = df['time'].min().floor(freq) - end = df['time'].max().ceil(freq) - bins = pd.date_range(start, end, freq=freq) - df['time_bin'] = pd.cut(df['time'], bins) + df["time_bin"] = np.cumsum(np.random.randint(1, 15 * 60, 1000)) return df @@ -65,65 +61,75 @@ def on_change(event, qgrid_widget): def test_edit_date(): view = QgridWidget(df=create_df()) - check_edit_success(view, - 'Date', - 3, - pd.Timestamp('2013-01-02 00:00:00'), - "2013-01-02T00:00:00.000Z", - pd.Timestamp('2013-01-16 00:00:00'), - "2013-01-16T00:00:00.000Z") + check_edit_success( + view, + "Date", + 3, + pd.Timestamp("2013-01-02 00:00:00"), + "2013-01-02T00:00:00.000Z", + pd.Timestamp("2013-01-16 00:00:00"), + "2013-01-16T00:00:00.000Z", + ) def test_edit_multi_index_df(): df_multi = create_multi_index_df() - df_multi.index.set_names('first', level=0, inplace=True) + df_multi.index.set_names("first", level=0, inplace=True) view = QgridWidget(df=df_multi) - old_val = df_multi.loc[('bar', 'two'), 1] - - check_edit_success(view, - 1, - 1, - old_val, - round(old_val, pd.get_option('display.precision') - 1), - 3.45678, - 3.45678) + old_val = df_multi.loc[("bar", "two"), 1] + + check_edit_success( + view, + 1, + 1, + old_val, + round(old_val, pd.get_option("display.precision") - 1), + 3.45678, + 3.45678, + ) -def check_edit_success(widget, - col_name, - row_index, - old_val_obj, - old_val_json, - new_val_obj, - new_val_json): +def check_edit_success( + widget, + col_name, + row_index, + old_val_obj, + old_val_json, + new_val_obj, + new_val_json, +): - event_history = init_event_history('cell_edited', widget) + event_history = init_event_history("cell_edited", widget) - grid_data = json.loads(widget._df_json)['data'] + grid_data = json.loads(widget._df_json)["data"] assert grid_data[row_index][str(col_name)] == old_val_json - widget._handle_qgrid_msg_helper({ - 'column': col_name, - 'row_index': row_index, - 'type': 'edit_cell', - 'unfiltered_index': row_index, - 'value': new_val_json - }) + widget._handle_qgrid_msg_helper( + { + "column": col_name, + "row_index": row_index, + "type": "edit_cell", + "unfiltered_index": row_index, + "value": new_val_json, + } + ) expected_index_val = widget._df.index[row_index] - assert event_history == [{ - 'name': 'cell_edited', - 'index': expected_index_val, - 'column': col_name, - 'old': old_val_obj, - 'new': new_val_obj, - 'source': 'gui' - }] + assert event_history == [ + { + "name": "cell_edited", + "index": expected_index_val, + "column": col_name, + "old": old_val_obj, + "new": new_val_obj, + "source": "gui", + } + ] assert widget._df[col_name][row_index] == new_val_obj # call _update_table so the widget updates _df_json widget._update_table(fire_data_change_event=False) - grid_data = json.loads(widget._df_json)['data'] + grid_data = json.loads(widget._df_json)["data"] assert grid_data[row_index][str(col_name)] == new_val_json @@ -132,155 +138,127 @@ def test_edit_number(): view = QgridWidget(df=create_df()) for idx in range(-10, 10, 1): - check_edit_success(view, 'D', 2, old_val, old_val, idx, idx) + check_edit_success(view, "D", 2, old_val, old_val, idx, idx) old_val = idx def test_add_row_button(): widget = QgridWidget(df=create_df()) - event_history = init_event_history('row_added', widget=widget) + event_history = init_event_history("row_added", widget=widget) - widget._handle_qgrid_msg_helper({ - 'type': 'add_row' - }) + widget._handle_qgrid_msg_helper({"type": "add_row"}) - assert event_history == [{ - 'name': 'row_added', - 'index': 4, - 'source': 'gui' - }] + assert event_history == [ + {"name": "row_added", "index": 4, "source": "gui"} + ] # make sure the added row in the internal dataframe contains the # expected values - added_index = event_history[0]['index'] - expected_values = np.array( - [4, 1.0, pd.Timestamp('2013-01-02 00:00:00'), 1.0, 3, 'bar', 'fox'], - dtype=object - ) - assert (widget._df.loc[added_index].values == expected_values).all() + added_index = event_history[0]["index"] + expected_values = pd.Series({ + "qgrid_unfiltered_index": 4, + "A": 1, + "C": 1, + "D": 3, + "Date": pd.Timestamp("2013-01-02 00:00:00"), + "E": "bar", + "F": "fox" + }) + sort_idx = widget._df.loc[added_index].index + assert (widget._df.loc[added_index] == expected_values[sort_idx]).all() def test_remove_row_button(): widget = QgridWidget(df=create_df()) - event_history = init_event_history(['row_removed', 'selection_changed'], - widget=widget) + event_history = init_event_history( + ["row_removed", "selection_changed"], widget=widget + ) selected_rows = [1, 2] - widget._handle_qgrid_msg_helper({ - 'rows': selected_rows, - 'type': "change_selection" - }) + widget._handle_qgrid_msg_helper( + {"rows": selected_rows, "type": "change_selection"} + ) - widget._handle_qgrid_msg_helper({ - 'type': 'remove_row' - }) + widget._handle_qgrid_msg_helper({"type": "remove_row"}) assert event_history == [ { - 'name': 'selection_changed', - 'old': [], - 'new': selected_rows, - 'source': 'gui' + "name": "selection_changed", + "old": [], + "new": selected_rows, + "source": "gui", }, - { - 'name': 'row_removed', - 'indices': selected_rows, - 'source': 'gui' - } + {"name": "row_removed", "indices": selected_rows, "source": "gui"}, ] def test_mixed_type_column(): - df = pd.DataFrame({'A': [1.2, 'xy', 4], 'B': [3, 4, 5]}) - df = df.set_index(pd.Index(['yz', 7, 3.2])) + df = pd.DataFrame({"A": [1.2, "xy", 4], "B": [3, 4, 5]}) + df = df.set_index(pd.Index(["yz", 7, 3.2])) view = QgridWidget(df=df) - view._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 'A', - 'sort_ascending': True - }) - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'A', - 'search_val': None - }) + view._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": "A", "sort_ascending": True} + ) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "A", "search_val": None} + ) def test_nans(): - df = pd.DataFrame([(pd.Timestamp('2017-02-02'), np.nan), - (4, 2), - ('foo', 'bar')]) + df = pd.DataFrame( + [(pd.Timestamp("2017-02-02"), np.nan), (4, 2), ("foo", "bar")] + ) view = QgridWidget(df=df) - view._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 1, - 'sort_ascending': True - }) - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 1, - 'search_val': None - }) + view._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": 1, "sort_ascending": True} + ) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": 1, "search_val": None} + ) def test_row_edit_callback(): sample_df = create_df() def can_edit_row(row): - return row['E'] == 'train' and row['F'] == 'bar' + return row["E"] == "train" and row["F"] == "bar" view = QgridWidget(df=sample_df, row_edit_callback=can_edit_row) - view._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 'index', - 'sort_ascending': True - }) + view._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": "index", "sort_ascending": True} + ) - expected_dict = { - 0: False, - 1: True, - 2: False, - 3: False - } + expected_dict = {0: False, 1: True, 2: False, 3: False} assert expected_dict == view._editable_rows def test_period_object_column(): - range_index = pd.period_range(start='2000', periods=10, freq='B') - df = pd.DataFrame({'a': 5, 'b': range_index}, index=range_index) + range_index = pd.period_range(start="2000", periods=10, freq="B") + df = pd.DataFrame({"a": 5, "b": range_index}, index=range_index) view = QgridWidget(df=df) - view._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 'index', - 'sort_ascending': True - }) - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'index', - 'search_val': None - }) - view._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 'b', - 'sort_ascending': True - }) - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'b', - 'search_val': None - }) + view._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": "index", "sort_ascending": True} + ) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "index", "search_val": None} + ) + view._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": "b", "sort_ascending": True} + ) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "b", "search_val": None} + ) def test_get_selected_df(): sample_df = create_df() selected_rows = [1, 3] view = QgridWidget(df=sample_df) - view._handle_qgrid_msg_helper({ - 'rows': selected_rows, - 'type': "change_selection" - }) + view._handle_qgrid_msg_helper( + {"rows": selected_rows, "type": "change_selection"} + ) selected_df = view.get_selected_df() assert len(selected_df) == 2 assert sample_df.iloc[selected_rows].equals(selected_df) @@ -288,216 +266,201 @@ def test_get_selected_df(): def test_integer_index_filter(): view = QgridWidget(df=create_df()) - view._handle_qgrid_msg_helper({ - 'field': "index", - 'filter_info': { - 'field': "index", - 'max': None, - 'min': 2, - 'type': "slider" - }, - 'type': "change_filter" - }) + view._handle_qgrid_msg_helper( + { + "field": "index", + "filter_info": { + "field": "index", + "max": None, + "min": 2, + "type": "slider", + }, + "type": "change_filter", + } + ) filtered_df = view.get_changed_df() assert len(filtered_df) == 2 def test_series_of_text_filters(): view = QgridWidget(df=create_df()) - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'E', - 'search_val': None - }) - view._handle_qgrid_msg_helper({ - 'field': "E", - 'filter_info': { - 'field': "E", - 'selected': [0, 1], - 'type': "text", - 'excluded': [] - }, - 'type': "change_filter" - }) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "E", "search_val": None} + ) + view._handle_qgrid_msg_helper( + { + "field": "E", + "filter_info": { + "field": "E", + "selected": [0, 1], + "type": "text", + "excluded": [], + }, + "type": "change_filter", + } + ) filtered_df = view.get_changed_df() assert len(filtered_df) == 2 # reset the filter... - view._handle_qgrid_msg_helper({ - 'field': "E", - 'filter_info': { - 'field': "E", - 'selected': None, - 'type': "text", - 'excluded': [] - }, - 'type': "change_filter" - }) + view._handle_qgrid_msg_helper( + { + "field": "E", + "filter_info": { + "field": "E", + "selected": None, + "type": "text", + "excluded": [], + }, + "type": "change_filter", + } + ) # ...and apply a text filter on a different column - view._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'F', - 'search_val': None - }) - view._handle_qgrid_msg_helper({ - 'field': "F", - 'filter_info': { - 'field': "F", - 'selected': [0, 1], - 'type': "text", - 'excluded': [] - }, - 'type': "change_filter" - }) + view._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "F", "search_val": None} + ) + view._handle_qgrid_msg_helper( + { + "field": "F", + "filter_info": { + "field": "F", + "selected": [0, 1], + "type": "text", + "excluded": [], + }, + "type": "change_filter", + } + ) filtered_df = view.get_changed_df() assert len(filtered_df) == 2 def test_date_index(): df = create_df() - df.set_index('Date', inplace=True) + df.set_index("Date", inplace=True) view = QgridWidget(df=df) - view._handle_qgrid_msg_helper({ - 'type': 'change_filter', - 'field': 'A', - 'filter_info': { - 'field': 'A', - 'type': 'slider', - 'min': 2, - 'max': 3 + view._handle_qgrid_msg_helper( + { + "type": "change_filter", + "field": "A", + "filter_info": { + "field": "A", + "type": "slider", + "min": 2, + "max": 3, + }, } - }) + ) def test_multi_index(): widget = QgridWidget(df=create_multi_index_df()) - event_history = init_event_history(['filter_dropdown_shown', - 'filter_changed', - 'sort_changed'], widget=widget) - - widget._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'level_0', - 'search_val': None - }) + event_history = init_event_history( + ["filter_dropdown_shown", "filter_changed", "sort_changed"], + widget=widget, + ) - widget._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 3, - 'search_val': None - }) + widget._handle_qgrid_msg_helper( + { + "type": "show_filter_dropdown", + "field": "level_0", + "search_val": None, + } + ) + + widget._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": 3, "search_val": None} + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_filter', - 'field': 3, - 'filter_info': { - 'field': 3, - 'type': 'slider', - 'min': -0.111, - 'max': None + widget._handle_qgrid_msg_helper( + { + "type": "change_filter", + "field": 3, + "filter_info": { + "field": 3, + "type": "slider", + "min": -0.111, + "max": None, + }, } - }) + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_filter', - 'field': 3, - 'filter_info': { - 'field': 3, - 'type': 'slider', - 'min': None, - 'max': None + widget._handle_qgrid_msg_helper( + { + "type": "change_filter", + "field": 3, + "filter_info": { + "field": 3, + "type": "slider", + "min": None, + "max": None, + }, } - }) + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_filter', - 'field': 'level_1', - 'filter_info': { - 'field': 'level_1', - 'type': 'text', - 'selected': [0], - 'excluded': [] + widget._handle_qgrid_msg_helper( + { + "type": "change_filter", + "field": "level_1", + "filter_info": { + "field": "level_1", + "type": "text", + "selected": [0], + "excluded": [], + }, } - }) + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 3, - 'sort_ascending': True - }) + widget._handle_qgrid_msg_helper( + {"type": "change_sort", "sort_field": 3, "sort_ascending": True} + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_sort', - 'sort_field': 'level_0', - 'sort_ascending': True - }) + widget._handle_qgrid_msg_helper( + { + "type": "change_sort", + "sort_field": "level_0", + "sort_ascending": True, + } + ) assert event_history == [ + {"name": "filter_dropdown_shown", "column": "level_0"}, + {"name": "filter_dropdown_shown", "column": 3}, + {"name": "filter_changed", "column": 3}, + {"name": "filter_changed", "column": 3}, + {"name": "filter_changed", "column": "level_1"}, { - 'name': 'filter_dropdown_shown', - 'column': 'level_0' + "name": "sort_changed", + "old": {"column": None, "ascending": True}, + "new": {"column": 3, "ascending": True}, }, { - 'name': 'filter_dropdown_shown', - 'column': 3 + "name": "sort_changed", + "old": {"column": 3, "ascending": True}, + "new": {"column": "level_0", "ascending": True}, }, - { - 'name': 'filter_changed', - 'column': 3 - }, - { - 'name': 'filter_changed', - 'column': 3 - }, - { - 'name': 'filter_changed', - 'column': 'level_1' - }, - { - 'name': 'sort_changed', - 'old': { - 'column': None, - 'ascending': True - }, - 'new': { - 'column': 3, - 'ascending': True - } - }, - { - 'name': 'sort_changed', - 'old': - { - 'column': 3, - 'ascending': True - }, - 'new': - { - 'column': 'level_0', - 'ascending': True - } - } ] def test_interval_index(): df = create_interval_index_df() - df.set_index('time_bin', inplace=True) + df.set_index("time_bin", inplace=True) show_grid(df) def test_multi_interval_index(): df = create_interval_index_df() - df['A'] = np.array([3] * 1000, dtype='int32') - df.set_index(['time', 'time_bin'], inplace=True) + df["A"] = np.array([3] * 1000, dtype="int32") + df.set_index(["time", "time_bin"], inplace=True) show_grid(df) def test_set_defaults(): - fake_grid_options_a = {'foo': 'bar'} - set_defaults(show_toolbar=False, precision=4, - grid_options=fake_grid_options_a) + fake_grid_options_a = {"foo": "bar"} + set_defaults( + show_toolbar=False, precision=4, grid_options=fake_grid_options_a + ) def assert_widget_vals_a(widget): assert not widget.show_toolbar @@ -511,9 +474,10 @@ def assert_widget_vals_a(widget): view = QgridWidget(df=df) assert_widget_vals_a(view) - fake_grid_options_b = {'foo': 'buzz'} - set_defaults(show_toolbar=True, precision=2, - grid_options=fake_grid_options_b) + fake_grid_options_b = {"foo": "buzz"} + set_defaults( + show_toolbar=True, precision=2, grid_options=fake_grid_options_b + ) def assert_widget_vals_b(widget): assert widget.show_toolbar @@ -537,76 +501,74 @@ def __init__(self, obj): def test_object_dtype(): - df = pd.DataFrame({'a': my_object_vals}, index=my_object_vals) + df = pd.DataFrame({"a": my_object_vals}, index=my_object_vals) widget = QgridWidget(df=df) - grid_data = json.loads(widget._df_json)['data'] + grid_data = json.loads(widget._df_json)["data"] - widget._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'a', - 'search_val': None - }) - widget._handle_qgrid_msg_helper({ - 'field': "a", - 'filter_info': { - 'field': "a", - 'selected': [0, 1], - 'type': "text", - 'excluded': [] - }, - 'type': "change_filter" - }) + widget._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": "a", "search_val": None} + ) + widget._handle_qgrid_msg_helper( + { + "field": "a", + "filter_info": { + "field": "a", + "selected": [0, 1], + "type": "text", + "excluded": [], + }, + "type": "change_filter", + } + ) - filter_table = widget._filter_tables['a'] + filter_table = widget._filter_tables["a"] assert not isinstance(filter_table[0], dict) assert not isinstance(filter_table[1], dict) - assert not isinstance(grid_data[0]['a'], dict) - assert not isinstance(grid_data[1]['a'], dict) + assert not isinstance(grid_data[0]["a"], dict) + assert not isinstance(grid_data[1]["a"], dict) - assert not isinstance(grid_data[0]['index'], dict) - assert not isinstance(grid_data[1]['index'], dict) + assert not isinstance(grid_data[0]["index"], dict) + assert not isinstance(grid_data[1]["index"], dict) def test_index_categorical(): - df = pd.DataFrame({ - 'foo': np.random.randn(3), - 'future_index': [22, 13, 87] - }) - df['future_index'] = df['future_index'].astype('category') - df = df.set_index('future_index') + df = pd.DataFrame( + {"foo": np.random.randn(3), "future_index": [22, 13, 87]} + ) + df["future_index"] = df["future_index"].astype("category") + df = df.set_index("future_index") widget = QgridWidget(df=df) - grid_data = json.loads(widget._df_json)['data'] + grid_data = json.loads(widget._df_json)["data"] - assert not isinstance(grid_data[0]['future_index'], dict) - assert not isinstance(grid_data[1]['future_index'], dict) + assert not isinstance(grid_data[0]["future_index"], dict) + assert not isinstance(grid_data[1]["future_index"], dict) def test_object_dtype_categorical(): cat_series = pd.Series( - pd.Categorical(my_object_vals, - categories=my_object_vals) + pd.Categorical(my_object_vals, categories=my_object_vals) ) widget = show_grid(cat_series) - constraints_enum = widget._columns[0]['constraints']['enum'] + constraints_enum = widget._columns[0]["constraints"]["enum"] assert not isinstance(constraints_enum[0], dict) assert not isinstance(constraints_enum[1], dict) - widget._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 0, - 'search_val': None - }) - widget._handle_qgrid_msg_helper({ - 'field': 0, - 'filter_info': { - 'field': 0, - 'selected': [0], - 'type': "text", - 'excluded': [] - }, - 'type': "change_filter" - }) + widget._handle_qgrid_msg_helper( + {"type": "show_filter_dropdown", "field": 0, "search_val": None} + ) + widget._handle_qgrid_msg_helper( + { + "field": 0, + "filter_info": { + "field": 0, + "selected": [0], + "type": "text", + "excluded": [], + }, + "type": "change_filter", + } + ) assert len(widget._df) == 1 assert widget._df[0][0] == cat_series[0] @@ -615,23 +577,17 @@ def test_change_viewport(): widget = QgridWidget(df=create_large_df()) event_history = init_event_history(All) - widget._handle_qgrid_msg_helper({ - 'type': 'change_viewport', - 'top': 7124, - 'bottom': 7136 - }) + widget._handle_qgrid_msg_helper( + {"type": "change_viewport", "top": 7124, "bottom": 7136} + ) assert event_history == [ { - 'name': 'json_updated', - 'triggered_by': 'change_viewport', - 'range': (7024, 7224) + "name": "json_updated", + "triggered_by": "change_viewport", + "range": (7024, 7224), }, - { - 'name': 'viewport_changed', - 'old': (0, 100), - 'new': (7124, 7136) - } + {"name": "viewport_changed", "old": (0, 100), "new": (7124, 7136)}, ] @@ -639,60 +595,59 @@ def test_change_filter_viewport(): widget = QgridWidget(df=create_large_df()) event_history = init_event_history(All) - widget._handle_qgrid_msg_helper({ - 'type': 'show_filter_dropdown', - 'field': 'B (as str)', - 'search_val': None - }) + widget._handle_qgrid_msg_helper( + { + "type": "show_filter_dropdown", + "field": "B (as str)", + "search_val": None, + } + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_filter_viewport', - 'field': 'B (as str)', - 'top': 556, - 'bottom': 568 - }) + widget._handle_qgrid_msg_helper( + { + "type": "change_filter_viewport", + "field": "B (as str)", + "top": 556, + "bottom": 568, + } + ) - widget._handle_qgrid_msg_helper({ - 'type': 'change_filter_viewport', - 'field': 'B (as str)', - 'top': 302, - 'bottom': 314 - }) + widget._handle_qgrid_msg_helper( + { + "type": "change_filter_viewport", + "field": "B (as str)", + "top": 302, + "bottom": 314, + } + ) assert event_history == [ + {"name": "filter_dropdown_shown", "column": "B (as str)"}, { - 'name': 'filter_dropdown_shown', - 'column': 'B (as str)' + "name": "text_filter_viewport_changed", + "column": "B (as str)", + "old": (0, 200), + "new": (556, 568), }, { - 'name': 'text_filter_viewport_changed', - 'column': 'B (as str)', - 'old': (0, 200), - 'new': (556, 568) + "name": "text_filter_viewport_changed", + "column": "B (as str)", + "old": (556, 568), + "new": (302, 314), }, - { - 'name': 'text_filter_viewport_changed', - 'column': 'B (as str)', - 'old': (556, 568), - 'new': (302, 314) - } ] def test_change_selection(): widget = QgridWidget(df=create_large_df(size=10)) - event_history = init_event_history('selection_changed', widget=widget) + event_history = init_event_history("selection_changed", widget=widget) - widget._handle_qgrid_msg_helper({ - 'type': 'change_selection', - 'rows': [5] - }) + widget._handle_qgrid_msg_helper({"type": "change_selection", "rows": [5]}) assert widget._selected_rows == [5] - widget._handle_qgrid_msg_helper({ - 'type': 'change_selection', - 'rows': [7, 8] - }) + widget._handle_qgrid_msg_helper( + {"type": "change_selection", "rows": [7, 8]} + ) assert widget._selected_rows == [7, 8] widget.change_selection([3, 5, 6]) @@ -702,29 +657,24 @@ def test_change_selection(): assert widget._selected_rows == [] assert event_history == [ + {"name": "selection_changed", "old": [], "new": [5], "source": "gui"}, { - 'name': 'selection_changed', - 'old': [], - 'new': [5], - 'source': 'gui' - }, - { - 'name': 'selection_changed', - 'old': [5], - 'new': [7, 8], - 'source': 'gui' + "name": "selection_changed", + "old": [5], + "new": [7, 8], + "source": "gui", }, { - 'name': 'selection_changed', - 'old': [7, 8], - 'new': [3, 5, 6], - 'source': 'api' + "name": "selection_changed", + "old": [7, 8], + "new": [3, 5, 6], + "source": "api", }, { - 'name': 'selection_changed', - 'old': [3, 5, 6], - 'new': [], - 'source': 'api' + "name": "selection_changed", + "old": [3, 5, 6], + "new": [], + "source": "api", }, ] @@ -733,45 +683,35 @@ def test_instance_created(): event_history = init_event_history(All) qgrid_widget = show_grid(create_df()) - assert event_history == [ - { - 'name': 'instance_created' - } - ] + assert event_history == [{"name": "instance_created"}] assert qgrid_widget.id def test_add_row(): event_history = init_event_history(All) - df = pd.DataFrame({'foo': ['hello'], 'bar': ['world'], 'baz': [42], 'boo': [57]}) - df.set_index('baz', inplace=True, drop=True) + df = pd.DataFrame( + {"foo": ["hello"], "bar": ["world"], "baz": [42], "boo": [57]} + ) + df.set_index("baz", inplace=True, drop=True) q = QgridWidget(df=df) new_row = [ - ('baz', 43), - ('bar', "new bar"), - ('boo', 58), - ('foo', "new foo") + ("baz", 43), + ("bar", "new bar"), + ("boo", 58), + ("foo", "new foo"), ] q.add_row(new_row) - assert q._df.loc[43, 'foo'] == 'new foo' - assert q._df.loc[42, 'foo'] == 'hello' + assert q._df.loc[43, "foo"] == "new foo" + assert q._df.loc[42, "foo"] == "hello" assert event_history == [ - {'name': 'instance_created'}, - { - 'name': 'json_updated', - 'range': (0, 100), - 'triggered_by': 'add_row' - }, - { - 'name': 'row_added', - 'index': 43, - 'source': 'api' - } + {"name": "instance_created"}, + {"name": "json_updated", "range": (0, 100), "triggered_by": "add_row"}, + {"name": "row_added", "index": 43, "source": "api"}, ] @@ -786,43 +726,41 @@ def test_remove_row(): assert len(widget._df) == 3 assert event_history == [ - {'name': 'instance_created'}, + {"name": "instance_created"}, { - 'name': 'json_updated', - 'range': (0, 100), - 'triggered_by': 'remove_row' + "name": "json_updated", + "range": (0, 100), + "triggered_by": "remove_row", }, - { - 'name': 'row_removed', - 'indices': [2], - 'source': 'api' - } + {"name": "row_removed", "indices": [2], "source": "api"}, ] def test_edit_cell(): - df = pd.DataFrame({'foo': ['hello'], 'bar': ['world'], 'baz': [42], 'boo': [57]}) - df.set_index('baz', inplace=True, drop=True) + df = pd.DataFrame( + {"foo": ["hello"], "bar": ["world"], "baz": [42], "boo": [57]} + ) + df.set_index("baz", inplace=True, drop=True) q = QgridWidget(df=df) event_history = init_event_history(All) - q.edit_cell(42, 'foo', 'hola') + q.edit_cell(42, "foo", "hola") - assert q._df.loc[42, 'foo'] == 'hola' + assert q._df.loc[42, "foo"] == "hola" assert event_history == [ { - 'name': 'json_updated', - 'range': (0, 100), - 'triggered_by': 'edit_cell' + "name": "json_updated", + "range": (0, 100), + "triggered_by": "edit_cell", }, { - 'name': 'cell_edited', - 'index': 42, - 'column': 'foo', - 'old': 'hello', - 'new': 'hola', - 'source': 'api' - } + "name": "cell_edited", + "index": 42, + "column": "foo", + "old": "hello", + "new": "hola", + "source": "api", + }, ] diff --git a/setup.cfg b/setup.cfg index 5c6311da..316c6c75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,10 @@ universal=1 [metadata] license_file = LICENSE + +[flake8] +exclude = + .git, + __pycache__, + setup.py, + docs \ No newline at end of file diff --git a/setup.py b/setup.py index 248983e3..3570f4f7 100644 --- a/setup.py +++ b/setup.py @@ -134,6 +134,15 @@ def package_files(directory): data_files = package_files('qgrid/static') + +def extras_require(): + return { + "test": [ + "pytest>=2.8.5", + "flake8>=3.6.0" + ], + } + setup_args = { 'name': 'qgrid', 'version': version_ns['__version__'], @@ -144,6 +153,7 @@ def package_files(directory): ('share/jupyter/nbextensions/qgrid', data_files), ], 'install_requires': reqs, + 'extras_require': extras_require(), 'packages': find_packages(), 'zip_safe': False, 'cmdclass': {