Skip to main content
Proton v5

Proton Cloud SDK for Python

The protoncloud-sdk package on PyPI, for Python 3.10 or later. The import name is proton.

Installation​

pip install protoncloud-sdk

Or, with uv:

uv add protoncloud-sdk

Configuration​

The address and the token are resolved in this order:

  1. Environment variable: PROTON_HOST and PROTON_TOKEN.
  2. proton.ini in the working directory.

Through the runner, PROTON_HOST and the run id (idDatasetRun) arrive ready to use. The updated runner also delivers the run token in PROTON_TOKEN, and SDK 5.2 uses it even if proton.ini has a different token. As long as the file still has a token, the SDK warns once in the log.

With an older runner, the token comes from the PROTON_TOKEN variable on the runner machine or from proton.ini, which also lets you run outside the runner, on the developer's machine:

[server]
hostname = https://app.protoncloud.com.br/api
token = <token>

The token can come with or without the Bearer prefix. Keep the proton.ini that holds the token out of version control.

To point the project to another server and ignore what the runner injects, use config_precedence = file in the [server] section. It applies to the address and to the personal token, but not to the run token. Without it, when the injected value differs from the file, the SDK prints a line saying which one won (the token is never printed).

The lifecycle of a run​

The runner calls uv run pytest tests/test_proton_script.py::test_run_proton_execution --id_dataset_run=<id>. Since 5.3, this test only hands the component executor the function that finds the component by name:

# tests/test_proton_script.py: the only file in the project that imports the SDK
import os

from proton.component_runner import run_components

from automacao.apoio.passos import localizar # component name -> executar(parametros) function


def test_run_proton_execution(request):
os.environ["idDatasetRun"] = request.config.getoption("id_dataset_run")
run_components(localizar)

localizar receives the component name and returns the function that runs it, or None when the component doesn't belong to the project. A dict from name to function also works. The --id_dataset_run option is registered in conftest.py (parser.addoption).

The component receives the step parameters, already resolved by Proton (variable, output reference, value typed when the run was triggered, encrypted), and returns the outputs:

# automacao/componentes/criar_pedido.py
def executar(parametros: dict) -> dict:
numero = TelaDoPedido().criar(parametros["in_cliente"])
return {"out_numero_pedido": numero}

To fail, the component raises an exception. The executor records the stack trace in the error panel and marks the run as Failed, with the failure reason. Since 5.5.0, the panel comes without the pytest lines and without the terminal colors.

Whatever the component records through logging (INFO level and above) goes live to the run log, with its level: warning, error and critical become Warning, Error and Critical.

The run_components options are:

OptionWhat it does
evidence_dirEvidence folder; the default is evidencias, and None turns uploading off
forward_logsWith False, the project's logging doesn't go to the run log
max_stepsStep limit
close_at_endResources the executor closes after the last step (5.5.0)

Outside a Proton run, run_components raises ProtonRunError instead of calling the server. is_proton_execution() (from proton.proton_env) tells whether the process is running in a Proton run; since 5.5.0, it requires a non-empty idDatasetRun.

Evidence folder​

At each step, the executor uploads whatever appeared in the evidence folder since the last upload: images go to the step, everything else to the run. Whatever the project saves between two steps is uploaded with the next step.

The runner can handle several runs at the same time, in the same project folder. Since 5.5.0, each run saves to its own folder, run_evidence_dir(): evidencias/<run id>, or evidencias outside Proton. The function returns the path and doesn't create the folder:

from proton.component_runner import run_evidence_dir

pasta = run_evidence_dir()
pasta.mkdir(parents=True, exist_ok=True)
navegador.save_screenshot(str(pasta / "pedido.png"))

In the default folder, the executor looks at all of evidencias except the subfolders of other runs: what the run saves in its own folder is uploaded only for that run, and what the project saves directly in evidencias is still uploaded, as before.

Close at the end​

The video and the trace of a session are only ready when the session closes, after the last step. With close_at_end (5.5.0), the executor closes the resources, in the given order and only once, and uploads whatever appeared in the evidence folder before recording the status. You can pass objects with close() or functions with no arguments, such as driver.quit. This way, the video of a run that passed also reaches Proton:

from proton.component_runner import run_components

navegador = abrir_navegador()
run_components(localizar, close_at_end=[navegador])

The executor also closes them when a step fails (before Failed) and when the next step belongs to another project. An error while closing doesn't change the result: if everything passed, it becomes a warning in the log; if a step failed, it becomes a note in that step's error.

Failure before the first step​

A failure that happens before the executor starts, such as a session that doesn't open or a device that is offline, reaches Proton through record_failure (5.5.0): the error goes to the error panel and Failed is recorded with the error's reason, or with the reason you give.

from proton.component_runner import record_failure
from proton.run_status import FailureReason

try:
navegador = abrir_navegador()
except Exception as erro:
record_failure(erro, FailureReason.ENVIRONMENT)
raise

Outside Proton, or when the run has already finished, record_failure records nothing.

Without the executor​

Projects older than 5.3 go through the steps with the SDK functions. A simplified example:

from proton.proton_automation import (
end_component,
get_current_component_name,
start_component,
update_run_status,
)
from proton.proton_logs import set_error_log, set_log_from_exception
from proton.run_status import RunStatus

from components import COMPONENTES # component name -> function with its code


def test_run_proton_execution(id_dataset_run):
update_run_status(RunStatus.RUNNING)

try:
while nome := get_current_component_name():
start_component() # opens the step and reads the run environment
COMPONENTES[nome]()
end_component() # closes the step as Passed

update_run_status(RunStatus.PASSED)
except Exception as erro:
set_log_from_exception(erro)
set_error_log(str(erro))
update_run_status(RunStatus.FAILED)
raise

The id_dataset_run fixture reads the --id_dataset_run option and calls set_id_dataset_run() (from proton.proton_env). Without the option, the SDK stays inactive and the test runs locally.

Inside a component, without the executor​

from proton.proton_environment import get_environment_value
from proton.proton_files_and_resources import upload_image
from proton.proton_logs import set_log
from proton.proton_parameter import get_proton_value, set_proton_value


def criar_pedido():
cliente = get_proton_value("CLIENTE") # dataset parameter
url = get_environment_value("SISTEMA_URL") # run environment variable

set_log(f"Creating order for {cliente}")
# ... automation ...
upload_image("evidencias/pedido.png") # step evidence
set_proton_value("NUMERO_PEDIDO", "4500012345") # output parameter

Run log​

Since 5.4.0, each log line has a level: Info (info, the default), Warning (warning), Error (error) or Critical (critical). The run screen filters by level and highlights warnings and errors, and log search finds the line across runs.

from proton.proton_logs import log_error, log_info, log_warning, set_log

log_info("Opening transaction BP")
log_warning("Record already exists: moving on to the next one", context={"cnpj": cnpj})
log_error("Timeout in LoginSAP", context={"tentativa": 3})
set_log("Message with the chosen level", level="critical")
  • context is an optional dict that goes along with the line (the item being processed, the attempt, the return code). Environment values are masked in it too.
  • set_log_from_exception(erro) sends the entire stack trace as an Error line.
  • In a Proton run, each line also goes to the process output, with the time and the level. The runner uploads this output as the run's log file: above the Proton log limit (10,000 lines), that file is where the full log is kept.

Correlation ID​

Each run has a Correlation ID, the same one the Proton screen shows. The runner delivers it in PROTON_CORRELATION_ID (runner 5.0.8 onward), and the SDK sends it in every call to Proton (X-Correlation-Id and traceparent, in the W3C Trace Context format). To carry it into the calls the automation makes (SAP, APIs, portals), use correlation_headers(). With it, the log of the called system can be found by the run id:

import requests
from proton.proton_env import correlation_headers, get_correlation_id
from proton.proton_logs import log_info

requests.post(url, json=pedido, headers={**correlation_headers(), "Authorization": token})
log_info(f"Order sent ({get_correlation_id()})")

Outside Proton, or with a runner that doesn't deliver the id yet, correlation_headers() returns an empty dictionary.

Run status​

Proton 5 accepts RunStatus.RUNNING, PASSED and FAILED. Since 5.4.0, FAILED can carry the reason, which says why the run failed without changing the status:

from proton.proton_automation import update_run_status
from proton.run_status import FailureReason, RunStatus

update_run_status(RunStatus.FAILED, reason=FailureReason.BUSINESS)
ReasonWhen
FailureReason.BUSINESSA business rule didn't allow the run to continue: a record that already exists, an invoice already rejected. Running again gives the same result.
FailureReason.TECHNICALAny other error: an element that didn't show up, a system that is down, a code exception. This is the default.
FailureReason.ENVIRONMENTThe environment didn't allow the run: a missing dependency, a wrong setting.

With the component executor, just raise the reason's exception in the step: ProtonBusinessError for a business failure and, since 5.5.0, ProtonEnvironmentFailure for an environment failure (a session that doesn't open, a device that is offline). Any other exception counts as technical.

from proton.run_status import ProtonBusinessError, ProtonEnvironmentFailure

if cadastro_existe:
raise ProtonBusinessError(f"CNPJ {cnpj} is already registered")

try:
navegador = abrir_navegador()
except WebDriverException as erro:
raise ProtonEnvironmentFailure("The grid didn't open the browser") from erro

Since 5.5.0, the executor looks for the reason in the error and in its causes (raise ... from causa), through the failure_reason function (from proton.run_status), and the first one that states a reason wins. An exception states the reason through the failure_reason attribute, so an exception from a library that already extends another class can state it too:

from proton.run_status import FailureReason


class SessaoNaoAbriu(RuntimeError):
failure_reason = FailureReason.ENVIRONMENT

The ProtonEnvironmentError from the run environment (the missing variable) also counts as an environment failure. In the Java SDK, the environment failure exception is called ProtonEnvironmentError; in Python that name was already taken by the run environment error, hence ProtonEnvironmentFailure.

The Proton 4 statuses (FAILED_DATA, FAILED_ENVIRONMENT and IN_PROCESS) are deprecated: the server rejects them, and the SDK sends FAILED in place of the first two, with the business and environment reasons, and RUNNING in place of the last one, with a DeprecationWarning and a warning in the log the first time.

Reference​

ModuleFunctionWhat it does
proton.component_runnerrun_components(localizar, ...)Runs the dataset steps with the component executor
run_evidence_dir()The run's evidence folder (5.5.0)
record_failure(erro, reason=None)Records a failure that happened before the executor (5.5.0)
proton.proton_automationstart_component()Opens the current step and reads the run environment
end_component()Closes the current step
get_current_component_name()Name of the current component; empty when there are no more steps
get_current_component_system()System of the current component
update_run_status(status, reason=None)Marks the run with a RunStatus and, for FAILED, the reason
get_automation_name() and get_cycle_name()Name of the automation and of the cycle
get_dataset_run_status(id=None)Status of the run (the current one, without an id)
get_dataset_run_info()Run data; in a mobile automation, also the device (device), where it runs (deviceFarm) and the OS version on BrowserStack (deviceOsVersion), the last two on a Proton that already returns them
get_dataset_info(id=None)Dataset data (the run's dataset, without an id)
get_automation_group_info(id)Automation group data
proton.proton_parameterget_proton_value(nome)Value of a dataset parameter in the open step
get_proton_all_component_parameters()All parameters of the open step
set_proton_value(nome, valor)Records an output parameter
get_proton_output_parameter_list(nome)Outputs recorded in the run with that name
proton.proton_logsset_log(texto, disable_date_time=False, level="info", context=None)Writes to the run log, with the level; with disable_date_time=True, accepts Markdown without date and time
log_info, log_warning, log_error and log_criticalWrites with the level in the name and an optional context
set_log_from_exception(erro)Writes the stack trace to the log, as Error
set_error_log(texto)Records the run error
proton.proton_files_and_resourcesupload_image(caminho)Uploads an image as evidence
upload_file_resource(caminho)Uploads a file as evidence
proton.proton_environmentget_environment_value(nome) and othersRun environment: see Run environment
proton.proton_envis_proton_execution()Whether the automation is running through Proton
get_id_dataset_run() and set_id_dataset_run(id)Run id
get_correlation_id() and correlation_headers()The run's Correlation ID and the headers to pass it along
proton.run_statusRunStatus and FailureReasonStatus and failure reason
ProtonBusinessError and ProtonEnvironmentFailureExceptions that mark a business failure and an environment failure
failure_reason(erro)Reason of an error, also looking at its causes (5.5.0)

Communication errors​

A server error doesn't stop the automation: reads come back empty (get_proton_value also returns "" when the parameter doesn't exist) and the control calls print a [proton] line with the route and the HTTP status, which the runner carries into the run log. When a value comes back empty for no apparent reason, start with the log.

The exception is the run environment: reading a variable that doesn't exist stops the script with ProtonEnvironmentError, because continuing with an empty value would hide the problem. Since 5.5.0, in the component executor, it counts as an environment failure.