Skip to content

Commit 7e86b7f

Browse files
Pavel.PrudkyPavel.Prudky
authored andcommitted
v0.1.2 ( minor bug fixes )
- better file save error handling - adding numerics to section names allowed regex - minor adjustments
1 parent 63055e4 commit 7e86b7f

File tree

9 files changed

+135
-50
lines changed

9 files changed

+135
-50
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Notes application written in Python 3.8 & KivyMD
1414
## version history
1515
| version | date | description |
1616
| :---: | :---: | :---: |
17+
| 0.1.2 | 10/10/2022 | minor bugfixes |
1718
| 0.1.1 | 12/7/2022 | removing item drawer menu highlight, improving diff feature, bugfixes |
1819
| 0.1.0 | 19/6/2022 | initial release |
1920

notes_app/controller/notes_controller.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ def save_file_data(self, data) -> None:
4747
"""
4848
save_file_data saves provided data to the file with location set in model.file_path
4949
"""
50-
f = open(self.model.file_path, "w")
51-
f.write(data)
52-
f.close()
50+
if len(data) == 0:
51+
return
52+
try:
53+
with open(self.model.file_path, "w") as f:
54+
f.write(data)
55+
except Exception as exc:
56+
raise exc
5357

5458
self.model.update()
5559
self.model.dump()

notes_app/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class Defaults:
22
DEFAULT_MODEL_STORE_FILE_NAME = "file_metadata.json"
33
DEFAULT_NOTES_FILE_NAME = "my_first_file.txt"
44
DEFAULT_SECTION_FILE_SEPARATOR = "<section={name}> "
5-
DEFAULT_SECTION_FILE_SEPARATOR_REGEX = "<section=[a-z A-Z]+> "
5+
DEFAULT_SECTION_FILE_SEPARATOR_REGEX = "<section=[a-z A-Z0-9]+> "
66
DEFAULT_SECTION_FILE_SEPARATOR_GROUP_SUBSTR_REGEX = "<section=(.+?)> "
77
DEFAULT_NOTES_FILE_CONTENT = f"{DEFAULT_SECTION_FILE_SEPARATOR.format(name='first')} Your first section. Here you can write your notes."
88
DEFAULT_AUTO_SAVE_TEXT_INPUT_CHANGE_COUNT = 5

notes_app/search.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ def search_full_words(self):
7878
def search_full_words(self, value):
7979
self._search_full_words = value
8080

81-
def search_for_occurrences(self, pattern, file, current_section_identifier):
81+
def search_for_occurrences(self, pattern, file, current_section):
8282
found_occurrences = dict()
8383

8484
if self.search_all_sections:
8585
sections_separators_to_search_in = file.section_separators_sorted
8686
else:
87-
sections_separators_to_search_in = [current_section_identifier]
87+
sections_separators_to_search_in = [current_section]
8888

8989
for section_separator in sections_separators_to_search_in:
9090
text = file.get_section_content(section_separator=section_separator)

notes_app/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.1.1"
1+
__version__ = "0.1.2"

notes_app/view/notes_view.py

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,10 @@ def execute_search(self, *args):
538538

539539
self.dialog.content_cls.results_list.clear_widgets()
540540

541-
# TODO rename current_section_identifier param to current_section
542541
found_occurrences = self.search.search_for_occurrences(
543542
pattern=self.last_searched_string,
544543
file=self.file,
545-
current_section_identifier=self.current_section,
544+
current_section=self.current_section,
546545
)
547546

548547
if not found_occurrences:
@@ -679,49 +678,54 @@ def cancel_dialog(self, *args):
679678
def save_current_section_to_file(self):
680679
merged_current_section_text_data = None
681680

682-
if self.model.external_update:
683-
self.file.reload()
684-
try:
685-
current_section_text_before = self.file.get_section_content(
686-
section_separator=self.text_section_view.section_file_separator
687-
)
688-
# KeyError raised if the current section was removed or renamed by a external update
689-
except KeyError:
690-
# merge_strings prioritizes current_section_text_after over current_section_text_before
691-
# so empty string placeholder is set to current_section_text_before
692-
current_section_text_before = ""
693-
# self.file.reload() will remove the current section separator from self.file.section_separators
694-
# in case it was deleted or renamed so the current section identifier is added back
695-
# si = SectionIdentifier(section_file_separator=self.text_section_view.section_file_separator, defaults=self.defaults)
696-
#
697-
self.file.set_section_content(
698-
section_separator=self.text_section_view.section_file_separator,
699-
section_content=SECTION_FILE_NEW_SECTION_PLACEHOLDER,
700-
)
681+
try:
682+
if self.model.external_update:
683+
self.file.reload()
684+
try:
685+
current_section_text_before = self.file.get_section_content(
686+
section_separator=self.text_section_view.section_file_separator
687+
)
688+
# KeyError raised if the current section was removed or renamed by a external update
689+
except KeyError:
690+
# merge_strings prioritizes current_section_text_after over current_section_text_before
691+
# so empty string placeholder is set to current_section_text_before
692+
current_section_text_before = ""
693+
# self.file.reload() will remove the current section separator from self.file.section_separators
694+
# in case it was deleted or renamed so the current section identifier is added back
695+
# si = SectionIdentifier(section_file_separator=self.text_section_view.section_file_separator, defaults=self.defaults)
696+
#
697+
self.file.set_section_content(
698+
section_separator=self.text_section_view.section_file_separator,
699+
section_content=SECTION_FILE_NEW_SECTION_PLACEHOLDER,
700+
)
701701

702-
current_section_text_after = self.text_section_view.text
702+
current_section_text_after = self.text_section_view.text
703703

704-
merged_current_section_text_data = merge_strings(
705-
before=current_section_text_before, after=current_section_text_after
706-
)
704+
merged_current_section_text_data = merge_strings(
705+
before=current_section_text_before, after=current_section_text_after
706+
)
707707

708-
self.text_section_view.text = merged_current_section_text_data
709-
# un-focus the TextInput so that the cursor is not offset by the external update
710-
self.text_section_view.focus = False
708+
self.text_section_view.text = merged_current_section_text_data
709+
# un-focus the TextInput so that the cursor is not offset by the external update
710+
self.text_section_view.focus = False
711711

712-
self.set_drawer_items(
713-
section_separators=self.file.section_separators_sorted
712+
self.set_drawer_items(
713+
section_separators=self.file.section_separators_sorted
714+
)
715+
716+
self.file.set_section_content(
717+
section_separator=self.text_section_view.section_file_separator,
718+
section_content=merged_current_section_text_data
719+
or self.text_section_view.text,
714720
)
715721

716-
self.file.set_section_content(
717-
section_separator=self.text_section_view.section_file_separator,
718-
section_content=merged_current_section_text_data
719-
or self.text_section_view.text,
720-
)
722+
raw_text_data = self.file.transform_data_by_sections_to_raw_data_content()
721723

722-
text_data = self.file.transform_data_by_sections_to_raw_data_content()
724+
self.controller.save_file_data(data=raw_text_data)
723725

724-
self.controller.save_file_data(data=text_data)
726+
except Exception as exc:
727+
self.show_error_bar(error_message=f"Error while saving file, details: {exc}")
728+
return
725729

726730
def press_menu_item_save_file(self, *args):
727731
self.save_current_section_to_file()

tests/test_unit_controller.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from time import sleep
24
from datetime import datetime
35
from os import path, remove
@@ -70,6 +72,50 @@ def test_save_file_data(self, get_app):
7072
assert controller.model.file_size > 0
7173
assert datetime.fromtimestamp(controller.model.last_updated_on) >= _epoch_before
7274

75+
def test_save_file_data_handle_error(self, get_app):
76+
# test case to cover scenario when data write fails no data is lost
77+
controller = get_app.controller
78+
79+
assert controller.model.file_size == 0
80+
_epoch_before = datetime.fromtimestamp(controller.model.last_updated_on)
81+
assert _epoch_before
82+
83+
# asserting the default notes file contains data
84+
with open(get_app.controller.defaults.DEFAULT_NOTES_FILE_NAME, mode="r") as f:
85+
assert f.readlines() == ['<section=first> Quod equidem non reprehendo\n',
86+
'<section=second> Quis istum dolorem timet']
87+
88+
controller.save_file_data(
89+
data="""<section=first> Quod equidem non reprehendo
90+
<section=second> Quis istum dolorem timet
91+
"""
92+
)
93+
94+
assert isinstance(controller.model.file_size, int)
95+
# assert the model file_size is still > 0
96+
assert controller.model.file_size > 0 # exact file size differs between OS types
97+
98+
# setting model file path as empty string is invalid enough to raise file write error exc
99+
controller.model.file_path = ""
100+
101+
# writing to file through save_file_data raises Exception
102+
with pytest.raises(Exception):
103+
controller.save_file_data(
104+
data="""<section=first> Quod equidem non reprehendo
105+
<section=second> Quis istum dolorem timet <section=third> !
106+
"""
107+
)
108+
109+
# assert the file still contains the data
110+
with open(get_app.controller.defaults.DEFAULT_NOTES_FILE_NAME, mode="r") as f:
111+
assert f.readlines() == ['<section=first> Quod equidem non reprehendo\n',
112+
'<section=second> Quis istum dolorem timet\n']
113+
114+
assert isinstance(controller.model.file_size, int)
115+
# assert the model file_size is still > 0
116+
assert controller.model.file_size > 0 # exact file size differs between OS types
117+
assert datetime.fromtimestamp(controller.model.last_updated_on) >= _epoch_before
118+
73119
def test_get_screen(self, get_app):
74120
controller = get_app.controller
75121
assert isinstance(controller.get_screen(), NotesView)

tests/test_unit_search.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_search_default(self, get_file):
101101
search.search_full_words = False
102102

103103
assert search.search_for_occurrences(
104-
pattern="do", file=get_file, current_section_identifier="<section=first> ",
104+
pattern="do", file=get_file, current_section="<section=first> ",
105105
) == {"<section=first> ": [25]}
106106

107107
def test_search_case_sensitive(self, get_file):
@@ -112,14 +112,14 @@ def test_search_case_sensitive(self, get_file):
112112
search.search_full_words = False
113113

114114
assert search.search_for_occurrences(
115-
pattern="do", file=get_file, current_section_identifier="<section=first> ",
115+
pattern="do", file=get_file, current_section="<section=first> ",
116116
) == {"<section=first> ": [25]}
117117

118118
assert (
119119
search.search_for_occurrences(
120120
pattern="dO",
121121
file=get_file,
122-
current_section_identifier="<section=first> ",
122+
current_section="<section=first> ",
123123
)
124124
== {}
125125
)
@@ -132,7 +132,7 @@ def test_search_all_sections(self, get_file):
132132
search.search_full_words = False
133133

134134
assert search.search_for_occurrences(
135-
pattern="do", file=get_file, current_section_identifier="<section=first> ",
135+
pattern="do", file=get_file, current_section="<section=first> ",
136136
) == {"<section=first> ": [25], "<section=second> ": [11]}
137137

138138
def test_search_full_words(self, get_file):
@@ -143,14 +143,14 @@ def test_search_full_words(self, get_file):
143143
search.search_full_words = True
144144

145145
assert search.search_for_occurrences(
146-
pattern="non", file=get_file, current_section_identifier="<section=first> ",
146+
pattern="non", file=get_file, current_section="<section=first> ",
147147
) == {"<section=first> ": [13]}
148148

149149
assert (
150150
search.search_for_occurrences(
151151
pattern="nonx",
152152
file=get_file,
153-
current_section_identifier="<section=first> ",
153+
current_section="<section=first> ",
154154
)
155155
== {}
156156
)

tests/test_unit_view.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,36 @@ def test_save_current_section_to_file_is_external_update_with_changes_to_differe
858858
== """<section=first> Quod equidem non reprehendo\n<section=second> Quis istum dolorem timet<section=test> test data"""
859859
)
860860

861+
def test_save_current_section_to_file_handle_error(self, get_app):
862+
# setting model._last_updated_on manually will guarantee model.external_update returns False
863+
get_app.controller.model._last_updated_on = int(time.time())
864+
865+
screen = get_app.controller.get_screen()
866+
867+
assert (
868+
screen.file.get_raw_data_content()
869+
== """<section=first> Quod equidem non reprehendo\n<section=second> Quis istum dolorem timet"""
870+
)
871+
872+
screen.text_section_view.section_file_separator = "<section=a> "
873+
874+
screen.text_section_view.text = "test text"
875+
876+
# setting model file path as empty string is invalid enough to raise file write error exc
877+
get_app.controller.model.file_path = ""
878+
879+
assert screen.save_current_section_to_file() is None
880+
881+
# assert `<section=a> test text` was not added to the file,
882+
# however the file content still contains data before the write failure
883+
assert (
884+
screen.file.get_raw_data_content()
885+
== """<section=first> Quod equidem non reprehendo\n<section=second> Quis istum dolorem timet"""
886+
)
887+
888+
assert isinstance(screen.snackbar, CustomSnackbar)
889+
assert screen.snackbar.text.startswith("Error while saving file, details:")
890+
861891
def test_press_menu_item_save_file_is_not_external_update(self, get_app):
862892
# setting model._last_updated_on manually will guarantee model.external_update returns False
863893
get_app.controller.model._last_updated_on = int(time.time())

0 commit comments

Comments
 (0)