protoncloud-sdk 5.0.0__py3-none-any.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.
- proton/__init__.py +0 -0
- proton/proton_automation.py +245 -0
- proton/proton_client_parama.py +59 -0
- proton/proton_env.py +187 -0
- proton/proton_files_and_resources.py +170 -0
- proton/proton_git.py +89 -0
- proton/proton_logs.py +53 -0
- proton/proton_parameter.py +127 -0
- proton/run_status.py +9 -0
- protoncloud_sdk-5.0.0.dist-info/METADATA +114 -0
- protoncloud_sdk-5.0.0.dist-info/RECORD +13 -0
- protoncloud_sdk-5.0.0.dist-info/WHEEL +4 -0
- protoncloud_sdk-5.0.0.dist-info/licenses/LICENSE +12 -0
proton/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from proton.proton_env import get_api_url, get_auth_header, get_host, get_id_dataset_run, is_proton_execution
|
|
4
|
+
from proton.run_status import RunStatus
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_dataset_run_url():
|
|
8
|
+
return get_api_url(f"dataset-run/{get_id_dataset_run()}")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _request_json(method, url, params=None, body=None):
|
|
12
|
+
if not is_proton_execution():
|
|
13
|
+
return {}
|
|
14
|
+
|
|
15
|
+
response = requests.request(method, url, params=params, json=body, headers=get_auth_header())
|
|
16
|
+
|
|
17
|
+
# O print vai para o stdout, que o runner envia como log da execucao no Proton.
|
|
18
|
+
if response.status_code != 200:
|
|
19
|
+
print(f"[proton] {method} {response.url} -> HTTP {response.status_code}: {response.text[:300]}")
|
|
20
|
+
return {}
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
return response.json()
|
|
24
|
+
except ValueError:
|
|
25
|
+
print(f"[proton] {method} {response.url} -> HTTP 200 sem JSON valido: {response.text[:120]!r}")
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_dataset_run_param(service, response_key):
|
|
30
|
+
data = _request_json("GET", f"{_get_dataset_run_url()}/{service}")
|
|
31
|
+
return data.get(response_key, "")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def start_component():
|
|
35
|
+
"""Inicia um componente."""
|
|
36
|
+
if is_proton_execution():
|
|
37
|
+
data = _request_json("PUT", f"{_get_dataset_run_url()}/start-component")
|
|
38
|
+
|
|
39
|
+
if data:
|
|
40
|
+
run_progress_id = data.get("id") or data.get("idRunProgress")
|
|
41
|
+
if run_progress_id is not None:
|
|
42
|
+
os.environ["idRunProgress"] = str(run_progress_id)
|
|
43
|
+
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
def end_component():
|
|
49
|
+
"""Finaliza um componente."""
|
|
50
|
+
|
|
51
|
+
if is_proton_execution():
|
|
52
|
+
data = _request_json("PUT", f"{_get_dataset_run_url()}/end-component")
|
|
53
|
+
os.environ.pop('idRunProgress', None)
|
|
54
|
+
|
|
55
|
+
if data:
|
|
56
|
+
return data.get("returnType", data.get("runType", ""))
|
|
57
|
+
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
def update_run_status(status:RunStatus):
|
|
61
|
+
"""Atualiza o status de execução de um cenário."""
|
|
62
|
+
if is_proton_execution():
|
|
63
|
+
status_value = status.value if isinstance(status, RunStatus) else str(status)
|
|
64
|
+
_request_json("PUT", f"{_get_dataset_run_url()}/update-status", body={"status": status_value})
|
|
65
|
+
|
|
66
|
+
def get_current_component_system():
|
|
67
|
+
"""
|
|
68
|
+
Retorna o tipo de sistema do componente corrente.
|
|
69
|
+
|
|
70
|
+
Retorna:
|
|
71
|
+
str: Tipo de sistema do componente. String vazia se não encontrado ou em caso de erro.
|
|
72
|
+
"""
|
|
73
|
+
return _get_dataset_run_param("component-system", "system")
|
|
74
|
+
|
|
75
|
+
def get_current_component_name():
|
|
76
|
+
"""
|
|
77
|
+
Retorna o nome do componente corrente.
|
|
78
|
+
|
|
79
|
+
Retorna:
|
|
80
|
+
str: Nome do componente. String vazia se não encontrado ou em caso de erro.
|
|
81
|
+
"""
|
|
82
|
+
return _get_dataset_run_param("component-name", "name")
|
|
83
|
+
|
|
84
|
+
def get_scenario_reference():
|
|
85
|
+
"""
|
|
86
|
+
Retorna o texto do campo de Referência do cenário
|
|
87
|
+
|
|
88
|
+
Retorna:
|
|
89
|
+
str: Referência do cenário. String vazia se a referência não for encontrada ou ocorrer algum erro.
|
|
90
|
+
"""
|
|
91
|
+
return _get_dataset_run_param("automation-reference", "reference")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_cycle_name():
|
|
96
|
+
"""Retorna o nome do ciclo."""
|
|
97
|
+
return _get_dataset_run_param("cycle", "cycle")
|
|
98
|
+
|
|
99
|
+
def get_dataset_run_info():
|
|
100
|
+
"""Retorna informações do Dataset Run em formato JSON."""
|
|
101
|
+
return _request_json("GET", _get_dataset_run_url())
|
|
102
|
+
|
|
103
|
+
def get_dataset_run_components():
|
|
104
|
+
"""Retorna informações do Dataset Run em formato JSON."""
|
|
105
|
+
data = _request_json(
|
|
106
|
+
"GET",
|
|
107
|
+
get_api_url("dataset-run"),
|
|
108
|
+
params={
|
|
109
|
+
"componentsByDatasetRun": "true",
|
|
110
|
+
"idDatasetRun": get_id_dataset_run(),
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return data if data else get_dataset_run_info()
|
|
115
|
+
|
|
116
|
+
def get_dataset_run_component_names():
|
|
117
|
+
"""
|
|
118
|
+
Retorna uma lista com os nomes de todos os componentes do Dataset Run.
|
|
119
|
+
|
|
120
|
+
Retorna:
|
|
121
|
+
list[str]: Lista com os nomes dos componentes.
|
|
122
|
+
"""
|
|
123
|
+
dataset_run_info = get_dataset_run_components()
|
|
124
|
+
|
|
125
|
+
# Verifica se o retorno contém a chave 'datasetScenario' e 'datasetComponent'
|
|
126
|
+
components = []
|
|
127
|
+
|
|
128
|
+
if "datasetScenario" in dataset_run_info and "datasetComponent" in dataset_run_info["datasetScenario"]:
|
|
129
|
+
components = dataset_run_info["datasetScenario"]["datasetComponent"]
|
|
130
|
+
elif "dataset" in dataset_run_info and "datasetComponent" in dataset_run_info["dataset"]:
|
|
131
|
+
components = dataset_run_info["dataset"]["datasetComponent"]
|
|
132
|
+
elif "datasetComponent" in dataset_run_info:
|
|
133
|
+
components = dataset_run_info["datasetComponent"]
|
|
134
|
+
elif "components" in dataset_run_info:
|
|
135
|
+
components = dataset_run_info["components"]
|
|
136
|
+
|
|
137
|
+
if components:
|
|
138
|
+
sorted_components = sorted(components, key=lambda x: x.get("componentOrder", 0))
|
|
139
|
+
|
|
140
|
+
component_names = [
|
|
141
|
+
comp.get("component", {}).get("name", comp.get("name", ""))
|
|
142
|
+
for comp in sorted_components
|
|
143
|
+
if comp.get("component") or comp.get("name")
|
|
144
|
+
]
|
|
145
|
+
return component_names
|
|
146
|
+
|
|
147
|
+
return []
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_dataset_scenario_by_dataset_run():
|
|
152
|
+
"""Retorna o ID do Dataset Scenario a partir do ID do Dataset Run."""
|
|
153
|
+
execution_info = get_dataset_run_info()
|
|
154
|
+
return execution_info.get("idDataset", execution_info.get("idDatasetScenario", execution_info.get("Id_Dataset_Scenario", "")))
|
|
155
|
+
|
|
156
|
+
def get_dataset_scenario_info(dataset_scenario_id):
|
|
157
|
+
"""Retorna informações do dataset Scenario em formato JSON."""
|
|
158
|
+
dataset_info = _request_json("GET", f"{get_host()}/dataset/{dataset_scenario_id}")
|
|
159
|
+
if dataset_info:
|
|
160
|
+
return dataset_info
|
|
161
|
+
|
|
162
|
+
return _request_json("GET", f"{get_host()}/dataset-scenario/{dataset_scenario_id}")
|
|
163
|
+
|
|
164
|
+
def get_execution_client():
|
|
165
|
+
"""Retorna informações de onde a automação foi disparada em formato JSON."""
|
|
166
|
+
informacoes_execucao = get_dataset_run_info()
|
|
167
|
+
client_id = informacoes_execucao.get('idClient')
|
|
168
|
+
|
|
169
|
+
if not client_id:
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
return _request_json("GET", f"{get_host()}/client/{client_id}")
|
|
173
|
+
|
|
174
|
+
def get_dataset_category():
|
|
175
|
+
"""Retorna as informações da categoria atribuída ao dataset."""
|
|
176
|
+
dataset_id = get_dataset_scenario_by_dataset_run()
|
|
177
|
+
dataset_info = get_dataset_scenario_info(dataset_id) if dataset_id else {}
|
|
178
|
+
|
|
179
|
+
if dataset_info:
|
|
180
|
+
return dataset_info
|
|
181
|
+
|
|
182
|
+
execution_info = get_dataset_run_info()
|
|
183
|
+
category_id = execution_info.get("datasetScenario", {}).get("idCategory")
|
|
184
|
+
|
|
185
|
+
if not category_id:
|
|
186
|
+
return {}
|
|
187
|
+
|
|
188
|
+
return _request_json("GET", f"{get_host()}/category/{category_id}")
|
|
189
|
+
|
|
190
|
+
def get_dataset_category_name():
|
|
191
|
+
"""Retorna o nome da categoria do dataset definido no Proton."""
|
|
192
|
+
dataset_category_infos = get_dataset_category()
|
|
193
|
+
return dataset_category_infos.get("category") or dataset_category_infos.get("name", "")
|
|
194
|
+
|
|
195
|
+
def get_dataset_system():
|
|
196
|
+
"""Retorna o campo 'System' do dataset definido no Proton."""
|
|
197
|
+
dataset_scenario_id = get_dataset_scenario_by_dataset_run()
|
|
198
|
+
scenario_info = get_dataset_scenario_info(dataset_scenario_id)
|
|
199
|
+
return scenario_info.get("system", "")
|
|
200
|
+
|
|
201
|
+
def get_component_types():
|
|
202
|
+
"""
|
|
203
|
+
Retorna os tipos de componentes presentes no Dataset Run.
|
|
204
|
+
|
|
205
|
+
Retorna:
|
|
206
|
+
str: Uma string com os tipos de componentes separados por ponto e vírgula (;),
|
|
207
|
+
ou uma string vazia em caso de erro.
|
|
208
|
+
"""
|
|
209
|
+
data = _request_json("GET", _get_dataset_run_url(), params={"componentTypes": "true"})
|
|
210
|
+
|
|
211
|
+
if isinstance(data, list):
|
|
212
|
+
component_types = [item.get("Type") for item in data if item.get("Type")]
|
|
213
|
+
return ";".join(component_types)
|
|
214
|
+
|
|
215
|
+
return ""
|
|
216
|
+
|
|
217
|
+
def get_dataset_run_parameters():
|
|
218
|
+
"""Retorna os parâmetros do Dataset Run em formato JSON."""
|
|
219
|
+
return _request_json(
|
|
220
|
+
"GET",
|
|
221
|
+
get_api_url("dataset-run-parameter"),
|
|
222
|
+
params={
|
|
223
|
+
"idDatasetRun": get_id_dataset_run(),
|
|
224
|
+
"organized": "true",
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def get_mobile_app(mobile_app_id):
|
|
229
|
+
"""Retorna informações sobre a aplicação mobile em formato JSON."""
|
|
230
|
+
return _request_json("GET", get_api_url(f"mobile-app/{mobile_app_id}"))
|
|
231
|
+
|
|
232
|
+
def get_scenario_name_by_dataset_run():
|
|
233
|
+
"""
|
|
234
|
+
Retorna o nome do cenário a partir do Dataset Run ID.
|
|
235
|
+
|
|
236
|
+
Retorna:
|
|
237
|
+
str: Nome do cenário. Uma string vazia caso o nome não seja encontrado ou ocorra um erro.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
scenario_name = _get_dataset_run_param("automation-name", "name")
|
|
241
|
+
if scenario_name:
|
|
242
|
+
return scenario_name
|
|
243
|
+
|
|
244
|
+
data = _request_json("GET", _get_dataset_run_url(), params={"scenarioNameByDatasetRun": "true"})
|
|
245
|
+
return data.get("Name", "")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
class ProtonClientParam(Enum):
|
|
5
|
+
@classmethod
|
|
6
|
+
def _root_folder(cls):
|
|
7
|
+
return os.path.expanduser("~")
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def _proton_client_folder(cls):
|
|
11
|
+
return cls._root_folder() + os.sep + ".proton-client"
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def _config_folder(cls):
|
|
15
|
+
return cls._proton_client_folder() + os.sep + "config"
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def _scripts_folder(cls):
|
|
19
|
+
return cls._proton_client_folder() + os.sep + "scripts"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def _components_folder(cls):
|
|
23
|
+
return cls._proton_client_folder() + os.sep + "components"
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def _library_folder(cls):
|
|
27
|
+
return cls._proton_client_folder() + os.sep + "library"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _drivers_folder(cls):
|
|
31
|
+
return cls._proton_client_folder() + os.sep + "drivers"
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _screenshots_folder(cls):
|
|
35
|
+
return cls._proton_client_folder() + os.sep + "screenshots"
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def _video_folder(cls):
|
|
39
|
+
return cls._proton_client_folder() + os.sep + "video"
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _log_folder(cls):
|
|
43
|
+
return cls._proton_client_folder() + os.sep + "log"
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _repository_folder(cls):
|
|
47
|
+
return cls._root_folder() + os.sep + ".proton-client-repo"
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def _attachments_folder(cls):
|
|
51
|
+
return cls._proton_client_folder() + os.sep + "attachments"
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _cmd_folder(cls):
|
|
55
|
+
return cls._proton_client_folder() + os.sep + "cmd"
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _app_folder(cls):
|
|
59
|
+
return cls._proton_client_folder() + os.sep + "app"
|
proton/proton_env.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from configparser import ConfigParser
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _is_localhost(host):
|
|
6
|
+
"""Retorna True quando o host aponta para a máquina local."""
|
|
7
|
+
return any(local_address in host for local_address in ("localhost", "127.0.0.1", "::1", "[::1]"))
|
|
8
|
+
|
|
9
|
+
def get_proton_config():
|
|
10
|
+
"""
|
|
11
|
+
Lê e retorna a configuração do arquivo `proton.ini`.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
ConfigParser: Objeto ConfigParser contendo as configurações do arquivo `proton.ini`.
|
|
15
|
+
"""
|
|
16
|
+
config = ConfigParser()
|
|
17
|
+
config.read('proton.ini')
|
|
18
|
+
return config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_logged_overrides = set()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _from_file(option, section='server'):
|
|
25
|
+
"""Valor de uma chave do `proton.ini`, ou string vazia se o arquivo/chave não existir."""
|
|
26
|
+
return get_proton_config().get(section, option, fallback='').strip()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _file_precedence():
|
|
30
|
+
"""
|
|
31
|
+
`[server] config_precedence = file` faz o arquivo do projeto vencer o que o runner
|
|
32
|
+
injeta. Serve para teste apontado a outro ambiente e para projetos legados que
|
|
33
|
+
precisam continuar falando com o servidor fixado no repositório.
|
|
34
|
+
"""
|
|
35
|
+
return _from_file('config_precedence').lower() == 'file'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve(env_key, option, sensitive=False):
|
|
39
|
+
"""
|
|
40
|
+
Resolve uma configuração com a prioridade ambiente -> `proton.ini`.
|
|
41
|
+
|
|
42
|
+
O runner injeta `PROTON_HOST` e `PROTON_TOKEN` no processo da automação, para que a
|
|
43
|
+
execução reporte ao servidor de onde ela nasceu — um `proton.ini` apontando para outro
|
|
44
|
+
ambiente faz a execução reportar no lugar errado, e como o componente nunca avança lá,
|
|
45
|
+
o runner relança a automação indefinidamente.
|
|
46
|
+
|
|
47
|
+
Mesma convenção de nomes do atomic-framework: a chave `proton.host` vira `PROTON_HOST`.
|
|
48
|
+
"""
|
|
49
|
+
file_value = _from_file(option)
|
|
50
|
+
|
|
51
|
+
if _file_precedence() and file_value:
|
|
52
|
+
return file_value
|
|
53
|
+
|
|
54
|
+
runtime_value = (os.getenv(env_key) or '').strip()
|
|
55
|
+
|
|
56
|
+
if not runtime_value:
|
|
57
|
+
return file_value
|
|
58
|
+
|
|
59
|
+
if file_value and file_value != runtime_value and env_key not in _logged_overrides:
|
|
60
|
+
# Sobrescrita silenciosa vira depuração às cegas. O valor sensível nunca é
|
|
61
|
+
# impresso: o stdout da automação sobe como log da execução no Proton.
|
|
62
|
+
_logged_overrides.add(env_key)
|
|
63
|
+
if sensitive:
|
|
64
|
+
print(f'[proton] {env_key} definido em tempo de execução (valor do arquivo ignorado)')
|
|
65
|
+
else:
|
|
66
|
+
print(f'[proton] {env_key} definido em tempo de execução: {runtime_value} '
|
|
67
|
+
f'(arquivo declarava: {file_value})')
|
|
68
|
+
|
|
69
|
+
return runtime_value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_host():
|
|
73
|
+
"""
|
|
74
|
+
Obtém o host do Proton: `PROTON_HOST` do ambiente, com o `proton.ini` como fallback.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: Host da API do Proton, sempre com protocolo.
|
|
78
|
+
"""
|
|
79
|
+
host = _resolve('PROTON_HOST', 'hostname')
|
|
80
|
+
|
|
81
|
+
if not host:
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
'Host do Proton não configurado: defina PROTON_HOST no ambiente '
|
|
84
|
+
'ou a chave hostname na seção [server] do proton.ini'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if _is_localhost(host) or host.startswith('https://'):
|
|
88
|
+
return host
|
|
89
|
+
|
|
90
|
+
if host.startswith('http://'):
|
|
91
|
+
return f"https://{host[len('http://'):] }"
|
|
92
|
+
|
|
93
|
+
return f"https://{host}"
|
|
94
|
+
|
|
95
|
+
def get_token():
|
|
96
|
+
"""
|
|
97
|
+
Obtém o token do Proton: `PROTON_TOKEN` do ambiente, com o `proton.ini` como fallback.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
str: Token de acesso, ou string vazia quando não há nenhum configurado.
|
|
101
|
+
"""
|
|
102
|
+
return _resolve('PROTON_TOKEN', 'token', sensitive=True)
|
|
103
|
+
|
|
104
|
+
def is_record_video():
|
|
105
|
+
"""
|
|
106
|
+
Verifica se a gravação de vídeo está habilitada para os relatórios.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
bool: `True` se `video_record` estiver configurado como 'true' na seção `[reports]` do arquivo `proton.ini`, caso contrário `False`.
|
|
110
|
+
"""
|
|
111
|
+
config = get_proton_config()
|
|
112
|
+
video_record = config.get('reports', 'video_record', fallback='false').lower()
|
|
113
|
+
return video_record == 'true'
|
|
114
|
+
|
|
115
|
+
def get_api_url(path):
|
|
116
|
+
"""
|
|
117
|
+
Monta a URL completa de um recurso da API a partir do hostname do `proton.ini`.
|
|
118
|
+
|
|
119
|
+
Não usa `urllib.parse.urljoin`: com um caminho iniciado por '/', o urljoin descarta o
|
|
120
|
+
prefixo '/api' do hostname e a requisição cai na página do frontend (HTML, status 200).
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
path (str): Caminho do recurso, com ou sem a barra inicial (ex.: 'dataset-run/10').
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
str: URL absoluta do recurso.
|
|
127
|
+
"""
|
|
128
|
+
return f"{get_host().rstrip('/')}/{str(path).lstrip('/')}"
|
|
129
|
+
|
|
130
|
+
def get_auth_header():
|
|
131
|
+
"""
|
|
132
|
+
Gera o cabeçalho de autenticação para requisições HTTP, baseado no token do Proton.
|
|
133
|
+
|
|
134
|
+
Aceita o token do `proton.ini` com ou sem o prefixo 'Bearer ' e nunca o duplica:
|
|
135
|
+
'Bearer Bearer <token>' é recusado pelo Proton 5 com 401.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
dict: Dicionário com o cabeçalho de autenticação no formato {'Authorization': 'Bearer token'}.
|
|
139
|
+
"""
|
|
140
|
+
token = (get_token() or '').strip()
|
|
141
|
+
|
|
142
|
+
if not token:
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
if token.lower().startswith('bearer '):
|
|
146
|
+
token = token[len('bearer '):].strip()
|
|
147
|
+
|
|
148
|
+
return {'Authorization': f'Bearer {token}'}
|
|
149
|
+
|
|
150
|
+
def get_id_dataset_run():
|
|
151
|
+
"""
|
|
152
|
+
Obtém o ID do Dataset Run, que representa o número de execução do cenário no Proton.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
str: Valor do ID Dataset Run configurado nas variáveis de ambiente.
|
|
156
|
+
Retorna None se a variável `idDatasetRun` não estiver definida.
|
|
157
|
+
"""
|
|
158
|
+
return os.getenv('idDatasetRun')
|
|
159
|
+
|
|
160
|
+
def get_idRunProgress():
|
|
161
|
+
"""
|
|
162
|
+
Obtém o ID do progresso de execução (Run Progress) no Proton.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
str: Valor do ID Run Progress configurado nas variáveis de ambiente.
|
|
166
|
+
Retorna None se a variável `Id_Run_Progress` não estiver definida.
|
|
167
|
+
"""
|
|
168
|
+
return os.getenv('idRunProgress')
|
|
169
|
+
|
|
170
|
+
def is_proton_execution():
|
|
171
|
+
"""
|
|
172
|
+
Verifica se a execução está sendo realizada no Proton.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
bool: Retorna True se a variável de ambiente `idDatasetRun` estiver definida,
|
|
176
|
+
indicando execução no Proton. Caso contrário, retorna False.
|
|
177
|
+
"""
|
|
178
|
+
return os.getenv('idDatasetRun') is not None
|
|
179
|
+
|
|
180
|
+
def set_id_dataset_run(id_dataset_run):
|
|
181
|
+
"""
|
|
182
|
+
Define o valor do ID Dataset Run (ID de execução do cenário) como uma variável de ambiente.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
id_dataset_run (str): Valor do ID Dataset Run a ser configurado.
|
|
186
|
+
"""
|
|
187
|
+
os.environ["idDatasetRun"] = id_dataset_run
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from proton.proton_client_parama import ProtonClientParam
|
|
6
|
+
from proton.proton_env import get_api_url, get_auth_header, get_host, get_id_dataset_run, get_idRunProgress, is_proton_execution
|
|
7
|
+
from proton.proton_logs import set_log
|
|
8
|
+
|
|
9
|
+
BUFFER_SIZE = 102400
|
|
10
|
+
ENTITY_RUN_PROGRESS = "run_progress"
|
|
11
|
+
ENTITY_DATASET_RUN_RESOURCE = "dataset_run_resource"
|
|
12
|
+
|
|
13
|
+
def upload_file_resource(file_resource):
|
|
14
|
+
"""Realiza o upload de um arquivo como recurso no Proton."""
|
|
15
|
+
try:
|
|
16
|
+
file_name = os.path.basename(file_resource)
|
|
17
|
+
file_path = os.path.dirname(file_resource)
|
|
18
|
+
json_response = insert_proton_file(ENTITY_DATASET_RUN_RESOURCE, get_id_dataset_run(), file_path, file_name)
|
|
19
|
+
return json_response
|
|
20
|
+
except Exception as e:
|
|
21
|
+
set_log(f"Erro ao fazer upload do arquivo: {e}")
|
|
22
|
+
return {} # Retorna um dicionário vazio em caso de falha
|
|
23
|
+
|
|
24
|
+
def upload_image(full_image_path):
|
|
25
|
+
"""Realiza upload de uma imagem para o Proton."""
|
|
26
|
+
try:
|
|
27
|
+
image_name = os.path.basename(full_image_path)
|
|
28
|
+
image_path = os.path.dirname(full_image_path)
|
|
29
|
+
return insert_proton_file(ENTITY_RUN_PROGRESS, get_idRunProgress(), image_path, image_name)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
set_log(f"Erro ao fazer upload da imagem: {e}")
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def insert_proton_file(entity, entity_id, file_path, file_name):
|
|
35
|
+
"""Lógica central de upload de arquivos. Requer uma implementação da função de upload."""
|
|
36
|
+
if is_proton_execution():
|
|
37
|
+
url = get_host() + f"/file-system/upload?entity={entity}&idEntity={entity_id}"
|
|
38
|
+
return file_upload(url, file_path, file_name)
|
|
39
|
+
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def file_upload(url_path, file_path, file_name):
|
|
44
|
+
"""Realiza upload de arquivos, retornando a resposta JSON do Proton.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
url_path (str): A URL para onde o arquivo será enviado.
|
|
48
|
+
file_path (str): O caminho da pasta contendo o arquivo.
|
|
49
|
+
file_name (str): O nome do arquivo.
|
|
50
|
+
|
|
51
|
+
Retorna:
|
|
52
|
+
dict: Dicionário Python contendo a resposta JSON do Proton, ou um dicionário vazio
|
|
53
|
+
caso ocorra algum erro.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
with open(os.path.normpath(os.path.join(file_path, file_name)), 'rb') as file_data:
|
|
57
|
+
files = {
|
|
58
|
+
"file": (file_name, file_data)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
response = requests.post(url_path, headers=get_auth_header(), files=files)
|
|
62
|
+
|
|
63
|
+
if response.status_code != 200:
|
|
64
|
+
set_log(f"Erro ao fazer upload do arquivo. Código de status: {response.status_code}")
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
return response.json() if response.content else {}
|
|
68
|
+
|
|
69
|
+
except ValueError:
|
|
70
|
+
set_log(f"Erro ao decodificar a resposta do upload do arquivo: {file_name}")
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
set_log(f"Erro durante o upload do arquivo: {e}")
|
|
74
|
+
|
|
75
|
+
return {}
|
|
76
|
+
|
|
77
|
+
def get_mobile_app(mobile_app_id):
|
|
78
|
+
"""Retorna informações sobre a aplicação mobile em formato JSON."""
|
|
79
|
+
url = get_api_url(f"mobile-app/{mobile_app_id}")
|
|
80
|
+
header = get_auth_header()
|
|
81
|
+
|
|
82
|
+
response = requests.get(url, headers=header)
|
|
83
|
+
|
|
84
|
+
if response.status_code == 200:
|
|
85
|
+
return response.json()
|
|
86
|
+
else:
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
def get_signed_url_file(entity, entity_id):
|
|
90
|
+
"""Retorna a URL assinada para um arquivo."""
|
|
91
|
+
url = get_api_url("file-system")
|
|
92
|
+
header = get_auth_header()
|
|
93
|
+
|
|
94
|
+
params = {
|
|
95
|
+
"entity": entity,
|
|
96
|
+
"idEntity": entity_id
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
response = requests.get(url, params=params, headers=header)
|
|
100
|
+
|
|
101
|
+
if response.status_code == 200:
|
|
102
|
+
return response.json()
|
|
103
|
+
else:
|
|
104
|
+
return {}
|
|
105
|
+
|
|
106
|
+
def download_mobile_app(entity_id):
|
|
107
|
+
"""Baixa uma aplicação mobile e retorna o caminho para o arquivo."""
|
|
108
|
+
json_file = get_signed_url_file("Mobile_App", entity_id)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
file_name = json_file.get("fileName", "")
|
|
112
|
+
app_file_path = ProtonClientParam._app_folder() + os.sep
|
|
113
|
+
|
|
114
|
+
os.makedirs(app_file_path, exist_ok=True) # Cria a pasta se não existir
|
|
115
|
+
execute_download_file(json_file.get("url", ""), file_name, app_file_path)
|
|
116
|
+
return os.path.join(app_file_path, file_name)
|
|
117
|
+
|
|
118
|
+
except KeyError as e:
|
|
119
|
+
print(f"Erro ao obter informações de download: {e}")
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
def execute_download_file(file_url, file_name, save_dir):
|
|
123
|
+
"""Realiza o download de um arquivo."""
|
|
124
|
+
try:
|
|
125
|
+
response = requests.get(file_url, stream=True)
|
|
126
|
+
response.raise_for_status() # Lança exceção em caso de código de erro
|
|
127
|
+
|
|
128
|
+
save_file_path = os.path.join(save_dir, file_name)
|
|
129
|
+
with open(save_file_path, 'wb') as f:
|
|
130
|
+
for chunk in response.iter_content(chunk_size=BUFFER_SIZE):
|
|
131
|
+
f.write(chunk)
|
|
132
|
+
|
|
133
|
+
set_log("Arquivo " + file_name + " baixado!")
|
|
134
|
+
|
|
135
|
+
except requests.exceptions.RequestException as e:
|
|
136
|
+
set_log(f"Erro ao baixar arquivo: {e}")
|
|
137
|
+
|
|
138
|
+
def get_component_file_id(component_id):
|
|
139
|
+
"""Retorna informações sobre o arquivo anexado no componente."""
|
|
140
|
+
url = get_api_url("file")
|
|
141
|
+
header = get_auth_header()
|
|
142
|
+
|
|
143
|
+
params = {
|
|
144
|
+
"componentFile": "true",
|
|
145
|
+
"Id_Component": component_id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
response = requests.get(url, params=params, headers=header)
|
|
149
|
+
|
|
150
|
+
if response.status_code == 200:
|
|
151
|
+
return response.text # Retornando texto diretamente (assumindo que o formato se mantém)
|
|
152
|
+
else:
|
|
153
|
+
return ""
|
|
154
|
+
|
|
155
|
+
def get_test_script_scenario_file_id(dataset_run_id):
|
|
156
|
+
"""Retorna informações do arquivo de script de teste associado a um dataset run."""
|
|
157
|
+
url = get_api_url("file")
|
|
158
|
+
header = get_auth_header()
|
|
159
|
+
|
|
160
|
+
params = {
|
|
161
|
+
"testScriptScenarioFileByDatasetRun": "true",
|
|
162
|
+
"idDatasetRun": dataset_run_id
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
response = requests.get(url, params=params, headers=header)
|
|
166
|
+
|
|
167
|
+
if response.status_code == 200:
|
|
168
|
+
return response.text
|
|
169
|
+
else:
|
|
170
|
+
return ""
|
proton/proton_git.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from proton.proton_client_parama import ProtonClientParam
|
|
7
|
+
from proton.proton_env import get_api_url, get_auth_header
|
|
8
|
+
from proton.proton_logs import set_log
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_remote_repository(system_type):
|
|
12
|
+
"""Obtém informações sobre um repositório remoto."""
|
|
13
|
+
url = get_api_url("git-repository")
|
|
14
|
+
header = get_auth_header()
|
|
15
|
+
|
|
16
|
+
params = {
|
|
17
|
+
"selectRemoteRepository": "true",
|
|
18
|
+
"System": system_type
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
response = requests.get(url, params=params, headers=header)
|
|
22
|
+
|
|
23
|
+
if response.status_code == 200:
|
|
24
|
+
return response.json()
|
|
25
|
+
else:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
def get_remote_repository_legacy(system_type):
|
|
29
|
+
"""Obtém informações sobre um repositório remoto (versão legacy)."""
|
|
30
|
+
url = get_api_url("git-repository")
|
|
31
|
+
header = get_auth_header()
|
|
32
|
+
|
|
33
|
+
params = {
|
|
34
|
+
"selectRemoteRepository": "true",
|
|
35
|
+
"Type": system_type
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
response = requests.get(url, params=params, headers=header)
|
|
39
|
+
|
|
40
|
+
if response.status_code == 200:
|
|
41
|
+
return response.json()
|
|
42
|
+
else:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def get_remote_header_hash(remote_authorization, repository, branch):
|
|
46
|
+
"""Obtém o hash do cabeçalho de um repositório remoto."""
|
|
47
|
+
url = get_api_url("git-repository")
|
|
48
|
+
header = get_auth_header()
|
|
49
|
+
|
|
50
|
+
params = {
|
|
51
|
+
"selectRemoteHeaderHash": "true",
|
|
52
|
+
"Remote_Authorization": remote_authorization,
|
|
53
|
+
"Repository": repository,
|
|
54
|
+
"Branch": branch
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
response = requests.get(url, params=params, headers=header)
|
|
58
|
+
|
|
59
|
+
if response.status_code == 200:
|
|
60
|
+
data = response.json()
|
|
61
|
+
return data.get("sha", "")
|
|
62
|
+
else:
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
def clone_repository(owner_repo, branch, commit_hash, authorization):
|
|
66
|
+
"""Clona um repositório Git."""
|
|
67
|
+
repo_folder = os.path.join(ProtonClientParam._repository_folder(), owner_repo, branch, commit_hash)
|
|
68
|
+
os.makedirs(repo_folder, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
def run_git_clone(repo_folder, branch, authorization, owner_repo):
|
|
71
|
+
try:
|
|
72
|
+
url = f"https://{authorization}@github.com/{owner_repo}.git"
|
|
73
|
+
subprocess.run(
|
|
74
|
+
["git", "clone", "--branch", branch, url],
|
|
75
|
+
cwd=repo_folder,
|
|
76
|
+
check=True, # Levanta exceção se o git retornar código de erro
|
|
77
|
+
capture_output=True, # Captura o output
|
|
78
|
+
text=True
|
|
79
|
+
)
|
|
80
|
+
except subprocess.CalledProcessError as e:
|
|
81
|
+
set_log(f"Erro ao clonar: {e.output}")
|
|
82
|
+
|
|
83
|
+
thread = threading.Thread(target=run_git_clone, args=(repo_folder, branch, authorization, owner_repo))
|
|
84
|
+
thread.start()
|
|
85
|
+
|
|
86
|
+
def get_local_repository_path(owner_repo, repository, branch, commit_hash):
|
|
87
|
+
"""Retorna o caminho para um repositório local."""
|
|
88
|
+
repo_folder = os.path.join(ProtonClientParam._repository_folder(), owner_repo, branch, commit_hash, repository)
|
|
89
|
+
return repo_folder
|
proton/proton_logs.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from proton.proton_env import get_auth_header, get_host, get_id_dataset_run, is_proton_execution
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_log(log_message, disable_date_time=False):
|
|
9
|
+
"""Envia uma entrada de log"""
|
|
10
|
+
|
|
11
|
+
if is_proton_execution():
|
|
12
|
+
dataset_run_id = get_id_dataset_run()
|
|
13
|
+
|
|
14
|
+
if dataset_run_id is not None:
|
|
15
|
+
url = f"{get_host()}/dataset-run-log/{dataset_run_id}"
|
|
16
|
+
|
|
17
|
+
body = {
|
|
18
|
+
"idDatasetRun": dataset_run_id,
|
|
19
|
+
"log": log_message,
|
|
20
|
+
"disableDateTime": disable_date_time,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return requests.put(url, json=body, headers=get_auth_header())
|
|
24
|
+
else:
|
|
25
|
+
print(log_message)
|
|
26
|
+
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def set_error_log(error_message):
|
|
31
|
+
"""Envia um log de erro estruturado para o Proton."""
|
|
32
|
+
if is_proton_execution():
|
|
33
|
+
dataset_run_id = get_id_dataset_run()
|
|
34
|
+
|
|
35
|
+
if dataset_run_id is not None:
|
|
36
|
+
url = f"{get_host()}/dataset-run/{dataset_run_id}/error"
|
|
37
|
+
body = {
|
|
38
|
+
"idDatasetRun": dataset_run_id,
|
|
39
|
+
"error": error_message,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return requests.put(url, json=body, headers=get_auth_header())
|
|
43
|
+
else:
|
|
44
|
+
print(error_message)
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# ATENÇÃO -----------------
|
|
49
|
+
def set_log_from_exception(e):
|
|
50
|
+
"""Envia um log de exceção formatando o stack trace"""
|
|
51
|
+
stack_trace = "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
52
|
+
for line in stack_trace.splitlines():
|
|
53
|
+
set_log(line)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from proton.proton_env import get_auth_header, get_host, get_id_dataset_run, is_proton_execution
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _get_dataset_run_url():
|
|
6
|
+
return f"{get_host()}/dataset-run/{get_id_dataset_run()}"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_proton_value(param, value):
|
|
10
|
+
"""
|
|
11
|
+
Insere um valor de output (saída) no Proton.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
param (str): O nome do parâmetro, deve ser idêntico ao nome definido no Proton.
|
|
15
|
+
value (str ou int): O valor a ser inserido no parâmetro de output.
|
|
16
|
+
"""
|
|
17
|
+
if is_proton_execution():
|
|
18
|
+
params = {
|
|
19
|
+
"parameter": param
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
params["value"] = str(value)
|
|
23
|
+
|
|
24
|
+
# Proton 5: a rota e "/set-value"; "/setValue" (Proton 4) responde 404 e o valor nunca era gravado.
|
|
25
|
+
response = requests.put(f"{_get_dataset_run_url()}/set-value", params=params, headers=get_auth_header())
|
|
26
|
+
|
|
27
|
+
if response.status_code != 200:
|
|
28
|
+
print(f"[proton] set_proton_value({param!r}) -> HTTP {response.status_code}: {response.text[:300]}")
|
|
29
|
+
|
|
30
|
+
return response.status_code
|
|
31
|
+
|
|
32
|
+
def get_proton_value(param):
|
|
33
|
+
"""Busca valor de parâmetro do Proton"""
|
|
34
|
+
if not is_proton_execution():
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
params = {
|
|
38
|
+
"parameter": param
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
response = requests.get(f"{_get_dataset_run_url()}/get-value", params=params, headers=get_auth_header())
|
|
42
|
+
|
|
43
|
+
if response.status_code == 200:
|
|
44
|
+
data = response.json()
|
|
45
|
+
|
|
46
|
+
return data.get("value", "") if data.get("value", "") else ''
|
|
47
|
+
else:
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
def get_proton_all_scenario_parameters():
|
|
51
|
+
"""
|
|
52
|
+
Retorna todos os parâmetros de cenário do Proton.
|
|
53
|
+
|
|
54
|
+
Retorna:
|
|
55
|
+
dict: Um dicionário contendo todos os parâmetros de cenário.
|
|
56
|
+
"""
|
|
57
|
+
host = get_host()
|
|
58
|
+
dataset_run_id = get_id_dataset_run()
|
|
59
|
+
header = get_auth_header()
|
|
60
|
+
|
|
61
|
+
url = f"{host}/dataset-run/get-all-params/{dataset_run_id}"
|
|
62
|
+
|
|
63
|
+
response = requests.get(url, headers=header)
|
|
64
|
+
|
|
65
|
+
if response.status_code == 200:
|
|
66
|
+
return response.json() # Retorna os parâmetros de cenário em formato JSON
|
|
67
|
+
else:
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
def get_proton_all_component_parameters():
|
|
71
|
+
"""
|
|
72
|
+
Retorna todos os parâmetros de componente do Proton.
|
|
73
|
+
|
|
74
|
+
Retorna:
|
|
75
|
+
dict: Um dicionário contendo todos os parâmetros de componente.
|
|
76
|
+
"""
|
|
77
|
+
host = get_host()
|
|
78
|
+
dataset_run_id = get_id_dataset_run()
|
|
79
|
+
header = get_auth_header()
|
|
80
|
+
|
|
81
|
+
url = f"{host}/dataset-run/get-component-params/{dataset_run_id}"
|
|
82
|
+
|
|
83
|
+
response = requests.get(url, headers=header)
|
|
84
|
+
|
|
85
|
+
if response.status_code == 200:
|
|
86
|
+
return response.json() # Retorna os parâmetros de componente em formato JSON
|
|
87
|
+
else:
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
def get_proton_output_parameter_list(output_parameter_name=None):
|
|
91
|
+
"""
|
|
92
|
+
Retorna as ocorrências de um parâmetro de output (saída) específico ou
|
|
93
|
+
de todos os parâmetros de output presentes na ID do Dataset Run.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
output_parameter_name (str, opcional): Nome do parâmetro de output a ser filtrado.
|
|
97
|
+
Se não for fornecido, retorna todos os parâmetros.
|
|
98
|
+
|
|
99
|
+
Retorna:
|
|
100
|
+
list: Uma lista com as ocorrências dos parâmetros de output no formato JSON.
|
|
101
|
+
Caso não encontre parâmetros ou ocorra algum erro, retorna uma lista vazia.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
host = get_host() # Lembre-se de definir a lógica para obter o 'host'
|
|
105
|
+
url = f"{host}/results-run"
|
|
106
|
+
header = get_auth_header()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
params = {
|
|
110
|
+
"idDatasetRun": get_id_dataset_run()
|
|
111
|
+
}
|
|
112
|
+
if output_parameter_name:
|
|
113
|
+
params["parameter"] = output_parameter_name
|
|
114
|
+
|
|
115
|
+
response = requests.get(url, params=params, headers=header)
|
|
116
|
+
|
|
117
|
+
if response.status_code == 200:
|
|
118
|
+
return response.json() # Assumindo que a resposta é em formato JSON
|
|
119
|
+
else:
|
|
120
|
+
return [] # Indica que não foram encontrados outputs ou houve erro
|
|
121
|
+
|
|
122
|
+
# Versão sobrecarregada do método, chamando a função principal com output_parameter_name=""
|
|
123
|
+
def get_proton_output_parameter_list_all():
|
|
124
|
+
"""
|
|
125
|
+
Retorna as ocorrências de todos os parâmetros de output presentes na ID do Dataset Run.
|
|
126
|
+
"""
|
|
127
|
+
return get_proton_output_parameter_list()
|
proton/run_status.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: protoncloud-sdk
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: Cliente Python da plataforma Proton: execução de automações, logs, parâmetros e recursos.
|
|
5
|
+
Project-URL: Homepage, https://protoncloud.com.br
|
|
6
|
+
Author: Atomic Solutions
|
|
7
|
+
License: Proprietary
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: automation,proton,qa,rpa,testing
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: Other/Proprietary License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: requests>=2.31
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Proton Lib (Python)
|
|
23
|
+
|
|
24
|
+
Cliente Python da plataforma [Proton](https://protoncloud.com.br). A automação usa
|
|
25
|
+
esta biblioteca para reportar ao Proton o progresso, o status, os logs, os
|
|
26
|
+
parâmetros e os recursos de cada execução.
|
|
27
|
+
|
|
28
|
+
Distribuição: `protoncloud-sdk`. Import: `proton`.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from proton.proton_automation import start_component, end_component, update_run_status
|
|
32
|
+
from proton.run_status import RunStatus
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Instalação
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install protoncloud-sdk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
ou, com [uv](https://docs.astral.sh/uv/):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv add protoncloud-sdk
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuração
|
|
48
|
+
|
|
49
|
+
Host e token são resolvidos nesta ordem — a mesma convenção das bibliotecas Java
|
|
50
|
+
do Proton:
|
|
51
|
+
|
|
52
|
+
1. **Variável de ambiente** — `PROTON_HOST`, `PROTON_TOKEN`
|
|
53
|
+
2. **`proton.ini`** no diretório de execução
|
|
54
|
+
|
|
55
|
+
Quando a automação roda pelo Proton Runner, ele injeta `PROTON_HOST` e
|
|
56
|
+
`PROTON_TOKEN` no processo, apontando para o servidor de onde a execução nasceu.
|
|
57
|
+
É isso que permite o mesmo projeto rodar em qualquer ambiente sem editar arquivo
|
|
58
|
+
— e sem guardar token no repositório.
|
|
59
|
+
|
|
60
|
+
O arquivo continua válido e é a única fonte quando se roda a automação fora do
|
|
61
|
+
runner:
|
|
62
|
+
|
|
63
|
+
```ini
|
|
64
|
+
[server]
|
|
65
|
+
hostname = https://app.protoncloud.com.br/api
|
|
66
|
+
token = <token>
|
|
67
|
+
|
|
68
|
+
[reports]
|
|
69
|
+
video_record = false
|
|
70
|
+
video_upload = true
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
O token pode vir com ou sem o prefixo `Bearer `; a biblioteca normaliza e nunca
|
|
74
|
+
o duplica.
|
|
75
|
+
|
|
76
|
+
Não versione o `proton.ini` com o token preenchido: prefira deixá-lo fora do
|
|
77
|
+
controle de versão e deixar o runner fornecer as credenciais.
|
|
78
|
+
|
|
79
|
+
### Fixando o ambiente do projeto
|
|
80
|
+
|
|
81
|
+
Para um teste apontado a outro servidor, ou um projeto que precise ignorar o que
|
|
82
|
+
o runner injeta:
|
|
83
|
+
|
|
84
|
+
```ini
|
|
85
|
+
[server]
|
|
86
|
+
config_precedence = file
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Sem isso, quando o valor injetado difere do arquivo, a biblioteca imprime uma
|
|
90
|
+
linha dizendo qual venceu. O valor do token nunca é impresso, porque a saída da
|
|
91
|
+
automação sobe como log da execução no Proton.
|
|
92
|
+
|
|
93
|
+
## Contexto da execução
|
|
94
|
+
|
|
95
|
+
| variável | origem | uso |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `idDatasetRun` | runner | identifica a execução; sem ela a biblioteca fica inerte (`is_proton_execution()` é `False`) |
|
|
98
|
+
| `PROTON_HOST` | runner ou `proton.ini` | base da API |
|
|
99
|
+
| `PROTON_TOKEN` | runner ou `proton.ini` | autenticação |
|
|
100
|
+
| `idRunProgress` | definida pela própria biblioteca em `start_component()` | componente em andamento |
|
|
101
|
+
|
|
102
|
+
Fora de uma execução do Proton, `is_proton_execution()` é `False` e as chamadas
|
|
103
|
+
viram no-op — o mesmo teste roda localmente sem falar com o servidor.
|
|
104
|
+
|
|
105
|
+
## Migrando de uma cópia local da pasta `proton/`
|
|
106
|
+
|
|
107
|
+
Projetos que carregam esta biblioteca como uma pasta `proton/` copiada migram em
|
|
108
|
+
dois passos:
|
|
109
|
+
|
|
110
|
+
1. adicione `protoncloud-sdk` às dependências;
|
|
111
|
+
2. **apague a pasta `proton/` do projeto.**
|
|
112
|
+
|
|
113
|
+
O segundo passo não é opcional: a pasta local tem precedência sobre o pacote
|
|
114
|
+
instalado, e enquanto ela existir o projeto continua executando a cópia antiga.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
proton/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
proton/proton_automation.py,sha256=7QFahZuiEvqP_8054yE5Yb5ndwqY0xDSAVv6zlhhrZI,8366
|
|
3
|
+
proton/proton_client_parama.py,sha256=4PDY3eUl9a_BfjACvAskmT5RU1D4fKhppN57Md3qS1E,1605
|
|
4
|
+
proton/proton_env.py,sha256=0HyTV_Hi9Dp-I6w0EAwLyBg7FSVf_HOzzoDkPETu8SI,6296
|
|
5
|
+
proton/proton_files_and_resources.py,sha256=sZnjLQ0UEh20ggCONS23xBMUq9xxkAMNVgRB8D3CtfI,5802
|
|
6
|
+
proton/proton_git.py,sha256=RZr2iRmySlF4HIE450GFphwxlBctrLeD_eNgNf01H1E,2904
|
|
7
|
+
proton/proton_logs.py,sha256=sbvjTyebUQYJ6OTQckjDDfbLtvLlAPoMEj9l3-zH2Tw,1511
|
|
8
|
+
proton/proton_parameter.py,sha256=I3RflQBM4njYkEt38PHDR8h3JJz_s8nYvSbfJM4lLvo,4113
|
|
9
|
+
proton/run_status.py,sha256=m5lD3a0nVOvmMvoxxb6Ch04Pno370J2xGuvg0fx6XNk,223
|
|
10
|
+
protoncloud_sdk-5.0.0.dist-info/METADATA,sha256=mfVlD5Nckz8EXEWqCX3iwXo3Onr81SSVi3q7wfn4tDU,3721
|
|
11
|
+
protoncloud_sdk-5.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
12
|
+
protoncloud_sdk-5.0.0.dist-info/licenses/LICENSE,sha256=FQ6Qm5N4ElW76McTRb-CEyjUAHvWkKOYllXmix3SgDw,530
|
|
13
|
+
protoncloud_sdk-5.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (c) Atomic Solutions. Todos os direitos reservados.
|
|
2
|
+
|
|
3
|
+
Este software é proprietário. O uso é permitido exclusivamente em conjunto com
|
|
4
|
+
a plataforma Proton, por licenciados da Atomic Solutions e nos termos do
|
|
5
|
+
contrato firmado com a empresa.
|
|
6
|
+
|
|
7
|
+
Não é concedida permissão para redistribuir, sublicenciar, vender ou criar
|
|
8
|
+
trabalhos derivados a partir deste software sem autorização prévia por escrito
|
|
9
|
+
da Atomic Solutions.
|
|
10
|
+
|
|
11
|
+
O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE QUALQUER NATUREZA, EXPRESSA
|
|
12
|
+
OU IMPLÍCITA.
|