psr-factory 4.1.0b11__py3-none-win_amd64.whl → 5.0.0b1__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- psr/cloud/cloud.py +351 -28
- psr/factory/__init__.py +1 -1
- psr/factory/api.py +5 -0
- psr/factory/factory.dll +0 -0
- psr/factory/factory.pmd +322 -37
- psr/factory/factory.pmk +378 -52
- psr/factory/libcurl-x64.dll +0 -0
- psr/factory/samples/{sddp_sample_case01.py → sddp_case01.py} +6 -5
- psr/factory/samples/{sddp_sample_case21.py → sddp_case21.py} +4 -3
- {psr_factory-4.1.0b11.dist-info → psr_factory-5.0.0b1.dist-info}/METADATA +3 -3
- {psr_factory-4.1.0b11.dist-info → psr_factory-5.0.0b1.dist-info}/RECORD +14 -15
- psr/factory/samples/sddp_1_stage_case01.py +0 -169
- {psr_factory-4.1.0b11.dist-info → psr_factory-5.0.0b1.dist-info}/WHEEL +0 -0
- {psr_factory-4.1.0b11.dist-info → psr_factory-5.0.0b1.dist-info}/licenses/LICENSE.txt +0 -0
- {psr_factory-4.1.0b11.dist-info → psr_factory-5.0.0b1.dist-info}/top_level.txt +0 -0
psr/cloud/cloud.py
CHANGED
@@ -4,10 +4,12 @@
|
|
4
4
|
|
5
5
|
import copy
|
6
6
|
import functools
|
7
|
+
import gzip
|
7
8
|
import hashlib
|
8
9
|
import logging
|
9
10
|
import os
|
10
11
|
import re
|
12
|
+
import shutil
|
11
13
|
import subprocess
|
12
14
|
import sys
|
13
15
|
import warnings
|
@@ -21,6 +23,7 @@ import pefile
|
|
21
23
|
import zeep
|
22
24
|
from filelock import FileLock
|
23
25
|
|
26
|
+
from .aws import download_case_from_s3, upload_case_to_s3
|
24
27
|
from .data import Case, CloudError, CloudInputError
|
25
28
|
from .desktop import import_case
|
26
29
|
from .log import enable_log_timestamp, get_logger
|
@@ -49,8 +52,12 @@ def thread_safe():
|
|
49
52
|
return decorator
|
50
53
|
|
51
54
|
|
52
|
-
def _md5sum(value: str) -> str:
|
53
|
-
|
55
|
+
def _md5sum(value: str, enconding=True) -> str:
|
56
|
+
if enconding:
|
57
|
+
return hashlib.md5(value.encode("utf-8")).hexdigest() # nosec
|
58
|
+
else:
|
59
|
+
# hash binary data
|
60
|
+
return hashlib.md5(value).hexdigest() # nosec
|
54
61
|
|
55
62
|
|
56
63
|
def hash_password(password: str) -> str:
|
@@ -134,6 +141,7 @@ class Client:
|
|
134
141
|
self._debug_mode = kwargs.get("debug", False)
|
135
142
|
self._dry_run = kwargs.get("dry_run", False)
|
136
143
|
self._timeout = kwargs.get("timeout", None)
|
144
|
+
self._python_client = kwargs.get("python_client", False)
|
137
145
|
|
138
146
|
# Client version
|
139
147
|
self.application_version = kwargs.get("application_version", None)
|
@@ -151,6 +159,11 @@ class Client:
|
|
151
159
|
|
152
160
|
self._logger.info(f"Client uid {log_id} initialized.")
|
153
161
|
|
162
|
+
if self._python_client:
|
163
|
+
self._logger.info(
|
164
|
+
"Using Python client for PSR Cloud. Some features may not be available."
|
165
|
+
)
|
166
|
+
|
154
167
|
self._console_path_setup(**kwargs)
|
155
168
|
|
156
169
|
self._credentials_setup(**kwargs)
|
@@ -617,20 +630,23 @@ class Client:
|
|
617
630
|
if self._dry_run:
|
618
631
|
return xml_content
|
619
632
|
|
620
|
-
self.
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
if id_parameter is None:
|
627
|
-
xml_str = _xml_to_str(xml)
|
628
|
-
raise CloudError(
|
629
|
-
f"Case id not found on returned XML response.\n"
|
630
|
-
f"Contact PSR support at psrcloud@psr-inc.com with following data:\n\n{xml_str}\n\n"
|
633
|
+
if self._python_client:
|
634
|
+
case_id = self._execute_case(parameters)
|
635
|
+
else:
|
636
|
+
self._run_console(xml_content)
|
637
|
+
xml = ET.parse(
|
638
|
+
f"{self._get_console_parent_path()}\\fake{case.program}_async.xml"
|
631
639
|
)
|
640
|
+
_check_for_errors(xml, self._logger)
|
641
|
+
id_parameter = xml.find("./Parametro[@nome='repositorioId']")
|
642
|
+
if id_parameter is None:
|
643
|
+
xml_str = _xml_to_str(xml)
|
644
|
+
raise CloudError(
|
645
|
+
f"Case id not found on returned XML response.\n"
|
646
|
+
f"Contact PSR support at psrcloud@psr-inc.com with following data:\n\n{xml_str}\n\n"
|
647
|
+
)
|
632
648
|
|
633
|
-
|
649
|
+
case_id = int(id_parameter.text)
|
634
650
|
if not wait:
|
635
651
|
self._logger.info(f"Case {case.name} started with id {case_id}")
|
636
652
|
|
@@ -648,7 +664,6 @@ class Client:
|
|
648
664
|
return case_id
|
649
665
|
|
650
666
|
def get_status(self, case_id: int) -> tuple["ExecutionStatus", str]:
|
651
|
-
# case = self.get_case(case_id)
|
652
667
|
delete_xml = not self._debug_mode
|
653
668
|
xml_content = ""
|
654
669
|
with CreateTempFile(
|
@@ -666,10 +681,14 @@ class Client:
|
|
666
681
|
"comando": "obterstatusresultados",
|
667
682
|
"arquivoSaida": status_xml_path,
|
668
683
|
}
|
669
|
-
run_xml_content = create_case_xml(parameters)
|
670
684
|
|
671
|
-
|
672
|
-
|
685
|
+
xml = None
|
686
|
+
if self._python_client:
|
687
|
+
xml = self._get_status_python(parameters)
|
688
|
+
else:
|
689
|
+
run_xml_content = create_case_xml(parameters)
|
690
|
+
self._run_console(run_xml_content)
|
691
|
+
xml = ET.parse(status_xml_path)
|
673
692
|
parameter_status = xml.find("./Parametro[@nome='statusExecucao']")
|
674
693
|
if parameter_status is None:
|
675
694
|
xml_str = _xml_to_str(xml)
|
@@ -689,7 +708,7 @@ class Client:
|
|
689
708
|
self._logger.info(f"Status: {STATUS_MAP_TEXT[status]}")
|
690
709
|
return status, STATUS_MAP_TEXT[status]
|
691
710
|
|
692
|
-
def list_download_files(self, case_id: int) -> List[
|
711
|
+
def list_download_files(self, case_id: int) -> List[dict]:
|
693
712
|
xml_files = self._make_soap_request(
|
694
713
|
"prepararListaArquivosRemotaDownload",
|
695
714
|
"listaArquivoRemota",
|
@@ -719,7 +738,7 @@ class Client:
|
|
719
738
|
files: Optional[List[str]] = None,
|
720
739
|
extensions: Optional[List[str]] = None,
|
721
740
|
) -> None:
|
722
|
-
filter = "
|
741
|
+
filter = ""
|
723
742
|
|
724
743
|
if not extensions and not files:
|
725
744
|
extensions = ["csv", "log", "hdr", "bin", "out", "ok"]
|
@@ -728,13 +747,12 @@ class Client:
|
|
728
747
|
|
729
748
|
if extensions:
|
730
749
|
Client._validate_extensions(extensions)
|
731
|
-
filter_elements.extend([f".*.{ext}
|
750
|
+
filter_elements.extend([f".*.{ext}" for ext in extensions])
|
732
751
|
|
733
752
|
if files:
|
734
753
|
filter_elements.extend(files)
|
735
754
|
|
736
755
|
filter += "|".join(filter_elements)
|
737
|
-
filter += ")$"
|
738
756
|
|
739
757
|
self._logger.info("Download filter: " + filter)
|
740
758
|
case = self.get_case(case_id)
|
@@ -752,9 +770,15 @@ class Client:
|
|
752
770
|
"filtroDownloadPorMascara": filter,
|
753
771
|
}
|
754
772
|
|
755
|
-
|
756
|
-
|
757
|
-
self.
|
773
|
+
os.makedirs(output_path, exist_ok=True)
|
774
|
+
|
775
|
+
if self._python_client:
|
776
|
+
self._download_results_python(parameters) ## Not implemented yet
|
777
|
+
else:
|
778
|
+
# Download results using Console
|
779
|
+
xml_content = create_case_xml(parameters)
|
780
|
+
self._run_console(xml_content)
|
781
|
+
self._logger.info(f"Results downloaded to {output_path}")
|
758
782
|
|
759
783
|
def cancel_case(self, case_id: int, wait: bool = False) -> bool:
|
760
784
|
parameters = {
|
@@ -768,8 +792,12 @@ class Client:
|
|
768
792
|
"idFila": str(case_id),
|
769
793
|
}
|
770
794
|
|
771
|
-
|
772
|
-
|
795
|
+
if self._python_client:
|
796
|
+
self._cancel_case_python(case_id, parameters)
|
797
|
+
else:
|
798
|
+
# Cancel case using Console
|
799
|
+
xml_content = create_case_xml(parameters)
|
800
|
+
self._run_console(xml_content)
|
773
801
|
self._logger.info(f"Request to cancel case {case_id} was sent")
|
774
802
|
|
775
803
|
if wait:
|
@@ -898,7 +926,6 @@ class Client:
|
|
898
926
|
if additional_arguments:
|
899
927
|
parameters.update(additional_arguments)
|
900
928
|
|
901
|
-
# FIXME make additional arguments work as a dictionary to work with this code
|
902
929
|
xml_input = create_case_xml(parameters)
|
903
930
|
|
904
931
|
try:
|
@@ -941,6 +968,284 @@ class Client:
|
|
941
968
|
)
|
942
969
|
return self._cloud_clusters_xml_cache
|
943
970
|
|
971
|
+
def _execute_case(self, case_dict) -> int:
|
972
|
+
"""
|
973
|
+
Execute a case on the PSR Cloud.
|
974
|
+
:param case_dict: Dictionary containing the case parameters.
|
975
|
+
:return: Case ID of the executed case.
|
976
|
+
"""
|
977
|
+
case_dict["programa"] = case_dict["modelo"]
|
978
|
+
case_dict["numeroProcessos"] = case_dict["nproc"]
|
979
|
+
case_dict["versao_cliente"] = "5.4.0"
|
980
|
+
filter_request_result = self._make_soap_request(
|
981
|
+
"obterFiltros", additional_arguments=case_dict
|
982
|
+
)
|
983
|
+
upload_filter = filter_request_result.find(
|
984
|
+
"./Parametro[@nome='filtroUpload']"
|
985
|
+
).text
|
986
|
+
upload_filter = "^[a-zA-Z0-9./_]*(" + upload_filter + ")$"
|
987
|
+
|
988
|
+
# Create Repository
|
989
|
+
self._logger.info("Creating remote repository")
|
990
|
+
repository_request_result = self._make_soap_request(
|
991
|
+
"criarRepositorio", additional_arguments=case_dict
|
992
|
+
)
|
993
|
+
|
994
|
+
# Add all parameters from the XML response to case_dict
|
995
|
+
# Iterates over each <Parametro> element in the XML response,
|
996
|
+
# extracts the 'nome' attribute for the key and the element's text for the value,
|
997
|
+
# then adds this key-value pair to case_dict.
|
998
|
+
for parametro_element in repository_request_result.findall("./Parametro"):
|
999
|
+
param_name = parametro_element.get("nome")
|
1000
|
+
param_value = (
|
1001
|
+
parametro_element.text
|
1002
|
+
) # This will be None if the element has no text.
|
1003
|
+
if param_name: # Ensure the parameter has a name before adding.
|
1004
|
+
case_dict[param_name] = param_value
|
1005
|
+
|
1006
|
+
repository_id = repository_request_result.find(
|
1007
|
+
"./Parametro[@nome='repositorioId']"
|
1008
|
+
)
|
1009
|
+
cloud_access = repository_request_result.find(
|
1010
|
+
"./Parametro[@nome='cloudAccess']"
|
1011
|
+
)
|
1012
|
+
cloud_secret = repository_request_result.find(
|
1013
|
+
"./Parametro[@nome='cloudSecret']"
|
1014
|
+
)
|
1015
|
+
cloud_session_token = repository_request_result.find(
|
1016
|
+
"./Parametro[@nome='cloudSessionToken']"
|
1017
|
+
)
|
1018
|
+
cloud_aws_url = repository_request_result.find("./Parametro[@nome='cloudUrl']")
|
1019
|
+
bucket_name = repository_request_result.find(
|
1020
|
+
"./Parametro[@nome='diretorioBase']"
|
1021
|
+
)
|
1022
|
+
|
1023
|
+
self._logger.info(f"Remote repository created with ID {repository_id.text}")
|
1024
|
+
case_dict["repositorioId"] = repository_id.text
|
1025
|
+
|
1026
|
+
# Filtering files to upload
|
1027
|
+
self._logger.info("Checking list of files to send")
|
1028
|
+
|
1029
|
+
file_list = _filter_upload_files(case_dict["diretorioDados"], upload_filter)
|
1030
|
+
|
1031
|
+
if not file_list:
|
1032
|
+
self._logger.warning(
|
1033
|
+
"No files found to upload. Please check the upload filter."
|
1034
|
+
)
|
1035
|
+
return
|
1036
|
+
|
1037
|
+
# generating .metadata folder with checksum for each file
|
1038
|
+
checksum_dictionary = {}
|
1039
|
+
metadata_folder = Path(case_dict["diretorioDados"]) / ".metadata"
|
1040
|
+
metadata_folder.mkdir(parents=True, exist_ok=True)
|
1041
|
+
for file_path in file_list:
|
1042
|
+
file_path = Path(file_path)
|
1043
|
+
if not file_path.is_absolute():
|
1044
|
+
file_path = Path(case_dict["diretorioDados"]) / file_path
|
1045
|
+
if not file_path.exists():
|
1046
|
+
self._logger.warning(f"File {file_path} does not exist. Skipping.")
|
1047
|
+
continue
|
1048
|
+
with open(file_path, "rb") as f:
|
1049
|
+
checksum = _md5sum(f.read(), enconding=False).upper()
|
1050
|
+
checksum_dictionary[file_path.name] = checksum
|
1051
|
+
metadata_file = metadata_folder / (file_path.name)
|
1052
|
+
with open(metadata_file, "w") as f:
|
1053
|
+
f.write(checksum)
|
1054
|
+
|
1055
|
+
self._logger.info(
|
1056
|
+
f"Uploading list of files to remote repository {repository_id.text}"
|
1057
|
+
)
|
1058
|
+
|
1059
|
+
# Uploading files to S3
|
1060
|
+
upload_case_to_s3(
|
1061
|
+
files=file_list,
|
1062
|
+
repository_id=repository_id.text,
|
1063
|
+
cluster_name=self.cluster["name"],
|
1064
|
+
checksums=checksum_dictionary,
|
1065
|
+
access=cloud_access.text if cloud_access is not None else None,
|
1066
|
+
secret=cloud_secret.text if cloud_secret is not None else None,
|
1067
|
+
session_token=cloud_session_token.text
|
1068
|
+
if cloud_session_token is not None
|
1069
|
+
else None,
|
1070
|
+
bucket_name=bucket_name.text if bucket_name is not None else None,
|
1071
|
+
url=cloud_aws_url.text if cloud_aws_url is not None else None,
|
1072
|
+
zip_compress=True,
|
1073
|
+
)
|
1074
|
+
|
1075
|
+
self._make_soap_request(
|
1076
|
+
"finalizarUpload",
|
1077
|
+
additional_arguments={"repositorioId": repository_id.text},
|
1078
|
+
)
|
1079
|
+
self._logger.info("Files uploaded successfully. Enqueuing case.")
|
1080
|
+
self._make_soap_request("enfileirarProcesso", additional_arguments=case_dict)
|
1081
|
+
|
1082
|
+
return repository_id.text
|
1083
|
+
|
1084
|
+
def _get_status_python(self, case_dict: dict) -> ET.Element:
|
1085
|
+
"""
|
1086
|
+
Get the status of a case using the Python client.
|
1087
|
+
:param case_dict: Dictionary containing the case parameters.
|
1088
|
+
:return: XML Element with the status information.
|
1089
|
+
"""
|
1090
|
+
try:
|
1091
|
+
response = self._make_soap_request(
|
1092
|
+
"obterStatusResultados", additional_arguments=case_dict
|
1093
|
+
)
|
1094
|
+
|
1095
|
+
# change response "status" parameter to "statusExecucao", as it is with current PSR Cloud
|
1096
|
+
status_param = response.find("./Parametro[@nome='status']")
|
1097
|
+
if status_param is not None:
|
1098
|
+
status_param.attrib["nome"] = "statusExecucao"
|
1099
|
+
|
1100
|
+
result_log = response.find("./Parametro[@nome='resultado']")
|
1101
|
+
if self._verbose and result_log is not None:
|
1102
|
+
self._logger.info(result_log.text)
|
1103
|
+
return response
|
1104
|
+
except Exception as e:
|
1105
|
+
self._logger.error(f"Error getting status: {str(e)}")
|
1106
|
+
raise CloudError(f"Failed to get status: {str(e)}")
|
1107
|
+
|
1108
|
+
def _cancel_case_python(self, case_id: int, xml_content: str) -> None:
|
1109
|
+
"""
|
1110
|
+
Cancel a case using the Python client.
|
1111
|
+
:param case_id: The ID of the case to cancel.
|
1112
|
+
:param xml_content: XML content for the cancel request.
|
1113
|
+
"""
|
1114
|
+
try:
|
1115
|
+
self._make_soap_request(
|
1116
|
+
"cancelarFila", additional_arguments={"idFila": str(case_id)}
|
1117
|
+
)
|
1118
|
+
except Exception as e:
|
1119
|
+
self._logger.error(f"Error cancelling case: {str(e)}")
|
1120
|
+
raise CloudError(f"Failed to cancel case: {str(e)}")
|
1121
|
+
|
1122
|
+
def _download_results_python(self, parameters: dict) -> None:
|
1123
|
+
"""
|
1124
|
+
Download results using the Python client.
|
1125
|
+
:param parameters: Dictionary containing the download parameters.
|
1126
|
+
"""
|
1127
|
+
|
1128
|
+
repository_id = parameters.get("repositorioId")
|
1129
|
+
download_filter = parameters.get("filtroDownloadPorMascara")
|
1130
|
+
output_path = parameters.get("diretorioDestino")
|
1131
|
+
|
1132
|
+
download_filter = (
|
1133
|
+
"^[a-zA-Z0-9./_]*(" + download_filter + ")$" if download_filter else None
|
1134
|
+
)
|
1135
|
+
self._logger.info("Obtaining credentials for download")
|
1136
|
+
credentials = self._make_soap_request(
|
1137
|
+
"buscaCredenciasDownload", additional_arguments=parameters
|
1138
|
+
)
|
1139
|
+
|
1140
|
+
access = credentials.find("./Parametro[@nome='cloudAccess']").text
|
1141
|
+
secret = credentials.find("./Parametro[@nome='cloudSecret']").text
|
1142
|
+
session_token = credentials.find("./Parametro[@nome='cloudSessionToken']").text
|
1143
|
+
url = credentials.find("./Parametro[@nome='cloudUrl']").text
|
1144
|
+
bucket_name = credentials.find("./Parametro[@nome='diretorioBase']").text
|
1145
|
+
bucket_name = bucket_name.replace("repository", "repository-download")
|
1146
|
+
|
1147
|
+
if access is None or secret is None or session_token is None or url is None:
|
1148
|
+
raise CloudError("Failed to retrieve credentials for downloading results.")
|
1149
|
+
|
1150
|
+
file_list = self.list_download_files(repository_id)
|
1151
|
+
|
1152
|
+
# filtering files to download
|
1153
|
+
if download_filter:
|
1154
|
+
filtered_file_list = [
|
1155
|
+
file["name"]
|
1156
|
+
for file in file_list
|
1157
|
+
if re.match(download_filter, file["name"])
|
1158
|
+
]
|
1159
|
+
else:
|
1160
|
+
filtered_file_list = [file["name"] for file in file_list]
|
1161
|
+
|
1162
|
+
self._logger.info("Downloading results")
|
1163
|
+
downloaded_list = download_case_from_s3(
|
1164
|
+
repository_id=parameters["repositorioId"],
|
1165
|
+
cluster_name=self.cluster["name"],
|
1166
|
+
access=access,
|
1167
|
+
secret=secret,
|
1168
|
+
session_token=session_token,
|
1169
|
+
bucket_name=bucket_name,
|
1170
|
+
url=url,
|
1171
|
+
output_path=output_path,
|
1172
|
+
file_list=filtered_file_list,
|
1173
|
+
)
|
1174
|
+
|
1175
|
+
# Decompress gzipped files
|
1176
|
+
for file in filtered_file_list:
|
1177
|
+
if self._is_file_gzipped(os.path.join(output_path, file)):
|
1178
|
+
self._decompress_gzipped_file(os.path.join(output_path, file))
|
1179
|
+
|
1180
|
+
# Check if all requested files were downloaded
|
1181
|
+
for file in filtered_file_list:
|
1182
|
+
if file not in downloaded_list:
|
1183
|
+
self._logger.warning(f"File {file} was not downloaded.")
|
1184
|
+
|
1185
|
+
self._logger.info(f"Results downloaded to {output_path}")
|
1186
|
+
|
1187
|
+
def _is_file_gzipped(self, file_path: str) -> bool:
|
1188
|
+
"""
|
1189
|
+
Checks if a file is gzipped by inspecting its magic number.
|
1190
|
+
|
1191
|
+
:param file_path: The path to the file.
|
1192
|
+
:return: True if the file is gzipped, False otherwise.
|
1193
|
+
"""
|
1194
|
+
try:
|
1195
|
+
with open(file_path, "rb") as f_check:
|
1196
|
+
return f_check.read(2) == b"\x1f\x8b"
|
1197
|
+
except IOError:
|
1198
|
+
# TODO: Replace print with proper logging
|
1199
|
+
print(
|
1200
|
+
f"WARNING: Could not read {file_path} to check for gzip magic number."
|
1201
|
+
)
|
1202
|
+
return False
|
1203
|
+
|
1204
|
+
def _decompress_gzipped_file(self, gzipped_file_path: str) -> str:
|
1205
|
+
"""
|
1206
|
+
Decompresses a gzipped file.
|
1207
|
+
|
1208
|
+
If the original filename ends with .gz, the .gz is removed for the
|
1209
|
+
decompressed filename. Otherwise, the file is decompressed in-place.
|
1210
|
+
The original gzipped file is removed upon successful decompression.
|
1211
|
+
|
1212
|
+
:param gzipped_file_path: The path to the gzipped file.
|
1213
|
+
:return: The path to the decompressed file. If decompression fails,
|
1214
|
+
the original gzipped_file_path is returned.
|
1215
|
+
"""
|
1216
|
+
decompressed_target_path = (
|
1217
|
+
gzipped_file_path[:-3]
|
1218
|
+
if gzipped_file_path.lower().endswith(".gz")
|
1219
|
+
else gzipped_file_path
|
1220
|
+
)
|
1221
|
+
# Use a temporary file for decompression to avoid data loss if issues occur
|
1222
|
+
temp_decompressed_path = decompressed_target_path + ".decompressing_tmp"
|
1223
|
+
|
1224
|
+
try:
|
1225
|
+
with gzip.open(gzipped_file_path, "rb") as f_in, open(
|
1226
|
+
temp_decompressed_path, "wb"
|
1227
|
+
) as f_out:
|
1228
|
+
shutil.copyfileobj(f_in, f_out)
|
1229
|
+
os.remove(gzipped_file_path)
|
1230
|
+
os.rename(temp_decompressed_path, decompressed_target_path)
|
1231
|
+
return decompressed_target_path
|
1232
|
+
except (gzip.BadGzipFile, EOFError, IOError) as e:
|
1233
|
+
print(
|
1234
|
+
f"ERROR: Failed to decompress {gzipped_file_path}: {e}. Original file kept."
|
1235
|
+
)
|
1236
|
+
except (
|
1237
|
+
Exception
|
1238
|
+
) as e: # Catch other errors like permission issues during rename/remove
|
1239
|
+
print(
|
1240
|
+
f"ERROR: Error during post-decompression file operations for {gzipped_file_path}: {e}. Original file kept."
|
1241
|
+
)
|
1242
|
+
finally:
|
1243
|
+
if os.path.exists(
|
1244
|
+
temp_decompressed_path
|
1245
|
+
): # Clean up temp file if it still exists
|
1246
|
+
os.remove(temp_decompressed_path)
|
1247
|
+
return gzipped_file_path # Return original path if decompression failed
|
1248
|
+
|
944
1249
|
def get_programs(self) -> List[str]:
|
945
1250
|
xml = self._get_cloud_versions_xml()
|
946
1251
|
programs = [model.attrib["nome"] for model in xml]
|
@@ -1057,6 +1362,24 @@ def _budget_matches_list(budget_part: str, all_budgets: List[str]) -> List[str]:
|
|
1057
1362
|
return [budget for budget in all_budgets if lowered_budget_part in budget.lower()]
|
1058
1363
|
|
1059
1364
|
|
1365
|
+
def _filter_upload_files(directory: str, upload_filter: str) -> List[str]:
|
1366
|
+
"""
|
1367
|
+
Filter files in a directory based on the upload filter.
|
1368
|
+
:param directory: Directory to filter files from.
|
1369
|
+
:param upload_filter: Regular expression filter for file names.
|
1370
|
+
:return: List of filtered file paths.
|
1371
|
+
"""
|
1372
|
+
if not os.path.exists(directory):
|
1373
|
+
raise CloudInputError(f"Directory {directory} does not exist")
|
1374
|
+
|
1375
|
+
regex = re.compile(upload_filter)
|
1376
|
+
filtered_files = []
|
1377
|
+
for file in os.listdir(directory):
|
1378
|
+
if regex.match(file):
|
1379
|
+
filtered_files.append(os.path.join(directory, file))
|
1380
|
+
return filtered_files
|
1381
|
+
|
1382
|
+
|
1060
1383
|
def replace_case_str_values(client: Client, case: Case) -> Case:
|
1061
1384
|
"""Create a new case object using internal integer IDs instead of string values."""
|
1062
1385
|
# Model Version
|
psr/factory/__init__.py
CHANGED
psr/factory/api.py
CHANGED
@@ -1234,6 +1234,11 @@ class Context(DataObject):
|
|
1234
1234
|
context_obj._hdr = None
|
1235
1235
|
return context
|
1236
1236
|
|
1237
|
+
def __repr__(self):
|
1238
|
+
properties = self.as_dict()
|
1239
|
+
props_str = ', '.join(f"{key}={repr(value)}" for key, value in properties.items())
|
1240
|
+
return f"psr.factory.Context({props_str})"
|
1241
|
+
|
1237
1242
|
|
1238
1243
|
class Study(_BaseObject):
|
1239
1244
|
def __init__(self):
|
psr/factory/factory.dll
CHANGED
Binary file
|