psr-factory 4.0.27__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/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
@@ -0,0 +1,5 @@
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
+ __version__ = "0.3.3"
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
+ "&lt;": "<",
36
+ "&gt;": ">",
37
+ "&quot;": '"',
38
+ "&apos;": "'",
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
+ "&": "&amp;",
48
+ "<": "&lt;",
49
+ ">": "&gt;",
50
+ '"': "&quot;",
51
+ "'": "&apos;",
52
+ }
53
+ for char, replacement in special_chars.items():
54
+ xml_content = str(xml_content).replace(char, replacement)
55
+ return xml_content
@@ -0,0 +1,7 @@
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
+ __version__ = "4.0.27"
6
+
7
+ from .api import *