Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eacb9635c0 | |||
| 0371ae9175 | |||
| 86d1d612a0 | |||
| 4c130636d3 | |||
| f67bef6d43 | |||
| 6a3720190f | |||
| 7e0959d9b7 | |||
| 08161fd276 | |||
| 4b548df2f7 | |||
| 65b39e4bb8 | |||
| 21f5227844 |
@@ -2,3 +2,4 @@
|
||||
.vscode
|
||||
**/__pycache__/
|
||||
**/main.py
|
||||
*.http
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user