Jessica Walkenhorst commited on
Commit
613f4ac
β€’
1 Parent(s): 9957bde

Add workaround for false xls files (#13)

Browse files

also:

* adds some comments
* excludes some rules from flake8 due to incompatibility with black

Files changed (3) hide show
  1. app.py +14 -7
  2. bin/run-flake8.sh +1 -1
  3. src/maorganizer/ui.py +44 -11
app.py CHANGED
@@ -5,6 +5,8 @@ from maorganizer.datawrangling import Person
5
 
6
  from maorganizer.ui import TASKS, render_xlsx_download_button, create_file_uploader, create_task_selector
7
 
 
 
8
  st.title("πŸ“… Meeting Attendance Organizer")
9
  st.markdown("This app fullfills a simple need: Take a list of names of people attending a meeting and peform one (or multiple) of the following tasks:")
10
  st.markdown("""* βœ‚οΈ Split their names into first name and surname\n* πŸ‘€ Compare two lists with each other and see who is new on the second list\n * πŸ”Ž Find people in a list by either searching for their complete names or parts of their name\n * πŸ’Ύ Write any of the results back out, so you can share it with others""")
@@ -12,6 +14,7 @@ st.markdown("""* βœ‚οΈ Split their names into first name and surname\n* πŸ‘€ Co
12
  st.header("πŸ“‚ Step 1: Upload your Files")
13
  st.markdown("Upload the file(s) containing your meeting attendees. The expected format is a single column containing the attendees' full names. If you column name is not Name, you will be able to specify the column name after uploading the data. Additional columns will be ignored.")
14
 
 
15
  meetings = {}
16
  meetings = create_file_uploader()
17
 
@@ -26,7 +29,6 @@ if meetings:
26
  if task == TASKS.SPLIT.value:
27
 
28
  filename = st.selectbox("Choose a file πŸ“„", options=list(meetings.keys()), key=task)
29
- #filename = render_file_selector(meetings, key=task)
30
 
31
  render_xlsx_download_button({'Full list of Attendees': meetings[filename]},
32
  filename=f"processed-attendees-{Path(filename).stem}.xlsx",
@@ -64,9 +66,14 @@ if meetings:
64
  with col2:
65
  filename_new = st.selectbox("Choose your updated file", options=set(meetings.keys()) - {filename_old})
66
 
67
- listcomparison = (
68
- {'Original List': meetings[filename_old],
69
- 'Updated List - Full': meetings[filename_new],
70
- 'Updated List - Only Updates': meetings[filename_old].update(meetings[filename_new])})
71
-
72
- render_xlsx_download_button(listcomparison, filename=f"{Path(filename_old).stem}-updated.xlsx", key=TASKS.COMPARE.value+'download')
 
 
 
 
 
 
5
 
6
  from maorganizer.ui import TASKS, render_xlsx_download_button, create_file_uploader, create_task_selector
7
 
8
+ # all these beautiful emojis from https://emojidb.org/file-emojis
9
+
10
  st.title("πŸ“… Meeting Attendance Organizer")
11
  st.markdown("This app fullfills a simple need: Take a list of names of people attending a meeting and peform one (or multiple) of the following tasks:")
12
  st.markdown("""* βœ‚οΈ Split their names into first name and surname\n* πŸ‘€ Compare two lists with each other and see who is new on the second list\n * πŸ”Ž Find people in a list by either searching for their complete names or parts of their name\n * πŸ’Ύ Write any of the results back out, so you can share it with others""")
 
14
  st.header("πŸ“‚ Step 1: Upload your Files")
15
  st.markdown("Upload the file(s) containing your meeting attendees. The expected format is a single column containing the attendees' full names. If you column name is not Name, you will be able to specify the column name after uploading the data. Additional columns will be ignored.")
16
 
17
+
18
  meetings = {}
19
  meetings = create_file_uploader()
20
 
 
29
  if task == TASKS.SPLIT.value:
30
 
31
  filename = st.selectbox("Choose a file πŸ“„", options=list(meetings.keys()), key=task)
 
32
 
33
  render_xlsx_download_button({'Full list of Attendees': meetings[filename]},
34
  filename=f"processed-attendees-{Path(filename).stem}.xlsx",
 
66
  with col2:
67
  filename_new = st.selectbox("Choose your updated file", options=set(meetings.keys()) - {filename_old})
68
 
69
+ # filename_new gets automatically populated if there is more than one file
70
+ # so if there is none, it's because there is only a single file available and no options left for filename_new
71
+ if filename_new is None:
72
+ st.info("⬆ Please upload a second file. ⬆")
73
+ else:
74
+ listcomparison = (
75
+ {'Original List': meetings[filename_old],
76
+ 'Updated List - Full': meetings[filename_new],
77
+ 'Updated List - Only Updates': meetings[filename_old].update(meetings[filename_new])})
78
+
79
+ render_xlsx_download_button(listcomparison, filename=f"{Path(filename_old).stem}-updated.xlsx", key=TASKS.COMPARE.value+'download')
bin/run-flake8.sh CHANGED
@@ -1,2 +1,2 @@
1
  #!/bin/sh
2
- poetry run flake8 src/* tests/*
 
1
  #!/bin/sh
2
+ poetry run flake8 --ignore=W605,W503 src/* tests/*
src/maorganizer/ui.py CHANGED
@@ -27,7 +27,23 @@ class TASKS(str, Enum):
27
 
28
  def load_df_from_uploaded_data(filename, data, sep=None) -> pd.DataFrame:
29
  if Path(filename).suffix in EXCEL_EXTENSIONS:
30
- df = pd.read_excel(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  elif Path(filename).suffix in CSV_EXTENSIONS:
32
  df = pd.read_csv(data, sep=sep)
33
  else:
@@ -49,30 +65,45 @@ def make_attendance_data_from_file_uploads(
49
 
50
 
51
  def load_data(uploaded_files) -> Tuple[Dict, bool]:
 
 
 
 
 
52
  try:
53
  data = make_attendance_data_from_file_uploads(
54
  uploaded_files, sep=None, cname=NAMECOLUMN
55
  )
 
 
 
 
 
56
  except KeyError:
57
- contains_csvs = sum(
58
- [Path(file.name).suffix in CSV_EXTENSIONS for file in uploaded_files]
59
- )
60
- if contains_csvs:
61
- separator = st.radio(
62
  "We detected text files in your input. What is their separator?",
63
  sorted(SEPARATORTYPES.keys()),
64
  )
 
 
 
65
 
66
- namecolumn = st.text_input(
 
67
  "Column header of your file's name column", NAMECOLUMN
68
  )
 
69
  try:
70
  data = make_attendance_data_from_file_uploads(
71
- uploaded_files, sep=SEPARATORTYPES[separator], cname=namecolumn
72
  )
73
  except KeyError:
74
  st.error(
75
- f"We could not find a column {namecolumn} in your data. Please use the options above to specify your column separator and the column name of your name column."
 
 
76
  )
77
  data = {}
78
 
@@ -115,11 +146,13 @@ def create_task_selector():
115
  st.markdown("❔ **Description:** Split a list of names into first and surname.")
116
  elif task == TASKS.COMPARE.value:
117
  st.markdown(
118
- "❔ **Description:** Compare two attendee lists with each and find attendees who have recently joined."
 
119
  )
120
  elif task == TASKS.FIND.value:
121
  st.markdown(
122
- "❔ **Description:** Find attendees in a list by either first name or surname or by substrings."
 
123
  )
124
  return task
125
 
 
27
 
28
  def load_df_from_uploaded_data(filename, data, sep=None) -> pd.DataFrame:
29
  if Path(filename).suffix in EXCEL_EXTENSIONS:
30
+ try:
31
+ df = pd.read_excel(data)
32
+ # If engine does not recognize excel as excel, it is likely to be
33
+ # a text format "disguised" as xls.
34
+ # (example: PyData Meeting files have .xls extension, but are in fact text files)
35
+ except ValueError as e:
36
+ if (
37
+ str(e)
38
+ == "Excel file format cannot be determined, you must specify an engine manually."
39
+ ):
40
+ st.info(
41
+ f"Your {Path(filename).suffix} file does not seem to be an excel file.\\\n\\\n"
42
+ "\- Trying to parse it as text file."
43
+ )
44
+ df = pd.read_csv(data, sep=sep)
45
+ else:
46
+ raise ValueError(e)
47
  elif Path(filename).suffix in CSV_EXTENSIONS:
48
  df = pd.read_csv(data, sep=sep)
49
  else:
 
65
 
66
 
67
  def load_data(uploaded_files) -> Tuple[Dict, bool]:
68
+ def _files_contain_csv(uploaded_files) -> bool:
69
+ return bool(
70
+ sum([Path(file.name).suffix in CSV_EXTENSIONS for file in uploaded_files])
71
+ )
72
+
73
  try:
74
  data = make_attendance_data_from_file_uploads(
75
  uploaded_files, sep=None, cname=NAMECOLUMN
76
  )
77
+
78
+ # let user specify file format, then retry loading
79
+ # Retry parsing after letting user manually specify separator and columnname.
80
+ # Display error **after** the input fields, so if manually specifying separator and columnname
81
+ # fixes the load issue, the error message disappears.
82
  except KeyError:
83
+ # Set separator if csv file present
84
+ if _files_contain_csv(uploaded_files):
85
+ seperator_key = st.radio(
 
 
86
  "We detected text files in your input. What is their separator?",
87
  sorted(SEPARATORTYPES.keys()),
88
  )
89
+ separator = SEPARATORTYPES[seperator_key]
90
+ else:
91
+ separator = None
92
 
93
+ # Set columnname
94
+ columnname = st.text_input(
95
  "Column header of your file's name column", NAMECOLUMN
96
  )
97
+
98
  try:
99
  data = make_attendance_data_from_file_uploads(
100
+ uploaded_files, sep=separator, cname=columnname
101
  )
102
  except KeyError:
103
  st.error(
104
+ f'We could not find a column "{columnname}" in your data.\\\n\\\n'
105
+ " Please use the options above to specify your column separator (if text/csv file)"
106
+ " and the column name of the column containing your attendees' names."
107
  )
108
  data = {}
109
 
 
146
  st.markdown("❔ **Description:** Split a list of names into first and surname.")
147
  elif task == TASKS.COMPARE.value:
148
  st.markdown(
149
+ "❔ **Description:** Compare two attendee lists with each"
150
+ " and find attendees who have recently joined."
151
  )
152
  elif task == TASKS.FIND.value:
153
  st.markdown(
154
+ "❔ **Description:** Find attendees in a list by either first name"
155
+ " or surname or by substrings."
156
  )
157
  return task
158