Skip to content

Commit 1e28745

Browse files
committed
ADDED: message of elapsed time, and added feature of downloading input data from shrepoint with argument to select if using local storage or sharepoint
1 parent 3c89d81 commit 1e28745

8 files changed

Lines changed: 163 additions & 45 deletions

File tree

src/NAF.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def slash_dash_str(self):
3838
return f"{self.province_code}/{self.middle_number}-{self.last_number}"
3939

4040

41-
def is_naf_correct(naf):
41+
def is_naf_format_correct(naf):
4242
"""Validate that NAF has NAF format"""
4343
try:
4444
NAF(naf) # Parse using constructor
@@ -47,13 +47,8 @@ def is_naf_correct(naf):
4747
return True
4848

4949

50-
def validate_parse_naf(value, valid_nafs):
51-
if not is_naf_correct(value):
52-
raise ArgumentNafInvalid("Naf " + value + " is not valid.")
53-
elif NAF(value) not in valid_nafs:
54-
raise ArgumentNafNotPresent("NAF value " + value + " is not in our NAF database (input/NAF_DNI.xlsx)")
55-
else:
56-
return NAF(value)
50+
def is_naf_present(value, valid_nafs):
51+
return NAF(value) in valid_nafs
5752

5853

5954
def clean_naf(naf):
@@ -95,7 +90,4 @@ def parse_naf(naf):
9590
return dict(zip(naf_fixed, name_col))
9691

9792

98-
NAF_TO_DNI = build_naf_to_dni(NAF_DATA_PATH)
9993

100-
# Build dictionaries to translate NAF to different identifier data
101-
NAF_TO_NAME = build_naf_to_name_and_surname(NAF_DATA_PATH)

src/arguments.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from functools import partial
55

6-
from NAF import NAF_TO_DNI, validate_parse_naf
6+
from NAF import is_naf_present, NAF, build_naf_to_dni
77
from custom_except import *
88
from defines import NAF_DATA_PATH, DocType, from_string
99

@@ -12,6 +12,9 @@ def get_compact_init():
1212
return {DocType.SALARY: False, DocType.PROOFS: False, DocType.CONTRACT: False, DocType.RNT: False, DocType.RLC: False}
1313

1414

15+
# Parser functions that validate the format and type of the data
16+
17+
1518
def parse_date(value, formatting="%Y_%m"):
1619
"""Validate date format"""
1720
try:
@@ -22,21 +25,17 @@ def parse_date(value, formatting="%Y_%m"):
2225

2326

2427
def parse_author(author):
25-
with open("input/users.txt", newline="", encoding="utf-8") as f:
26-
for line in f.readlines():
27-
if line.__eq__(author):
28-
return author
29-
raise ArgumentAuthorError("Author " + author + " is not in the accepted user list (input/users.txt).")
28+
return author
3029

3130

3231
def parse_naf(value):
3332
try:
34-
return validate_parse_naf(value, NAF_TO_DNI.keys())
33+
return NAF(value)
3534
except ValueError as e:
36-
raise ArgumentNafNotPresent("NAF is not valid" + e.__str__())
35+
raise ArgumentNafInvalid("NAF is not valid" + e.__str__())
3736

3837

39-
def parse_compact(value):
38+
def parse_compact_options(value):
4039
to_compact = get_compact_init()
4140
try:
4241
if "," in value:
@@ -52,7 +51,16 @@ def parse_compact(value):
5251
exit(1)
5352

5453

55-
def parse_arguments(valid_nafs):
54+
def parse_input_type(value):
55+
if value == "sharepoint":
56+
return value
57+
elif value == "local":
58+
return value
59+
else:
60+
raise UndefinedInputType("The type supplied for input type \"" + value + "\" is not defined.")
61+
62+
63+
def parse_arguments():
5664
"""Parse and validate command-line arguments"""
5765
parser = argparse.ArgumentParser(description="Process NAF and date range.")
5866

@@ -62,23 +70,27 @@ def parse_arguments(valid_nafs):
6270
parser.add_argument("-e", "--end", type=parse_date, required=True, help="End date (YYYY-MM)")
6371
parser.add_argument("-a", "--author", type=parse_author, required=True, help="author's email doing request")
6472

65-
parser.add_argument("-c", "--compact", type=parse_compact, required=False, default=get_compact_init(),
73+
parser.add_argument("-c", "--compact", type=parse_compact_options, required=False, default=get_compact_init(),
6674
help="Comma separated list of values that indicate which documents need to be merged in one "
6775
"single PDF in the output. Possible values are: " +
6876
",".join([dt.value.__str__() for dt in DocType]))
6977

78+
parser.add_argument("-i", "--input", type=parse_input_type, required=False, default="sharepoint",
79+
help="Location of the input data. Possible values are: \"sharepoint\" to download from "
80+
"sharepoint location and \"local\" to use the local file system storage adn read the input"
81+
" folder in the repository root folder.")
7082
args = parser.parse_args()
7183

7284
return args
7385

7486

75-
def process_arguments():
87+
def process_parse_arguments():
7688
common = ("Error parsing arguments. Program aborting. The arguments are: "
7789
+ str(sys.argv) + "The program is in a uninitialized state and cannot proceed. This error will be "
7890
"notified to the admin via log file. We can't create log file in user author folder "
7991
"because user author could not be parsed.")
8092
try:
81-
args = parse_arguments(NAF_TO_DNI.keys())
93+
args = parse_arguments()
8294

8395
except ArgumentNafNotPresent as e:
8496
print("The NAF provided is valid but is not present in " + NAF_DATA_PATH + ". Internal error is " + e.__str__())
@@ -103,3 +115,49 @@ def process_arguments():
103115
return args
104116

105117

118+
# Validations functions that check if the data from the request is valid regarding business rules
119+
120+
121+
def validate_naf(naf, valid_nafs):
122+
if not is_naf_present(naf, valid_nafs):
123+
raise ArgumentNafNotPresent
124+
125+
126+
def is_author_present(author, valid_authors):
127+
return author in valid_authors
128+
129+
130+
def validate_author(author, valid_authors):
131+
if not is_author_present(author, valid_authors):
132+
raise ArgumentAuthorError("Author \"" + str(author) + " is not valid. ") # more specific exception
133+
134+
135+
def validate_arguments(args, valid_nafs, valid_authors):
136+
validate_author(args.author, valid_authors)
137+
validate_naf(args.naf, valid_nafs)
138+
139+
140+
def process_validate_arguments(args, naf_data_path, user_list_data_path):
141+
common = ("Error validating arguments. Program aborting. The arguments are: "
142+
+ str(sys.argv) + "The program is in a uninitialized state and cannot proceed. This error will be "
143+
"notified to the admin via log file. We can't create log file in user author folder "
144+
"because the process that validates user author could not finish.")
145+
146+
nafs = build_naf_to_dni(naf_data_path).keys()
147+
148+
authors = []
149+
with open(user_list_data_path, newline="", encoding="utf-8") as f:
150+
for line in f.readlines():
151+
authors.append(line)
152+
153+
try:
154+
validate_arguments(args, nafs, authors)
155+
156+
except ArgumentNafNotPresent as e:
157+
print("The NAF provided is valid but is not present in " + naf_data_path + ". Internal error is " + e.__str__())
158+
print(common)
159+
exit(1)
160+
except ArgumentAuthorError as e:
161+
print("The author is not present in the accepted user list. Internal error is " + e.__str__())
162+
print(common)
163+
exit(4)

src/chrono.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import time
2+
from datetime import timedelta
3+
4+
from logger import get_process_logger, get_logger_instance
5+
6+
7+
def elapsed_time(start_time):
8+
9+
elapsed = time.time() - start_time
10+
delta = timedelta(seconds=int(elapsed))
11+
days = delta.days
12+
hours, remainder = divmod(delta.seconds, 3600)
13+
minutes, seconds = divmod(remainder, 60)
14+
15+
parts = []
16+
if days:
17+
parts.append(f"{days} day{'s' if days != 1 else ''}")
18+
if hours:
19+
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
20+
if minutes:
21+
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
22+
if seconds or not parts:
23+
parts.append(f"{seconds} second{'s' if seconds != 1 else ''}")
24+
25+
formatted = ', '.join(parts)
26+
return formatted

src/custom_except.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ class UndefinedRegularSalaryType(Exception):
1919
pass
2020

2121

22+
class UndefinedInputType(Exception):
23+
pass
24+

src/defines.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
# Obtain absolute path to the input directory
1111
INPUT_FOLDER = os.path.join(ROOT_FOLDER, "input")
12+
# Obtain absolute path to the valid user list
13+
USER_LIST_DATA_PATH = os.path.join(INPUT_FOLDER, "input")
1214

1315
# Admin logs
1416
ADMIN_LOG_FOLDER = os.path.join(GENERAL_OUTPUT_FOLDER, "_admin_logs")
@@ -37,6 +39,7 @@ class DocType(Enum):
3739
RNT = "RNT"
3840
PROOFS = "proofs"
3941

42+
4043
def from_string(value: str):
4144
_aliases = {
4245
DocType.SALARY: {"salary", "salaries", "SALARY", "Salary", "payslip"},

src/filesystem.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
2+
import shutil
23
from datetime import datetime
34

4-
from NAF import NAF_TO_NAME
55
from defines import GENERAL_OUTPUT_FOLDER, ADMIN_LOG_FOLDER, SUPERVISOR_LOG_FOLDER, SALARIES_OUTPUT_NAME, \
66
PROOFS_OUTPUT_NAME, CONTRACTS_OUTPUT_NAME, RNTS_OUTPUT_NAME, RLCS_OUTPUT_NAME
77

@@ -107,10 +107,10 @@ def flatten_dirs(folder_to_flat):
107107
return flatted_folders
108108

109109

110-
def compute_paths(args):
110+
def compute_paths(args, naf_to_name):
111111
NOW = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
112112

113-
id_str = (NOW + "_" + args.author + "_" + args.naf.__str__() + "_" + NAF_TO_NAME[args.naf] + "_" +
113+
id_str = (NOW + "_" + args.author + "_" + args.naf.__str__() + "_" + naf_to_name[args.naf] + "_" +
114114
args.begin.strftime("%Y-%m") + "-" + args.end.strftime("%Y-%m") + ".log.txt")
115115
# Admin logs
116116
ADMIN_LOG_PATH = os.path.join(ADMIN_LOG_FOLDER, id_str)
@@ -120,18 +120,28 @@ def compute_paths(args):
120120
CURRENT_USER_FOLDER: str = os.path.join(GENERAL_OUTPUT_FOLDER, args.author)
121121

122122
# Folder for the current justification
123-
justification_name = (NOW + " " + str(args.naf) + " " + NAF_TO_NAME[args.naf] + " from " +
123+
justification_name = (NOW + " " + str(args.naf) + " " + naf_to_name[args.naf] + " from " +
124124
str(args.begin.strftime("%Y-%m")) + " to " +
125125
str(args.end.strftime("%Y-%m")))
126126
CURRENT_JUSTIFICATION_FOLDER = os.path.join(CURRENT_USER_FOLDER, justification_name)
127127

128128
USER_REPORT_FILE = os.path.join(CURRENT_JUSTIFICATION_FOLDER, NOW + "_" +
129129
args.naf.__str__()
130-
+ "_" + NAF_TO_NAME[args.naf].replace(" ", "_") + "_" +
130+
+ "_" + naf_to_name[args.naf].replace(" ", "_") + "_" +
131131
args.begin.strftime("%Y-%m") + "-" + args.end.strftime("%Y-%m") + ".log.txt")
132132
return CURRENT_USER_FOLDER, CURRENT_JUSTIFICATION_FOLDER, USER_REPORT_FILE, ADMIN_LOG_PATH, SUPERVISOR_LOG_PATH
133133

134134

135+
def remove_folder(folder_path):
136+
"""Remove the folder at the given path if it exists. Do nothing if it doesn't."""
137+
try:
138+
shutil.rmtree(folder_path)
139+
except FileNotFoundError:
140+
pass # Do nothing if the folder does not exist
141+
except Exception as e:
142+
print(f"Error removing folder {folder_path}: {e}")
143+
144+
135145
def ensure_file_structure(CURRENT_USER_FOLDER, CURRENT_JUSTIFICATION_FOLDER):
136146
os.makedirs(GENERAL_OUTPUT_FOLDER, exist_ok=True)
137147
ensure_output_gitignore()

src/main.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import os.path
33
import os.path
44
import shutil
5+
import time
56

6-
from NAF import NAF, NAF_TO_DNI
7-
from arguments import parse_date, process_arguments
7+
from NAF import NAF, build_naf_to_dni, build_naf_to_name_and_surname
8+
from arguments import parse_date, process_parse_arguments
89
from data import get_rlc_monthly_result_structure, parse_date_from_salary_filename, \
910
parse_salary_filename_from_salary_path, unparse_date, parse_salary_type, unparse_month
1011
from defines import *
@@ -15,6 +16,8 @@
1516
merge_pdfs, compact_folder, parse_regular_salary_type, get_matching_pages
1617
from report import get_end_user_report, get_initial_user_report
1718
from custom_except import UndefinedRegularSalaryType
19+
from sharepoint import ensure_input_folder
20+
from chrono import elapsed_time
1821

1922

2023
def process_rlc_aux(salary_date, rlc_folder_path, months_found, rlc_subtype: str, rlc_type: str):
@@ -32,9 +35,9 @@ def process_rlc_aux(salary_date, rlc_folder_path, months_found, rlc_subtype: str
3235
return rlc_n_path
3336
else:
3437
logger.error("Monthly salary was found, but the expected L" + rlc_type + " RLC of type " + rlc_subtype + " was "
35-
"not found in the "
36-
"expected location "
37-
"" + str(
38+
"not found in the "
39+
"expected location "
40+
"" + str(
3841
rlc_n_path) + ". Skipping merge of this salary file.")
3942
raise ValueError("File was not detected") # TODO custom except
4043

@@ -340,10 +343,22 @@ def process_RNTs(rnts_folder_path, naf_dir, naf, begin, end):
340343

341344

342345
def main():
343-
args = process_arguments()
346+
start_time = time.time()
347+
348+
args = process_parse_arguments()
349+
350+
# Ensure fresh input data
351+
if args.input == "sharepoint":
352+
ensure_input_folder(INPUT_FOLDER)
353+
elif args.input == "local":
354+
pass
355+
356+
start_time = time.time()
357+
358+
NAF_TO_DNI = build_naf_to_dni(NAF_DATA_PATH)
344359

345360
current_user_folder, current_justification_folder, user_report_file, admin_log_path, supervisor_log_path = (
346-
compute_paths(args))
361+
compute_paths(args, NAF_TO_DNI))
347362

348363
ensure_file_structure(current_user_folder, current_justification_folder)
349364

@@ -357,6 +372,14 @@ def main():
357372

358373
raw_logger.info(get_initial_user_report(args))
359374

375+
end_time = elapsed_time(start_time)
376+
raw_logger.info("Time elapsed for downloading and validating input data: " + str(end_time) + ".")
377+
start_time = time.time()
378+
379+
# Build dictionaries to translate NAF to different identifier data
380+
NAF_TO_NAME = build_naf_to_name_and_surname(NAF_DATA_PATH)
381+
382+
# Begin processing
360383
reports = {}
361384
# Salaries & RLC
362385
reports[DocType.SALARY] = process_salaries_with_rlc(SALARIES_FOLDER, RLCS_FOLDER, current_justification_folder,
@@ -392,6 +415,12 @@ def main():
392415
report_text = get_end_user_report(reports, args)
393416
final_logger.info(report_text)
394417

418+
end_time = elapsed_time(start_time)
419+
raw_logger.info("Time elapsed for doing this justification: " + str(end_time) + ".")
420+
start_time = time.time()
421+
elapsed_time(start_time)
422+
423+
395424

396425
if __name__ == "__main__":
397426
main()

0 commit comments

Comments
 (0)