From 8ab99463a6084beef1ea41f3da2a9fdee6383703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Tue, 17 Mar 2026 14:04:35 +0100 Subject: [PATCH 1/7] bump version to 0.35.1-dev --- doc/source/changes.rst | 8 +++++ doc/source/changes/version_0_35_1.rst.inc | 41 +++++++++++++++++++++++ larray_editor/__init__.py | 2 +- pyproject.toml | 2 +- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 doc/source/changes/version_0_35_1.rst.inc diff --git a/doc/source/changes.rst b/doc/source/changes.rst index f5087440..88e7cefe 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -1,6 +1,14 @@ Change log ########## +Version 0.35.1 +============== + +In development. + +.. include:: ./changes/version_0_35_1.rst.inc + + Version 0.35 ============ diff --git a/doc/source/changes/version_0_35_1.rst.inc b/doc/source/changes/version_0_35_1.rst.inc new file mode 100644 index 00000000..c4533eb1 --- /dev/null +++ b/doc/source/changes/version_0_35_1.rst.inc @@ -0,0 +1,41 @@ +.. py:currentmodule:: larray_editor + +Syntax changes +^^^^^^^^^^^^^^ + +* renamed ``MappingEditor.old_method_name()`` to :py:obj:`MappingEditor.new_method_name()` (closes :editor_issue:`1`). + +* renamed ``old_argument_name`` argument of :py:obj:`MappingEditor.method_name()` to ``new_argument_name``. + + +Backward incompatible changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* other backward incompatible changes + + +New features +^^^^^^^^^^^^ + +* added a feature (see the :ref:`miscellaneous section ` for details). + +* added another feature in the editor (closes :editor_issue:`1`). + + .. note:: + + - It works for foo bar ! + - It does not work for foo baz ! + + +.. _misc_editor: + +Miscellaneous improvements +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* improved something. + + +Fixes +^^^^^ + +* fixed something (closes :editor_issue:`1`). diff --git a/larray_editor/__init__.py b/larray_editor/__init__.py index 4d135191..6a99f7ce 100644 --- a/larray_editor/__init__.py +++ b/larray_editor/__init__.py @@ -1,3 +1,3 @@ from larray_editor.api import * # noqa: F403 -__version__ = '0.35' +__version__ = '0.35.1-dev' diff --git a/pyproject.toml b/pyproject.toml index 5885a728..b41cdee0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ [project] name = "larray-editor" -version = "0.35" +version = "0.35.1-dev" description = "Graphical User Interface for LArray library" readme = { file = "README.rst", content-type = "text/x-rst" } From e53a4d1441cf14f48940b79b9d29e2e66a48992d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Wed, 3 Dec 2025 13:26:50 +0100 Subject: [PATCH 2/7] MAINT: added support for Python 3.14 --- .github/workflows/ci.yml | 2 +- doc/source/changes/version_0_35_1.rst.inc | 25 +---------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b205e5de..5b1b20d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: # os: ["ubuntu-latest", "macos-latest", "windows-latest"] - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] defaults: run: diff --git a/doc/source/changes/version_0_35_1.rst.inc b/doc/source/changes/version_0_35_1.rst.inc index c4533eb1..099f6b0a 100644 --- a/doc/source/changes/version_0_35_1.rst.inc +++ b/doc/source/changes/version_0_35_1.rst.inc @@ -1,33 +1,10 @@ .. py:currentmodule:: larray_editor -Syntax changes -^^^^^^^^^^^^^^ - -* renamed ``MappingEditor.old_method_name()`` to :py:obj:`MappingEditor.new_method_name()` (closes :editor_issue:`1`). - -* renamed ``old_argument_name`` argument of :py:obj:`MappingEditor.method_name()` to ``new_argument_name``. - - -Backward incompatible changes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* other backward incompatible changes - - New features ^^^^^^^^^^^^ -* added a feature (see the :ref:`miscellaneous section ` for details). - -* added another feature in the editor (closes :editor_issue:`1`). - - .. note:: - - - It works for foo bar ! - - It does not work for foo baz ! - +* added explicit support for Python 3.14. -.. _misc_editor: Miscellaneous improvements ^^^^^^^^^^^^^^^^^^^^^^^^^^ From 2608f5539bd1f80152132abf08aedf01bc687af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Tue, 3 Mar 2026 11:30:24 +0100 Subject: [PATCH 3/7] FIX: fixed test_api_larray.py on Python < 3.13 --- larray_editor/tests/test_api_larray.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/larray_editor/tests/test_api_larray.py b/larray_editor/tests/test_api_larray.py index d5c920ca..7f94c99e 100644 --- a/larray_editor/tests/test_api_larray.py +++ b/larray_editor/tests/test_api_larray.py @@ -34,7 +34,8 @@ array_signed_int = array.array('l', [1, 2, 3, 4, 5]) array_signed_int_empty = array.array('l') # should show as hello alpha and omega -array_unicode = array.array('w', 'hello \u03B1 and \u03C9') +unicode_typecode = 'w' if sys.version_info >= (3, 13) else 'u' +array_unicode = array.array(unicode_typecode, 'hello \u03B1 and \u03C9') # list list_empty = [] From 58afe26867ec1ada57f86e009194c45f860507b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Tue, 3 Mar 2026 14:21:23 +0100 Subject: [PATCH 4/7] FIX: fixed displaying DataFrames on Pandas>=3 --- doc/source/changes/version_0_35_1.rst.inc | 2 +- larray_editor/arrayadapter.py | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/source/changes/version_0_35_1.rst.inc b/doc/source/changes/version_0_35_1.rst.inc index 099f6b0a..46d91b35 100644 --- a/doc/source/changes/version_0_35_1.rst.inc +++ b/doc/source/changes/version_0_35_1.rst.inc @@ -15,4 +15,4 @@ Miscellaneous improvements Fixes ^^^^^ -* fixed something (closes :editor_issue:`1`). +* fixed displaying DataFrames with Pandas >= 3 (closes :editor_issue:`308`). diff --git a/larray_editor/arrayadapter.py b/larray_editor/arrayadapter.py index ccd2bda4..fd432bfe 100644 --- a/larray_editor/arrayadapter.py +++ b/larray_editor/arrayadapter.py @@ -1834,22 +1834,36 @@ def get_hnames(self): def get_vnames(self): return self.data.index.names + @staticmethod + def _ensure_numpy_array(data): + """Convert data to a numpy array if it is not already one.""" + pd = sys.modules['pandas'] + if isinstance(data, pd.arrays.ArrowStringArray): + return data.to_numpy() + else: + assert isinstance(data, np.ndarray) + return data + def get_vlabels_values(self, start, stop): pd = sys.modules['pandas'] index = self.sorted_data.index[start:stop] if isinstance(index, pd.MultiIndex): + # It seems like Pandas always returns a 1D array of tuples for + # MultiIndex.values, even if the MultiIndex has an homoneneous + # string type. That's why we do not need _ensure_numpy_array here + # list(row) because we want a list of list and not a list of tuples return [list(row) for row in index.values] else: - return index.values[:, np.newaxis] + return self._ensure_numpy_array(index.values)[:, np.newaxis] def get_hlabels_values(self, start, stop): pd = sys.modules['pandas'] index = self.sorted_data.columns[start:stop] if isinstance(index, pd.MultiIndex): - return [index.get_level_values(i).values + return [self._ensure_numpy_array(index.get_level_values(i).values) for i in range(index.nlevels)] else: - return [index.values] + return [self._ensure_numpy_array(index.values)] def get_values(self, h_start, v_start, h_stop, v_stop): # Sadly, as of Pandas 2.2.3, the previous version of this code: From abe97525b16eb097b5a96262b332a17618190977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Wed, 11 Mar 2026 11:04:48 +0100 Subject: [PATCH 5/7] MNT: avoid using deprecated warning method otherwise the warning method produced a (deprecation) warning itself ;-) --- larray_editor/arraywidget.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/larray_editor/arraywidget.py b/larray_editor/arraywidget.py index d87d70ae..caa5a98a 100644 --- a/larray_editor/arraywidget.py +++ b/larray_editor/arraywidget.py @@ -357,7 +357,7 @@ def _close_adapter(adapter): def on_clicked(self): if not len(self._back_data): - logger.warn("Back button has no target to go to") + logger.warning("Back button has no target to go to") return target_data = self._back_data.pop() data_adapter = self._back_data_adapters.pop() @@ -1272,8 +1272,8 @@ def update_range(self): max_value = total_cols - buffer_ncols + hidden_hscroll_max logger.debug(f"update_range horizontal {total_cols=} {buffer_ncols=} {hidden_hscroll_max=} => {max_value=}") if total_cols == 0 and max_value != 0: - logger.warn(f"empty data but {max_value=}. We let it pass for " - f"now (set it to 0).") + logger.warning(f"empty data but {max_value=}. We let it pass " + "for now (set it to 0).") max_value = 0 else: buffer_nrows = self.model.nrows @@ -1281,8 +1281,8 @@ def update_range(self): max_value = total_rows - buffer_nrows + hidden_vscroll_max logger.debug(f"update_range vertical {total_rows=} {buffer_nrows=} {hidden_vscroll_max=} => {max_value=}") if total_rows == 0 and max_value != 0: - logger.warn(f"empty data but {max_value=}. We let it pass for " - f"now (set it to 0).") + logger.warning(f"empty data but {max_value=}. We let it pass " + "for now (set it to 0).") max_value = 0 assert max_value >= 0, "max_value should not be negative" value_before = self.value() From 42ddf1fee1e32456540c98ae0197f232d297c52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Tue, 17 Mar 2026 12:18:38 +0100 Subject: [PATCH 6/7] FIX: refresh objects when setting any attribute(subset) (fixes #310) most prominently `df.loc[key] = value` and `df.iloc[key] = value` but also `arr.data[0] = -1` or `arr.data = np.array([[5, 6, 7], [8, 9, 0]])` --- doc/source/changes/version_0_35_1.rst.inc | 4 + larray_editor/editor.py | 38 ++++++--- .../test_inplace_modification_pattern.py | 77 ++++++++++++------- 3 files changed, 79 insertions(+), 40 deletions(-) diff --git a/doc/source/changes/version_0_35_1.rst.inc b/doc/source/changes/version_0_35_1.rst.inc index 46d91b35..a0609214 100644 --- a/doc/source/changes/version_0_35_1.rst.inc +++ b/doc/source/changes/version_0_35_1.rst.inc @@ -16,3 +16,7 @@ Fixes ^^^^^ * fixed displaying DataFrames with Pandas >= 3 (closes :editor_issue:`308`). + +* fixed some objects not being updated immediately when updated. This includes + updates to Pandas DataFrames done via `df.loc[key] = value` and + `df.iloc[position] = value` (closes :editor_issue:`310`). \ No newline at end of file diff --git a/larray_editor/editor.py b/larray_editor/editor.py index b59e49d2..8363e875 100644 --- a/larray_editor/editor.py +++ b/larray_editor/editor.py @@ -94,11 +94,20 @@ REOPEN_LAST_FILE = object() ASSIGNMENT_PATTERN = re.compile(r'[^\[\]]+[^=]=[^=].+') -SUBSET_UPDATE_PATTERN = re.compile(r'(\w+)' - r'(\.i|\.iflat|\.points|\.ipoints)?' - r'\[.+\]\s*' - r'([-+*/%&|^><]|//|\*\*|>>|<<)?' - r'=\s*[^=].*') +# This will match for: +# * variable = expr +# * variable[key] = expr +# * variable.attribute = expr +# * variable.attribute[key] = expr +# and their inplace ops counterpart +UPDATE_VARIABLE_PATTERN = re.compile( + r'(?P\w+)' + r'(?P\.\w+)?' + r'(?P\[.+\])?' + r'\s*' + r'(?P[-+*/%&|^><]|//|\*\*|>>|<<)?' + r'=\s*[^=].*' +) # = expr HISTORY_VARS_PATTERN = re.compile(r'_i?\d+') opened_secondary_windows = [] @@ -986,11 +995,18 @@ def ipython_cell_executed(self): # It would be easier to use '_' instead but that refers to the last output, not the output of the # last command. Which means that if the last command did not produce any output, _ is not modified. cur_output = user_ns['_oh'].get(cur_input_num) - setitem_pattern_match = SUBSET_UPDATE_PATTERN.match(last_input_last_line) - # setitem - if setitem_pattern_match is not None: - varname = setitem_pattern_match.group(1) - # simple variable + + # matches both setitem and setattr + update_variable_match = UPDATE_VARIABLE_PATTERN.match(last_input_last_line) + if update_variable_match is not None: + parts = update_variable_match.groupdict() + varname = parts['variable'] + if all(parts[name] is None for name in ('attribute', 'subset', 'inplaceop')): + # simple variable assignment => could be a new variable + # => must update mapping and varlist + changed_var = self.update_mapping_and_varlist(clean_ns) + assert changed_var == varname + # simple variable name (only) elif last_input_last_line in clean_ns: varname = last_input_last_line # any other statement @@ -1018,7 +1034,7 @@ def ipython_cell_executed(self): # For better or worse, _save_data() only saves "displayable data" # so changes to variables we cannot display do not concern us, # and this line should not be moved outside the if condition. - if setitem_pattern_match is not None: + if update_variable_match is not None: self.unsaved_modifications = True # TODO: this completely refreshes the array, including detecting diff --git a/larray_editor/tests/test_inplace_modification_pattern.py b/larray_editor/tests/test_inplace_modification_pattern.py index d1398091..4001cfc0 100644 --- a/larray_editor/tests/test_inplace_modification_pattern.py +++ b/larray_editor/tests/test_inplace_modification_pattern.py @@ -1,32 +1,51 @@ -from larray_editor.editor import SUBSET_UPDATE_PATTERN +from larray_editor.editor import UPDATE_VARIABLE_PATTERN def test_pattern(): - assert SUBSET_UPDATE_PATTERN.match('arr1[1] = 2') - assert SUBSET_UPDATE_PATTERN.match('arr1[1]= 2') - assert SUBSET_UPDATE_PATTERN.match('arr1[1]=2') - assert SUBSET_UPDATE_PATTERN.match("arr1['a'] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[func(mapping['a'])] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1.i[0, 0] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1.iflat[0, 0] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1.points[0, 0] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1.ipoints[0, 0] = arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] += arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] -= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] *= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] /= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] %= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] //= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] **= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] &= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] |= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] ^= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] >>= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0] <<= arr2") - assert SUBSET_UPDATE_PATTERN.match("arr1[0]") is None - assert SUBSET_UPDATE_PATTERN.match("arr1.method()") is None - assert SUBSET_UPDATE_PATTERN.match("arr1[0].method()") is None - assert SUBSET_UPDATE_PATTERN.match("arr1[0].method(arg=thing)") is None - assert SUBSET_UPDATE_PATTERN.match("arr1[0].method(arg==thing)") is None - # this test fails but I don't think it is possible to fix it with regex - # assert SUBSET_UPDATE_PATTERN.match("arr1[func('[]=0')].method()") is None + matching_patterns = [ + 'arr1[1] = 2', + 'arr1[1]= 2', + 'arr1[1]=2', + "arr1['a'] = arr2", + "arr1[func(mapping['a'])] = arr2", + "arr1.i[0, 0] = arr2", + "arr1.iflat[0, 0] = arr2", + "arr1.points[0, 0] = arr2", + "arr1.ipoints[0, 0] = arr2", + "arr1[0] += arr2", + "arr1[0] -= arr2", + "arr1[0] *= arr2", + "arr1[0] /= arr2", + "arr1[0] %= arr2", + "arr1[0] //= arr2", + "arr1[0] **= arr2", + "arr1[0] &= arr2", + "arr1[0] |= arr2", + "arr1[0] ^= arr2", + "arr1[0] >>= arr2", + "arr1[0] <<= arr2", + "arr1.data[1] = 2", + "arr1.data = np.array([1, 2, 3])" + ] + for pattern in matching_patterns: + match = UPDATE_VARIABLE_PATTERN.match(pattern) + assert match is not None and match.group('variable') == 'arr1' + + for pattern in [ + "df.loc[1] = 2", + "df.iloc[1] = 2" + ]: + match = UPDATE_VARIABLE_PATTERN.match(pattern) + assert match is not None and match.group('variable') == 'df' + + # no match + for pattern in [ + "arr1[0]", + "arr1.method()", + "arr1[0].method()", + "arr1[0].method(arg=thing)", + "arr1[0].method(arg==thing)", + # this test fails but I don't think it is possible to fix it with regex + # "arr1[func('[]=0')].method()" + ]: + assert UPDATE_VARIABLE_PATTERN.match(pattern) is None From ffa8117ef6c3ae1e2d6334a86af2418b16c6f1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20de=20Menten?= Date: Tue, 17 Mar 2026 14:21:30 +0100 Subject: [PATCH 7/7] DOC: document what gui-scripts are for --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b41cdee0..5df76227 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,11 @@ excel = ["xlwings"] # (=PyTables) to load the example datasets from larray hdf5 = ["tables"] +# project.gui-scripts create .exe files on Windows (like project.scripts would) +# but which call pythonw.exe internally instead of python.exe and thus do not +# open a console when launched [project.gui-scripts] +# name_of_executable = "module:function" larray-editor = "larray_editor.start:main" [project.urls]