11 Commits

11 changed files with 406 additions and 30 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
.idea
.vscode
**/__pycache__/
**/main.py
**/main.py
*.http
+89
View File
@@ -0,0 +1,89 @@
import base64
import json
import os
import requests
class API_Proxy:
"""
## API Proxy
---
O Serviço API Proxy permite realizar chamadas a api externas proxiadas pelo Replay. Dessa forma, as chamadas podem ficar pré- configuradas dentro do Replay.
Caso o robô ou aplicação desejem alterar algum parâmetro antes da chamada ainda é possível, para isso, deve-se modificar a estrutura da requisição.
Para entender melhor a como funciona a estrutura de requisição, você pode visitar a documentaçao oficial ou dar uma olhada no método "do ()" deste client.
"""
ep: str = ""
def __init__ (self):
self.ep = "https://localhost:8443"
def __request_json_post__(self, path: str, object: dict):
"""
## HTTP JSON POST
---
Este método é responsável por realizar requisições HTTP do tipo POST para objetos JSON.
Ele retorna o corpo de resposta da requisição, ou uma mensagem de erro, que indica qual foi a irregularidade ocorrida ao chamar a API.
"""
url = self.ep + path
print("Calling: " + url)
apikey = os.environ.get('REPLAY_APIKEY')
headers = {"X-API-KEY": apikey}
res = requests.post(url, json = object, headers = headers, verify = False)
response = json.loads(res.text)
byte_encoded_body = base64.b64decode(response['body'])
decoded_body = str(byte_encoded_body, "utf-8")
if response['status_code'] >= 400:
raise Exception(f"HTTP ERROR: {str(response['status_code'])} - {response['body']}")
if response['header']['Content-Type'] != None and response['header']['Content-Type'].find('json') != -1:
return json.loads(decoded_body)
else:
return decoded_body
def do (self, request: dict):
"""
## API Proxy Do
Faz o proxy da chamada remota e retorna qualquer que seja o resultado desta requisição.
---
#### Parâmetros:
- request: Estrutura específica da requisição. Veja abaixo como construí-la:
{
"Name": "Nome de template da chamada",
"Method": "Método de requisição (GET, POST, PUT, ...)",
"Url": "Url da API que se deseja chamar",
"Header": {
"Chave da configuração de cabeçalho": "Valor associado",
...
},
"Body": "Elementos pertencentes ao corpo da requisição",
"Readonly":
- True: Mesmo que seja encontrado um template correspondente ao valor de "Name" passado, as configurações desta estrutura prevalecerão na requisição.
- False: Caso um template seja encontrado em correspondência ao valor passado em "Name", as configurações do mesmo sobrescreverão as informações passadas nesta estrutura.
}
---
#### Retorna:
-> Resultado da requisição
"""
return self.__request_json_post__ ("/ipc/apiproxy/do", request)
+12
View File
@@ -0,0 +1,12 @@
Feature: The API Proxy client
Scenario: A HTTP request with json body response
Given a API Proxy client
When a request is made with GET for the API: https://pokeapi.co/api/v2/pokemon/ditto
Then there must be a json response
And the pokemon name must be ditto
Scenario: A HTTP request with text body response
Given a API Proxy client
When a request is made with GET for the API: https://www.slashdot.org
Then the response content type must be string
+41
View File
@@ -0,0 +1,41 @@
from re import A
from behave import *
from cli import API_Proxy
# =========================== JSON RESPONSE BODY ===========================
@given(u'a API Proxy client')
def step_impl(context):
context.client = API_Proxy ()
@when(u'a request is made with GET for the API: https://pokeapi.co/api/v2/pokemon/ditto')
def step_impl(context):
context.response = context.client.do (
{
"Method": "GET",
"Url": "https://pokeapi.co/api/v2/pokemon/ditto",
}
)
@then(u'there must be a json response')
def step_impl(context):
assert type (context.response) == dict, "Something went wrong calling the pokemon/ditto API."
@then(u'the pokemon name must be ditto')
def step_impl(context):
assert context.response["forms"][0]["name"] == "ditto", "This pokemon is not Ditto."
# =========================== TEXT RESPONSE BODY ===========================
@when(u'a request is made with GET for the API: https://www.slashdot.org')
def step_impl(context):
context.response = context.client.do (
{
"Method": "GET",
"Url": "https://www.slashdot.org",
}
)
@then(u'the response content type must be string')
def step_impl(context):
assert type (context.response) == str, "Something went wrong calling the slashdot API."
+148
View File
@@ -0,0 +1,148 @@
from datetime import datetime
import json
import requests
class DataAPI:
"""
## Data API
---
Esta classe permite a execução de comandos básicos SQL para o banco de dados remoto do Replay.
Para que a classe funcione corretamente, é necessário que o usuário tenha acesso à chave da API do BD remoto. Devendo passá-la como parâmetro ao instanciar a classe DataAPI. Por exemplo:
d_api = DataAPI ("ahsgcn21390-dsaf_dain2eq81288sad9")
"""
ep: str = ""
def __init__ (self, api_key: str):
self.ep = "https://dataapi.digitalcircle.com.br"
self.api_key = api_key
def __request_json_post__ (self, object: dict):
"""
## HTTP JSON POST
---
Este método é responsável por realizar requisições HTTP do tipo POST para objetos JSON.
Ele retorna o corpo de resposta da requisição, ou uma mensagem de erro, que indica qual foi a irregularidade ocorrida ao chamar a API.
"""
url = self.ep + "/"
print("Calling: " + url)
headers = {"X-API-KEY": self.api_key}
res = requests.post(url, json = object, headers = headers, verify = False)
if res.status_code >= 400:
raise Exception(f"HTTP ERROR: {str(res.status_code)} - {res.text}")
if res.headers.get("Content-Type") != None and res.headers.get("Content-Type").find("json") != -1:
return json.loads(res.text)
else:
return res.text
def do (self, dataAPI_request: dict):
"""
## DataAPI Do
Este método é responsável pela execução dos comandos SQL no Banco de Dados remoto.
---
#### Parâmetros de dataAPI_request:
- Col: Nome da tabela SQL em que se quer realizar a operação (OBRIGATÓRIO)
- Op: Operação que se quer desenvolver. São elas: "R", "C", "D", "U" (OBRIGATÓRIO)
- R: Retrieve (Busca informações da coluna da tabela)
- C: Create (Cria uma nova tabela)
- D: Delete (Deleta uma tabela ou as colunas de uma tabela)
- U: Update (Atualiza as informações de uma tabela)
- Q: Nome do atributo (coluna) que se quer consultar ou alterar na tabela SQL
- Id: Identificador da instância (linha) da tabela que se quer realizar a operação
- Data: Dados que se quer inserir na tabela
---
#### Retorna:
-> O retorno do comando feito, qualquer que seja ele.
"""
return self.__request_json_post__(dataAPI_request)
def registrar_exec (self, register_table: str, check: bool):
"""
## Registrar Execução
Esta função é utilizada para realizar um registro de execuções falhas e bem sucedidas de um robô. Tudo é feito diretamente na Base de Dados remota, eliminando a necessidade de planilhas Excel locais.
---
#### Parâmetros:
- register_table: Tabela utilizada para os registros de execução. Para começos de projeto, procure criar uma nova tabela, cheque se ela já existe utilizando a operação de Retrieve pelo método Do.
- check: Assume dois valores dependendo do sucesso da execução do robô: True ou False.
- True: Execução obteve sucesso.
- False: Execução falhou em algum ponto.
---
#### Retorna:
---
"""
today_date = datetime.now().isoformat()[:10]
table_check = self.do({
"Col": register_table,
"Op": "R",
"Q": f"@[?date=='{today_date}']"
})
if len(table_check["data"]) > 0:
data = table_check["data"][0]
id = str(int(data["ID"]))
if check:
success = int(data["exec"]) + 1
try:
self.do({
"Col": register_table,
"Op": "U",
"Id": id,
"Data": {
"exec": success
}
})
except:
return "Algo falhou ao registrar a execução"
else:
fail = int(data["err"]) + 1
try:
self.do({
"Col": register_table,
"Op": "U",
"Id": id,
"Data": {
"err": fail
}
})
except:
return "Algo falhou ao registrar a execução"
else:
if check:
success = 1
fail = 0
else:
success = 0
fail = 1
try:
self.do({
"Col": register_table,
"Op": "C",
"Data": {
"date": today_date,
"exec": success,
"err": fail,
}
})
except:
return "Algo falhou ao registrar a execução"
+13
View File
@@ -0,0 +1,13 @@
Feature: The Data API Client
Scenario: Testing the data_API.do() method
Given a data_API client
When a new table is created with the value "Bond" for the query "the_name_is"
Then the value retrieved from the new table associated with the query "the_name_is" must be "Bond"
Scenario: Testing the data_API.registrar_exec method
Given a data_API client
When the registrar_exec method is called with True for check
And the registrar_exec method is called with False for check
And data is retrieved from the table
Then there must be at least 1 register for "exec" and "err" today
+53
View File
@@ -0,0 +1,53 @@
from behave import *
from cli import *
# ============================= DATA_API DO =============================
@given(u'a data_API client')
def step_impl(context):
context.client = DataAPI ("Insira aqui a chave de API. Você pode obtê-la pedindo ao fornecedor deste client.")
@when(u'a new table is created with the value "Bond" for the query "the_name_is"')
def step_impl(context):
context.client.do ({
"Col": "newTestTable",
"Op": "C",
"Data": {
"the_name_is": "Bond",
"James": "Bond"
}
})
@then(u'the value retrieved from the new table associated with the query "the_name_is" must be "Bond"')
def step_impl(context):
context.retrieved_data = context.client.do({
"Col": "newTestTable",
"Op": "R"
})["data"][0]
context.query_value = context.retrieved_data["the_name_is"]
assert context.query_value == "Bond", "Something went wrong with the method Do"
# ============================= DATA_API REGISTRAR_EXEC =============================
@when(u'the registrar_exec method is called with True for check')
def step_impl(context):
context.client.registrar_exec("anotherNewTestTable", True)
@when(u'the registrar_exec method is called with False for check')
def step_impl(context):
context.client.registrar_exec("anotherNewTestTable", False)
@when(u'data is retrieved from the table')
def step_impl(context):
today_date = datetime.now().isoformat()[:10]
context.retrieved_data = context.client.do({
"Col": "anotherNewTestTable",
"Op": "R",
"Q": f"@[?date=='{today_date}']"
})["data"][0]
@then(u'there must be at least 1 register for "exec" and "err" today')
def step_impl(context):
assert context.retrieved_data["exec"] > 0 and context.retrieved_data["err"] > 0, "Something went wrong with the method registrar_exec"
+48
View File
@@ -0,0 +1,48 @@
import json
import os
import requests
class Data_sync:
"""
## Data Sync
---
?
"""
ep: str = ""
def __init__ (self):
self.ep = "https://localhost:8443"
def __request_json_post__ (self, path: str, object: dict):
"""
## HTTP JSON POST
---
Este método é responsável por realizar requisições HTTP do tipo POST para objetos JSON.
Ele retorna o corpo de resposta da requisição, ou uma mensagem de erro, que indica qual foi a irregularidade ocorrida ao chamar a API.
"""
url = self.ep + path
print("Calling: " + url)
apikey = os.environ.get('REPLAY_APIKEY')
headers = {"X-API-KEY": apikey}
res = requests.post(url, json = object, headers = headers, verify = False)
if res.status_code >= 400:
raise Exception(f"HTTP ERROR: {str(res.status_code)} - {res.text}")
if res.headers.get("Content-Type") != None and res.headers.get("Content-Type").find("json") != -1:
return json.loads(res.text)
else:
return res.text
def new (self, data_sync_report: dict):
"""
???
"""
return self.__request_json_post__("/ipc/datasyncreportmgr/new", data_sync_report)
-29
View File
@@ -1,7 +1,6 @@
from datetime import datetime
import json
import os
from time import time
import requests
import urllib
@@ -546,31 +545,3 @@ class Replay:
"""
return self.__request_get__(f"/api/v1/robots/op/enqueue/{job_id}")
def error (self, err_msg: str, desc: str):
"""
## Error
Este método é utilizado para postar erros relacionados ao programa, não às APIs, na base de dados do Replay.
---
#### Parâmetros:
- err_msg: Mensagem de erro que será registrada. Pense nela como um título, deve ser curto e direto ao ponto.
- desc: Descrição do erro ocorrido. Aqui devem ser dados mais detalhes acerca do que ocorreu de errado.
---
#### Retorna:
- ?
"""
robot_data = self.queue_get_my_data ()
error = {
"Feature": self.replay_env_alias (),
"Err": err_msg,
"When": datetime.now().isoformat() + "Z",
"Stack": "",
"InputData": robot_data,
"Details": desc
}
return self.__request_json_post__("/api/v1/err", error)