psr-factory 4.1.0b1__py3-none-win_amd64.whl → 4.1.0b2__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/apps/__init__.py +7 -0
- psr/apps/apps.py +232 -0
- psr/apps/version.py +5 -0
- psr/cloud/__init__.py +7 -0
- psr/cloud/cloud.py +1041 -0
- psr/cloud/data.py +105 -0
- psr/cloud/desktop.py +82 -0
- psr/cloud/log.py +40 -0
- psr/cloud/status.py +81 -0
- psr/cloud/tempfile.py +117 -0
- psr/cloud/version.py +5 -0
- psr/cloud/xml.py +55 -0
- psr/factory/__init__.py +1 -1
- psr/factory/factory.dll +0 -0
- psr/factory/libcurl-x64.dll +0 -0
- psr/psrfcommon/__init__.py +6 -0
- psr/psrfcommon/psrfcommon.py +54 -0
- psr/psrfcommon/tempfile.py +118 -0
- psr/runner/__init__.py +7 -0
- psr/runner/runner.py +649 -0
- psr/runner/version.py +5 -0
- {psr_factory-4.1.0b1.dist-info → psr_factory-4.1.0b2.dist-info}/METADATA +7 -12
- psr_factory-4.1.0b2.dist-info/RECORD +30 -0
- psr_factory-4.1.0b1.dist-info/RECORD +0 -11
- {psr_factory-4.1.0b1.dist-info → psr_factory-4.1.0b2.dist-info}/WHEEL +0 -0
- {psr_factory-4.1.0b1.dist-info → psr_factory-4.1.0b2.dist-info}/top_level.txt +0 -0
psr/cloud/data.py
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import os
|
6
|
+
from datetime import datetime
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import List, Optional, Tuple, Union
|
9
|
+
|
10
|
+
|
11
|
+
class CloudInputError(ValueError):
|
12
|
+
"""Raised when invalid input is provided."""
|
13
|
+
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class CloudError(RuntimeError):
|
18
|
+
"""Raised when case remote execution fails."""
|
19
|
+
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class Case:
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
name: str,
|
27
|
+
data_path: Optional[Union[str, Path]],
|
28
|
+
program: str,
|
29
|
+
program_version: Union[str, int],
|
30
|
+
execution_type: Union[str, int],
|
31
|
+
price_optimized: bool,
|
32
|
+
number_of_processes: int,
|
33
|
+
memory_per_process_ratio: str,
|
34
|
+
**kwargs,
|
35
|
+
) -> None:
|
36
|
+
self.name: str = name
|
37
|
+
self._validate_type(self.name, str, "Case name must be a string")
|
38
|
+
|
39
|
+
self.data_path: str = str(data_path)
|
40
|
+
if data_path and not os.path.isabs(data_path):
|
41
|
+
self.data_path = os.path.abspath(data_path)
|
42
|
+
if data_path and not Path(data_path).exists():
|
43
|
+
raise CloudInputError("Data path does not exist")
|
44
|
+
|
45
|
+
self.program: str = program
|
46
|
+
self._validate_type(self.program, str, "Program must be a string")
|
47
|
+
|
48
|
+
self.program_version: Union[str, int] = program_version
|
49
|
+
self._validate_type(
|
50
|
+
self.program_version,
|
51
|
+
(int, str),
|
52
|
+
"Program version must be an integer or string (id or name)",
|
53
|
+
)
|
54
|
+
|
55
|
+
self.execution_type: Union[str, int] = execution_type
|
56
|
+
self._validate_type(
|
57
|
+
self.execution_type,
|
58
|
+
(int, str),
|
59
|
+
"Execution type must be an integer or string (id or name)",
|
60
|
+
)
|
61
|
+
|
62
|
+
self.price_optimized: bool = price_optimized
|
63
|
+
self._validate_type(
|
64
|
+
self.price_optimized, bool, "price_optimized must be a boolean"
|
65
|
+
)
|
66
|
+
|
67
|
+
self.number_of_processes: int = number_of_processes
|
68
|
+
self._validate_type(
|
69
|
+
self.number_of_processes, int, "Number of processes must be an integer"
|
70
|
+
)
|
71
|
+
|
72
|
+
self.memory_per_process_ratio: str = memory_per_process_ratio
|
73
|
+
self._validate_type(
|
74
|
+
self.memory_per_process_ratio,
|
75
|
+
str,
|
76
|
+
"Memory per process ratio must be a string",
|
77
|
+
)
|
78
|
+
|
79
|
+
self.repository_duration: Optional[Union[str, int]] = kwargs.get("repository_duration", 2)
|
80
|
+
self._validate_type(
|
81
|
+
self.repository_duration,
|
82
|
+
(int, str),
|
83
|
+
"Repository duration must be an integer or string (id or name)",
|
84
|
+
)
|
85
|
+
|
86
|
+
self.id: Optional[int] = kwargs.get("id", None)
|
87
|
+
self.user: Optional[str] = kwargs.get("user", None)
|
88
|
+
self.parent_case_id: Optional[int] = kwargs.get("parent_case_id", None)
|
89
|
+
self.execution_date: Optional[datetime] = kwargs.get("execution_date", None)
|
90
|
+
self.budget: Optional[str] = kwargs.get("budget", None)
|
91
|
+
if self.budget is not None:
|
92
|
+
self.budget = self.budget.strip()
|
93
|
+
|
94
|
+
# Save In Cloud
|
95
|
+
self.upload_only = kwargs.get("upload_only", False)
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def _validate_type(
|
99
|
+
value, expected_type: Union[List[type], Tuple[type], type], error_message: str
|
100
|
+
):
|
101
|
+
if not isinstance(value, expected_type):
|
102
|
+
raise CloudInputError(error_message)
|
103
|
+
|
104
|
+
def __str__(self):
|
105
|
+
return str(self.__dict__)
|
psr/cloud/desktop.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import os
|
6
|
+
import xml.etree.ElementTree as ET
|
7
|
+
from datetime import datetime
|
8
|
+
from typing import Optional
|
9
|
+
|
10
|
+
from .data import Case
|
11
|
+
from .xml import create_desktop_xml
|
12
|
+
|
13
|
+
|
14
|
+
def import_case(case: Case, console_cluster: str, instance_type_id: int) -> None:
|
15
|
+
case_counter = _get_last_case_id()
|
16
|
+
filepath = os.path.expandvars(
|
17
|
+
os.path.join(r"%appdata%\PSR\PSRCloud\Dados", f"Caso{case_counter}.xml")
|
18
|
+
)
|
19
|
+
now = datetime.now()
|
20
|
+
case_datetime = now.strftime("%d/%m/%Y %H:%M")
|
21
|
+
case_date = now.strftime("%d/%m/%Y")
|
22
|
+
case_lifetime = ""
|
23
|
+
case_budget = "" if case.budget is None else case.budget
|
24
|
+
repositorio_template = (
|
25
|
+
case.parent_case_id
|
26
|
+
if case.parent_case_id is not None and case.parent_case_id != 0
|
27
|
+
else ""
|
28
|
+
)
|
29
|
+
|
30
|
+
parameters = {
|
31
|
+
"Id": str(case_counter),
|
32
|
+
"IdRepositorio": str(case.id),
|
33
|
+
"IdFila": str(case.id),
|
34
|
+
"Modelo": case.program.upper(),
|
35
|
+
"RepositorioTemplate": repositorio_template,
|
36
|
+
"DirDados": case.data_path,
|
37
|
+
"TipoExecucao": str(case.execution_type),
|
38
|
+
"InstanciaTipo": str(instance_type_id),
|
39
|
+
"Cluster": console_cluster,
|
40
|
+
"ClusterSrv": console_cluster,
|
41
|
+
"DtX": case_datetime,
|
42
|
+
"NProc": str(case.number_of_processes),
|
43
|
+
"Versao": str(case.program_version),
|
44
|
+
"Status": "2",
|
45
|
+
"StatusX": "Executando",
|
46
|
+
"Budget": case_budget,
|
47
|
+
"PodeListarFila": "True",
|
48
|
+
"IdX": case.name,
|
49
|
+
"Nom": case.name,
|
50
|
+
"Tag": f"PyCloud\\{case_date.replace('/', '-')}",
|
51
|
+
"DuracaoRepositorio": case_lifetime,
|
52
|
+
"FlagMaqNormalS": "False",
|
53
|
+
}
|
54
|
+
|
55
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
56
|
+
xml_contents = create_desktop_xml(parameters)
|
57
|
+
f.write(xml_contents)
|
58
|
+
|
59
|
+
|
60
|
+
def _get_last_case_id() -> Optional[int]:
|
61
|
+
config_path = os.path.expandvars(r"%appdata%\PSR\PSRCloud\ePSRConfig.xml")
|
62
|
+
data_path = os.path.expandvars(r"%appdata%\PSR\PSRCloud\Dados")
|
63
|
+
|
64
|
+
if os.path.isfile(config_path):
|
65
|
+
xml = ET.parse(config_path, parser=ET.XMLParser(encoding="utf-16"))
|
66
|
+
root = xml.getroot()
|
67
|
+
last_case_id = None
|
68
|
+
for child in root.iter("Aplicacao"):
|
69
|
+
last_case_id = int(child.get("idUltimoCaso"))
|
70
|
+
break
|
71
|
+
if last_case_id is not None:
|
72
|
+
# Check for existing files with the same
|
73
|
+
# last_case_id. Increment it if necessary.
|
74
|
+
last_case_id = int(last_case_id)
|
75
|
+
files = os.listdir(data_path)
|
76
|
+
while f"Caso{last_case_id}.xml" in files:
|
77
|
+
last_case_id += 1
|
78
|
+
return last_case_id
|
79
|
+
# No case has been run yet
|
80
|
+
return None
|
81
|
+
else:
|
82
|
+
raise Exception("ERROR: PSR Cloud Desktop is not installed.")
|
psr/cloud/log.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
|
8
|
+
|
9
|
+
def get_logger(
|
10
|
+
id: int, quiet: bool, debug_mode: bool, log_dir: str = os.getcwd()
|
11
|
+
) -> logging.Logger:
|
12
|
+
logger = logging.getLogger(str(id))
|
13
|
+
logger.setLevel(logging.DEBUG)
|
14
|
+
formatter = logging.Formatter("%(asctime)s - %(message)s")
|
15
|
+
|
16
|
+
if debug_mode:
|
17
|
+
os.makedirs(log_dir, exist_ok=True)
|
18
|
+
file_handler = logging.FileHandler(f"{log_dir}/psr_cloud_console_{id}.log")
|
19
|
+
file_handler.setLevel(logging.DEBUG)
|
20
|
+
file_handler.setFormatter(formatter)
|
21
|
+
logger.addHandler(file_handler)
|
22
|
+
|
23
|
+
if not quiet:
|
24
|
+
console_handler = logging.StreamHandler()
|
25
|
+
if debug_mode:
|
26
|
+
console_handler.setLevel(logging.DEBUG)
|
27
|
+
else:
|
28
|
+
console_handler.setLevel(logging.INFO)
|
29
|
+
console_handler.setFormatter(formatter)
|
30
|
+
logger.addHandler(console_handler)
|
31
|
+
|
32
|
+
return logger
|
33
|
+
|
34
|
+
|
35
|
+
def enable_log_timestamp(logger: logging.Logger, enable: bool) -> None:
|
36
|
+
for handler in logger.handlers:
|
37
|
+
if enable:
|
38
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(message)s"))
|
39
|
+
else:
|
40
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
psr/cloud/status.py
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
|
7
|
+
|
8
|
+
class ExecutionStatus(Enum):
|
9
|
+
QUEUE = 0
|
10
|
+
PENDING = 1
|
11
|
+
RUNNING = 2
|
12
|
+
SUCCESS = 3
|
13
|
+
ERROR = 4
|
14
|
+
SYNC_ERROR = 5
|
15
|
+
WAITING_CANCEL = 6
|
16
|
+
CANCELLED = 7
|
17
|
+
NOT_IN_QUEUE = 8
|
18
|
+
CANCELLED_PENDING = 9
|
19
|
+
WAITING_MACHINE = 10
|
20
|
+
WAITING_SPOT = 11
|
21
|
+
SPOT_ACTIVE = 12
|
22
|
+
SPOT_CANCELLED = 13
|
23
|
+
SPOT_CANCELLED_PENDING = 14
|
24
|
+
SERVER_CANCELLED = 15
|
25
|
+
ABORTED = 16
|
26
|
+
WAITING_STOP = 17
|
27
|
+
STOPPED = 18
|
28
|
+
CANCELLED_IDLE = 19
|
29
|
+
WAITING_START = 20
|
30
|
+
WAITING_MACHINE_ON = 21
|
31
|
+
WAITING_ARCHITECTURE = 22
|
32
|
+
WARNING = 23
|
33
|
+
PARENT_CASE_FAILED = 24
|
34
|
+
RESTART_INTERRUPTED = 27
|
35
|
+
SPECIAL_WAITING = 30
|
36
|
+
WAITING_UPLOAD_QUEUE = 31
|
37
|
+
UPLOADED = 32
|
38
|
+
|
39
|
+
|
40
|
+
FAULTY_TERMINATION_STATUS = [
|
41
|
+
ExecutionStatus.ERROR,
|
42
|
+
ExecutionStatus.SYNC_ERROR,
|
43
|
+
ExecutionStatus.CANCELLED,
|
44
|
+
ExecutionStatus.NOT_IN_QUEUE,
|
45
|
+
ExecutionStatus.ABORTED,
|
46
|
+
ExecutionStatus.CANCELLED_IDLE,
|
47
|
+
ExecutionStatus.RESTART_INTERRUPTED,
|
48
|
+
]
|
49
|
+
|
50
|
+
FINISHED_STATUS = FAULTY_TERMINATION_STATUS + [ExecutionStatus.SUCCESS]
|
51
|
+
|
52
|
+
STATUS_MAP_TEXT = {
|
53
|
+
ExecutionStatus.QUEUE: "Item in the queue without machine allocations",
|
54
|
+
ExecutionStatus.PENDING: "Waiting to turn machines on",
|
55
|
+
ExecutionStatus.RUNNING: "Running",
|
56
|
+
ExecutionStatus.SUCCESS: "Finished successfully",
|
57
|
+
ExecutionStatus.ERROR: "Finished with errors",
|
58
|
+
ExecutionStatus.SYNC_ERROR: "Finished due to lack of update request in a synchronous execution",
|
59
|
+
ExecutionStatus.WAITING_CANCEL: "The cancellation was requested, but there is still no response from the queue processor",
|
60
|
+
ExecutionStatus.CANCELLED: "Cancelled process",
|
61
|
+
ExecutionStatus.NOT_IN_QUEUE: "Process is not in the queue",
|
62
|
+
ExecutionStatus.CANCELLED_PENDING: "The cancellation process has already been called, but there is still no response from the Process Handler to complete the cancellation",
|
63
|
+
ExecutionStatus.WAITING_MACHINE: "State where the queue is waiting for the machines to be connected to move to PENDING state",
|
64
|
+
ExecutionStatus.WAITING_SPOT: "Waiting for the spot offer to be made",
|
65
|
+
ExecutionStatus.SPOT_ACTIVE: "The bid is in active mode",
|
66
|
+
ExecutionStatus.SPOT_CANCELLED: "The platform requested cancellation, but there is still no response from the Queue Processor",
|
67
|
+
ExecutionStatus.SPOT_CANCELLED_PENDING: "The cancellation process has already been called by the server, but there is still no response from the Process Handler to complete the cancellation",
|
68
|
+
ExecutionStatus.SERVER_CANCELLED: "Cancelled process by the server",
|
69
|
+
ExecutionStatus.ABORTED: "The model runs more aborts due to lack of input files",
|
70
|
+
ExecutionStatus.WAITING_STOP: "Waiting for STOP by the queue processor",
|
71
|
+
ExecutionStatus.STOPPED: "STOP machines in the round are frozen",
|
72
|
+
ExecutionStatus.CANCELLED_IDLE: "Cancelled after being idle",
|
73
|
+
ExecutionStatus.WAITING_START: "Waiting for START by the queue processor",
|
74
|
+
ExecutionStatus.WAITING_MACHINE_ON: "Waiting for machine to turn on",
|
75
|
+
ExecutionStatus.WAITING_ARCHITECTURE: "Waiting for architecture change",
|
76
|
+
ExecutionStatus.WARNING: "Finished with warnings",
|
77
|
+
ExecutionStatus.PARENT_CASE_FAILED: "Parent case failed",
|
78
|
+
ExecutionStatus.SPECIAL_WAITING: "Special Waiting",
|
79
|
+
ExecutionStatus.WAITING_UPLOAD_QUEUE: "Waiting for uploading case",
|
80
|
+
ExecutionStatus.UPLOADED: "Uploaded and Saved in Cloud",
|
81
|
+
}
|
psr/cloud/tempfile.py
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import errno
|
6
|
+
import io
|
7
|
+
import os
|
8
|
+
from random import Random
|
9
|
+
|
10
|
+
|
11
|
+
class _RandomNameSequence:
|
12
|
+
"""An instance of _RandomNameSequence generates an endless
|
13
|
+
sequence of unpredictable strings which can safely be incorporated
|
14
|
+
into file names. Each string is eight characters long. Multiple
|
15
|
+
threads can safely use the same instance at the same time.
|
16
|
+
|
17
|
+
_RandomNameSequence is an iterator."""
|
18
|
+
|
19
|
+
# Method extracted from tempfile Python's module.
|
20
|
+
|
21
|
+
characters = "abcdefghijklmnopqrstuvwxyz0123456789_"
|
22
|
+
|
23
|
+
@property
|
24
|
+
def rng(self):
|
25
|
+
cur_pid = os.getpid()
|
26
|
+
if cur_pid != getattr(self, "_rng_pid", None):
|
27
|
+
self._rng = Random() # nosec
|
28
|
+
self._rng_pid = cur_pid
|
29
|
+
return self._rng
|
30
|
+
|
31
|
+
def __iter__(self):
|
32
|
+
return self
|
33
|
+
|
34
|
+
def __next__(self):
|
35
|
+
c = self.characters
|
36
|
+
choose = self.rng.choice
|
37
|
+
letters = [choose(c) for dummy in range(8)]
|
38
|
+
return "".join(letters)
|
39
|
+
|
40
|
+
|
41
|
+
def _get_tempfile_name(base_path: str, prefix: str):
|
42
|
+
"""Calculate the default directory to use for temporary files.
|
43
|
+
This routine should be called exactly once.
|
44
|
+
|
45
|
+
We determine whether a candidate temp dir is usable by
|
46
|
+
trying to create and write to a file in that directory. If this
|
47
|
+
is successful, the test file is deleted. To prevent denial of
|
48
|
+
service, the name of the test file must be randomized."""
|
49
|
+
# Method extracted from tempfile Python's module.
|
50
|
+
|
51
|
+
_text_openflags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
52
|
+
if hasattr(os, "O_NOFOLLOW"):
|
53
|
+
_text_openflags |= os.O_NOFOLLOW
|
54
|
+
|
55
|
+
_bin_openflags = _text_openflags
|
56
|
+
if hasattr(os, "O_BINARY"):
|
57
|
+
_bin_openflags |= os.O_BINARY
|
58
|
+
|
59
|
+
namer = _RandomNameSequence()
|
60
|
+
|
61
|
+
if base_path != os.curdir:
|
62
|
+
base_path = os.path.abspath(base_path)
|
63
|
+
# Try only a few names per directory.
|
64
|
+
for seq in range(100):
|
65
|
+
name = next(namer)
|
66
|
+
filename = os.path.join(base_path, prefix + name)
|
67
|
+
try:
|
68
|
+
fd = os.open(filename, _bin_openflags, 0o600)
|
69
|
+
try:
|
70
|
+
try:
|
71
|
+
with io.open(fd, "wb", closefd=False) as fp:
|
72
|
+
fp.write(b"blat")
|
73
|
+
finally:
|
74
|
+
os.close(fd)
|
75
|
+
finally:
|
76
|
+
os.unlink(filename)
|
77
|
+
return filename
|
78
|
+
except FileExistsError:
|
79
|
+
pass
|
80
|
+
except PermissionError:
|
81
|
+
# This exception is thrown when a directory with the chosen name
|
82
|
+
# already exists on windows.
|
83
|
+
if (
|
84
|
+
os.name == "nt"
|
85
|
+
and os.path.isdir(base_path)
|
86
|
+
and os.access(base_path, os.W_OK)
|
87
|
+
):
|
88
|
+
continue
|
89
|
+
break # no point trying more names in this directory
|
90
|
+
except OSError:
|
91
|
+
break # no point trying more names in this directory
|
92
|
+
raise FileNotFoundError(
|
93
|
+
errno.ENOENT, "No usable temporary file found in " % base_path
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
class CreateTempFile:
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
base_path: str,
|
101
|
+
prefix: str,
|
102
|
+
xml_content: str,
|
103
|
+
delete_temp_xml: bool = True,
|
104
|
+
):
|
105
|
+
self.delete_temp_xml = delete_temp_xml
|
106
|
+
# get temp file name
|
107
|
+
self.xml_file_name = _get_tempfile_name(base_path, prefix) + ".xml"
|
108
|
+
self.xml_content = xml_content
|
109
|
+
|
110
|
+
def __enter__(self):
|
111
|
+
with open(self.xml_file_name, "w", encoding="utf-8-sig") as xml_file:
|
112
|
+
xml_file.write(self.xml_content)
|
113
|
+
return xml_file
|
114
|
+
|
115
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
116
|
+
if self.delete_temp_xml:
|
117
|
+
os.remove(self.xml_file_name)
|
psr/cloud/version.py
ADDED
psr/cloud/xml.py
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# PSR Cloud. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
from typing import Any, Dict
|
6
|
+
from xml.etree import ElementTree as ET
|
7
|
+
|
8
|
+
|
9
|
+
def create_case_xml(parameters: Dict[str, Any]) -> str:
|
10
|
+
root = ET.Element("ColecaoParametro")
|
11
|
+
for name, value in parameters.items():
|
12
|
+
value = _handle_invalid_xml_chars(value)
|
13
|
+
parameter = ET.SubElement(root, "Parametro", nome=name, tipo="System.String")
|
14
|
+
parameter.text = value
|
15
|
+
ET.indent(root, " ")
|
16
|
+
return ET.tostring(root, encoding="unicode", method="xml")
|
17
|
+
|
18
|
+
|
19
|
+
def create_desktop_xml(parameters: Dict[str, Any]) -> str:
|
20
|
+
# use element tree to write the file contents instead
|
21
|
+
node = ET.Element("Repositorio")
|
22
|
+
case_node = ET.SubElement(node, "CasoOperacao")
|
23
|
+
for key, value in parameters.items():
|
24
|
+
value_escaped = _handle_invalid_xml_chars(value)
|
25
|
+
case_node.set(key, value_escaped)
|
26
|
+
tree = ET.ElementTree(node)
|
27
|
+
return ET.tostring(
|
28
|
+
tree.getroot(), encoding="unicode", method="xml", xml_declaration=False
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
def _return_invalid_xml_chars(xml_content: str) -> str:
|
33
|
+
special_chars = {
|
34
|
+
"&": "&",
|
35
|
+
"<": "<",
|
36
|
+
">": ">",
|
37
|
+
""": '"',
|
38
|
+
"'": "'",
|
39
|
+
}
|
40
|
+
for char, replacement in special_chars.items():
|
41
|
+
xml_content = xml_content.replace(char, replacement)
|
42
|
+
return xml_content
|
43
|
+
|
44
|
+
|
45
|
+
def _handle_invalid_xml_chars(xml_content: str) -> str:
|
46
|
+
special_chars = {
|
47
|
+
"&": "&",
|
48
|
+
"<": "<",
|
49
|
+
">": ">",
|
50
|
+
'"': """,
|
51
|
+
"'": "'",
|
52
|
+
}
|
53
|
+
for char, replacement in special_chars.items():
|
54
|
+
xml_content = str(xml_content).replace(char, replacement)
|
55
|
+
return xml_content
|
psr/factory/__init__.py
CHANGED
psr/factory/factory.dll
CHANGED
Binary file
|
Binary file
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# PSR Factory. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import os
|
6
|
+
import pathlib
|
7
|
+
import subprocess
|
8
|
+
import sys
|
9
|
+
from contextlib import contextmanager
|
10
|
+
from typing import Union, List
|
11
|
+
|
12
|
+
|
13
|
+
@contextmanager
|
14
|
+
def change_cwd(new_dir: Union[str, pathlib.Path]):
|
15
|
+
last_dir = os.getcwd()
|
16
|
+
os.chdir(new_dir)
|
17
|
+
try:
|
18
|
+
yield
|
19
|
+
finally:
|
20
|
+
os.chdir(last_dir)
|
21
|
+
|
22
|
+
|
23
|
+
def exec_cmd(cmd: Union[str, List[str]], **kwargs) -> int:
|
24
|
+
dry_run = kwargs.get("dry_run", False)
|
25
|
+
print_progress = kwargs.get("show_progress", False)
|
26
|
+
|
27
|
+
if print_progress or dry_run:
|
28
|
+
sys.stdout.flush()
|
29
|
+
|
30
|
+
if dry_run:
|
31
|
+
if isinstance(cmd, list):
|
32
|
+
print(" ".join(cmd))
|
33
|
+
else:
|
34
|
+
print(cmd)
|
35
|
+
return_code = 0
|
36
|
+
else:
|
37
|
+
try:
|
38
|
+
return_code = subprocess.call(cmd, shell=True)
|
39
|
+
if return_code > 0:
|
40
|
+
raise RuntimeError(f"Execution error, code {return_code}")
|
41
|
+
else:
|
42
|
+
if print_progress:
|
43
|
+
print("Execution success", return_code)
|
44
|
+
except OSError as e:
|
45
|
+
msg = f"Execution failed: {e}"
|
46
|
+
if print_progress:
|
47
|
+
print(msg, file=sys.stderr)
|
48
|
+
raise RuntimeError(msg)
|
49
|
+
|
50
|
+
if print_progress or dry_run:
|
51
|
+
sys.stdout.flush()
|
52
|
+
return return_code
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# PSR Factory. Copyright (C) PSR, Inc - All Rights Reserved
|
2
|
+
# Unauthorized copying of this file, via any medium is strictly prohibited
|
3
|
+
# Proprietary and confidential
|
4
|
+
|
5
|
+
import errno
|
6
|
+
import io
|
7
|
+
import os
|
8
|
+
from random import Random
|
9
|
+
|
10
|
+
|
11
|
+
class _RandomNameSequence:
|
12
|
+
"""An instance of _RandomNameSequence generates an endless
|
13
|
+
sequence of unpredictable strings which can safely be incorporated
|
14
|
+
into file names. Each string is eight characters long. Multiple
|
15
|
+
threads can safely use the same instance at the same time.
|
16
|
+
|
17
|
+
_RandomNameSequence is an iterator."""
|
18
|
+
|
19
|
+
# Method extracted from tempfile Python's module.
|
20
|
+
|
21
|
+
characters = "abcdefghijklmnopqrstuvwxyz0123456789_"
|
22
|
+
|
23
|
+
@property
|
24
|
+
def rng(self):
|
25
|
+
cur_pid = os.getpid()
|
26
|
+
if cur_pid != getattr(self, "_rng_pid", None):
|
27
|
+
self._rng = Random() # nosec
|
28
|
+
self._rng_pid = cur_pid
|
29
|
+
return self._rng
|
30
|
+
|
31
|
+
def __iter__(self):
|
32
|
+
return self
|
33
|
+
|
34
|
+
def __next__(self):
|
35
|
+
c = self.characters
|
36
|
+
choose = self.rng.choice
|
37
|
+
letters = [choose(c) for dummy in range(8)]
|
38
|
+
return "".join(letters)
|
39
|
+
|
40
|
+
|
41
|
+
def _get_tempfile_name(base_path: str, prefix: str):
|
42
|
+
"""Calculate the default directory to use for temporary files.
|
43
|
+
This routine should be called exactly once.
|
44
|
+
|
45
|
+
We determine whether a candidate temp dir is usable by
|
46
|
+
trying to create and write to a file in that directory. If this
|
47
|
+
is successful, the test file is deleted. To prevent denial of
|
48
|
+
service, the name of the test file must be randomized."""
|
49
|
+
# Method extracted from tempfile Python's module.
|
50
|
+
|
51
|
+
_text_openflags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
52
|
+
if hasattr(os, "O_NOFOLLOW"):
|
53
|
+
_text_openflags |= os.O_NOFOLLOW
|
54
|
+
|
55
|
+
_bin_openflags = _text_openflags
|
56
|
+
if hasattr(os, "O_BINARY"):
|
57
|
+
_bin_openflags |= os.O_BINARY
|
58
|
+
|
59
|
+
namer = _RandomNameSequence()
|
60
|
+
|
61
|
+
if base_path != os.curdir:
|
62
|
+
base_path = os.path.abspath(base_path)
|
63
|
+
# Try only a few names per directory.
|
64
|
+
for seq in range(100):
|
65
|
+
name = next(namer)
|
66
|
+
filename = os.path.join(base_path, prefix + name)
|
67
|
+
try:
|
68
|
+
fd = os.open(filename, _bin_openflags, 0o600)
|
69
|
+
try:
|
70
|
+
try:
|
71
|
+
with io.open(fd, "wb", closefd=False) as fp:
|
72
|
+
fp.write(b"blat")
|
73
|
+
finally:
|
74
|
+
os.close(fd)
|
75
|
+
finally:
|
76
|
+
os.unlink(filename)
|
77
|
+
return filename
|
78
|
+
except FileExistsError:
|
79
|
+
pass
|
80
|
+
except PermissionError:
|
81
|
+
# This exception is thrown when a directory with the chosen name
|
82
|
+
# already exists on windows.
|
83
|
+
if (
|
84
|
+
os.name == "nt"
|
85
|
+
and os.path.isdir(base_path)
|
86
|
+
and os.access(base_path, os.W_OK)
|
87
|
+
):
|
88
|
+
continue
|
89
|
+
break # no point trying more names in this directory
|
90
|
+
except OSError:
|
91
|
+
break # no point trying more names in this directory
|
92
|
+
raise FileNotFoundError(
|
93
|
+
errno.ENOENT, "No usable temporary file found in " % base_path
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
class CreateTempFile:
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
base_path: str,
|
101
|
+
prefix: str,
|
102
|
+
file_content: str,
|
103
|
+
extension: str = ".dat",
|
104
|
+
delete_tempfile: bool = True,
|
105
|
+
):
|
106
|
+
self.delete_tempfile = delete_tempfile
|
107
|
+
# get temp file name
|
108
|
+
self.temp_file_name = _get_tempfile_name(base_path, prefix) + extension
|
109
|
+
self.temp_content = file_content
|
110
|
+
|
111
|
+
def __enter__(self):
|
112
|
+
with open(self.temp_file_name, "w", encoding="utf-8-sig") as tempfile:
|
113
|
+
tempfile.write(self.temp_content)
|
114
|
+
return tempfile
|
115
|
+
|
116
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
117
|
+
if self.delete_tempfile:
|
118
|
+
os.remove(self.temp_file_name)
|