getrak-python-sdk 1.0.0__tar.gz → 1.2.0__tar.gz
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.
- {getrak_python_sdk-1.0.0/src/getrak_python_sdk.egg-info → getrak_python_sdk-1.2.0}/PKG-INFO +3 -1
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/pyproject.toml +8 -2
- getrak_python_sdk-1.2.0/src/getrak/api.py +151 -0
- getrak_python_sdk-1.2.0/src/getrak/apis/recebidos.py +45 -0
- getrak_python_sdk-1.2.0/src/getrak/errors.py +14 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0/src/getrak_python_sdk.egg-info}/PKG-INFO +3 -1
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak_python_sdk.egg-info/SOURCES.txt +4 -2
- getrak_python_sdk-1.2.0/src/getrak_python_sdk.egg-info/requires.txt +2 -0
- getrak_python_sdk-1.2.0/src/getrak_python_sdk.egg-info/top_level.txt +1 -0
- getrak_python_sdk-1.2.0/tests/test_client.py +138 -0
- getrak_python_sdk-1.0.0/src/getrak/api.py +0 -46
- getrak_python_sdk-1.0.0/src/getrak/apis/recebidos.py +0 -14
- getrak_python_sdk-1.0.0/src/getrak/database/models/__init__.py +0 -0
- getrak_python_sdk-1.0.0/src/getrak_python_sdk.egg-info/top_level.txt +0 -2
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/LICENSE +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/README.md +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/setup.cfg +0 -0
- {getrak_python_sdk-1.0.0/src → getrak_python_sdk-1.2.0/src/getrak}/__init__.py +0 -0
- {getrak_python_sdk-1.0.0/src/getrak → getrak_python_sdk-1.2.0/src/getrak/apis}/__init__.py +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak/apis/localizacoes.py +0 -0
- {getrak_python_sdk-1.0.0/src/getrak/apis → getrak_python_sdk-1.2.0/src/getrak/database}/__init__.py +0 -0
- {getrak_python_sdk-1.0.0/src/getrak/database → getrak_python_sdk-1.2.0/src/getrak/database/models}/__init__.py +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak/database/models/dataexplained.py +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak/database/models/vehicle.py +0 -0
- {getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak_python_sdk.egg-info/dependency_links.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getrak-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Client de comunicaçao c/ API da Getrak
|
|
5
5
|
Author-email: Filipe Coelho <filipe@fmconsult.com.br>
|
|
6
6
|
License: MIT License
|
|
@@ -18,4 +18,6 @@ Classifier: Operating System :: OS Independent
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: fmconsult-utils-python-sdk>=2.2.0
|
|
22
|
+
Requires-Dist: jsonpickle>=3.0
|
|
21
23
|
Dynamic: license-file
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "getrak-python-sdk"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Client de comunicaçao c/ API da Getrak"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -19,7 +19,13 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
dependencies = [
|
|
22
|
+
dependencies = [
|
|
23
|
+
"fmconsult-utils-python-sdk>=2.2.0",
|
|
24
|
+
"jsonpickle>=3.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
23
29
|
|
|
24
30
|
[project.urls]
|
|
25
31
|
"Homepage" = "https://github.com/seuuser/minha-biblioteca"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from http import HTTPMethod
|
|
7
|
+
|
|
8
|
+
from fmconsult.exceptions.bad_request_exception import BadRequestException
|
|
9
|
+
from fmconsult.exceptions.forbidden_exception import ForbiddenException
|
|
10
|
+
from fmconsult.exceptions.unauthorized_exception import UnauthorizedException
|
|
11
|
+
from fmconsult.http.api import ApiBase, ContentType
|
|
12
|
+
from fmconsult.utils.url import UrlUtil
|
|
13
|
+
|
|
14
|
+
from getrak.errors import AuthenticationFailed, GetrakError
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
AUTH_REJECTED = (BadRequestException, UnauthorizedException, ForbiddenException)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GetrakApi(ApiBase):
|
|
22
|
+
"""Cliente da API Getrak.
|
|
23
|
+
|
|
24
|
+
Sem argumentos, lê `getrak.api.auth_code`, `getrak.api.login`, `getrak.api.password`
|
|
25
|
+
e `getrak.api.grant_type` e autentica na construção, como antes. Usuário e senha vão
|
|
26
|
+
no corpo do POST. Os demais argumentos são opcionais e servem para reutilizar um
|
|
27
|
+
token, limitar a taxa e tentar de novo em falha passageira.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
auth_code=None,
|
|
34
|
+
login=None,
|
|
35
|
+
password=None,
|
|
36
|
+
grant_type=None,
|
|
37
|
+
base_url=None,
|
|
38
|
+
rate_limiter=None,
|
|
39
|
+
retry_transient=False,
|
|
40
|
+
authenticate_on_init=True,
|
|
41
|
+
verbose=True,
|
|
42
|
+
sleep=None,
|
|
43
|
+
clock=None,
|
|
44
|
+
max_attempts=5,
|
|
45
|
+
on_rate_limited=None,
|
|
46
|
+
on_token=None,
|
|
47
|
+
log_requests=True,
|
|
48
|
+
timeout=60,
|
|
49
|
+
):
|
|
50
|
+
self.api_auth_code = auth_code if auth_code is not None else os.environ["getrak.api.auth_code"]
|
|
51
|
+
self.api_login = login if login is not None else os.environ["getrak.api.login"]
|
|
52
|
+
self.api_password = password if password is not None else os.environ["getrak.api.password"]
|
|
53
|
+
self.api_grant_type = grant_type if grant_type is not None else os.environ["getrak.api.grant_type"]
|
|
54
|
+
self.base_url = (base_url or "https://api.getrak.com").rstrip("/")
|
|
55
|
+
self.verbose = verbose
|
|
56
|
+
self._rate_limiter = rate_limiter
|
|
57
|
+
self._retry_transient = retry_transient
|
|
58
|
+
self._max_attempts = max_attempts
|
|
59
|
+
self._sleep = sleep or time.sleep
|
|
60
|
+
self._clock = clock or time.monotonic
|
|
61
|
+
self._on_rate_limited = on_rate_limited
|
|
62
|
+
self._on_token = on_token
|
|
63
|
+
self._log_requests = log_requests
|
|
64
|
+
self._timeout = timeout
|
|
65
|
+
self._auth_failed = False
|
|
66
|
+
self._token = None
|
|
67
|
+
self._token_type = "Bearer"
|
|
68
|
+
self._token_lock = threading.Lock()
|
|
69
|
+
self.requests = 0
|
|
70
|
+
self.rate_limited = 0
|
|
71
|
+
self.last_http_attempts = 0
|
|
72
|
+
self.current_operation = None
|
|
73
|
+
self.headers = {"Authorization": f"Basic {self.api_auth_code}"}
|
|
74
|
+
if authenticate_on_init:
|
|
75
|
+
self._ensure_token()
|
|
76
|
+
|
|
77
|
+
def _raise_if_auth_failed(self):
|
|
78
|
+
if self._auth_failed:
|
|
79
|
+
raise AuthenticationFailed("autenticação na Getrak já foi recusada nesta execução")
|
|
80
|
+
|
|
81
|
+
def _ensure_token(self):
|
|
82
|
+
with self._token_lock:
|
|
83
|
+
self._raise_if_auth_failed()
|
|
84
|
+
if self._token is None:
|
|
85
|
+
self._token = self._authenticate()
|
|
86
|
+
return self._token
|
|
87
|
+
|
|
88
|
+
def _renew_token(self, stale):
|
|
89
|
+
with self._token_lock:
|
|
90
|
+
self._raise_if_auth_failed()
|
|
91
|
+
if self._token is None or self._token == stale:
|
|
92
|
+
self._token = self._authenticate()
|
|
93
|
+
return self._token
|
|
94
|
+
|
|
95
|
+
def _authenticate(self):
|
|
96
|
+
if self.verbose:
|
|
97
|
+
log.info("api authentication...")
|
|
98
|
+
self.current_operation = "autenticação"
|
|
99
|
+
self.headers = {"Authorization": f"Basic {self.api_auth_code}"}
|
|
100
|
+
url = UrlUtil().make_url(self.base_url, ["newkoauth", "oauth", "token"])
|
|
101
|
+
try:
|
|
102
|
+
raw = self.call_request(
|
|
103
|
+
HTTPMethod.POST,
|
|
104
|
+
url,
|
|
105
|
+
payload={
|
|
106
|
+
"username": self.api_login,
|
|
107
|
+
"password": self.api_password,
|
|
108
|
+
"grant_type": self.api_grant_type,
|
|
109
|
+
},
|
|
110
|
+
content_type=ContentType.FORM_URLENCODED,
|
|
111
|
+
request_timeout=self._timeout,
|
|
112
|
+
)
|
|
113
|
+
except AUTH_REJECTED:
|
|
114
|
+
self._auth_failed = True
|
|
115
|
+
raise AuthenticationFailed(
|
|
116
|
+
"Getrak recusou a autenticação; verifique usuário, senha e a credencial do cliente"
|
|
117
|
+
) from None
|
|
118
|
+
try:
|
|
119
|
+
data = json.loads(raw)
|
|
120
|
+
token = data["access_token"]
|
|
121
|
+
except (ValueError, KeyError, TypeError):
|
|
122
|
+
raise GetrakError("resposta de autenticação da Getrak sem access_token") from None
|
|
123
|
+
if not isinstance(token, str) or not token:
|
|
124
|
+
raise GetrakError("resposta de autenticação da Getrak sem access_token")
|
|
125
|
+
if isinstance(data, dict) and data.get("error"):
|
|
126
|
+
raise AuthenticationFailed("Getrak recusou a autenticação")
|
|
127
|
+
token_type = str(data.get("token_type") or "Bearer")
|
|
128
|
+
self._token_type = "Bearer" if token_type.lower() == "bearer" else token_type
|
|
129
|
+
self.headers = {"Authorization": f"{self._token_type} {token}"}
|
|
130
|
+
if self._on_token is not None:
|
|
131
|
+
self._on_token(token)
|
|
132
|
+
return token
|
|
133
|
+
|
|
134
|
+
def _get(self, url, token, operation):
|
|
135
|
+
self.current_operation = operation
|
|
136
|
+
self.headers = {"Authorization": f"{self._token_type} {token}"}
|
|
137
|
+
return self.call_request(HTTPMethod.GET, url, request_timeout=self._timeout)
|
|
138
|
+
|
|
139
|
+
def _authorized(self, url, operation):
|
|
140
|
+
attempts = 0
|
|
141
|
+
token = self._ensure_token()
|
|
142
|
+
try:
|
|
143
|
+
raw = self._get(url, token, operation)
|
|
144
|
+
except UnauthorizedException:
|
|
145
|
+
attempts += self.last_http_attempts
|
|
146
|
+
token = self._renew_token(token)
|
|
147
|
+
raw = self._get(url, token, operation)
|
|
148
|
+
attempts += self.last_http_attempts
|
|
149
|
+
return raw, attempts
|
|
150
|
+
attempts += self.last_http_attempts
|
|
151
|
+
return raw, attempts
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import jsonpickle
|
|
6
|
+
from http import HTTPMethod
|
|
7
|
+
from fmconsult.utils.url import UrlUtil
|
|
8
|
+
from getrak.api import GetrakApi
|
|
9
|
+
from getrak.errors import GetrakError, InvalidVehicleId, VehicleNotFound
|
|
10
|
+
|
|
11
|
+
VEHICLE_ID_RE = re.compile(r"^[A-Za-z0-9_-]+$")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Recebidos(GetrakApi):
|
|
15
|
+
|
|
16
|
+
def get(self, id_veiculo, data_inicio, data_fim):
|
|
17
|
+
logging.info(f'get vehicle {id_veiculo} records from {data_inicio} to {data_fim}...')
|
|
18
|
+
res = self.call_request(
|
|
19
|
+
http_method=HTTPMethod.GET,
|
|
20
|
+
request_url=UrlUtil().make_url(self.base_url, ['v0.1','recebidos', id_veiculo, data_inicio, data_fim])
|
|
21
|
+
)
|
|
22
|
+
return jsonpickle.decode(res)
|
|
23
|
+
|
|
24
|
+
def fetch(self, vehicle_id, start, end):
|
|
25
|
+
"""Busca as posições já autenticando sob demanda. Devolve `(lista, tentativas HTTP)`.
|
|
26
|
+
|
|
27
|
+
Valida o ID aqui. `get` continua aceitando o valor que o chamador passar.
|
|
28
|
+
"""
|
|
29
|
+
if not isinstance(vehicle_id, str) or VEHICLE_ID_RE.fullmatch(vehicle_id) is None:
|
|
30
|
+
raise InvalidVehicleId("ID de veículo Getrak inválido")
|
|
31
|
+
url = UrlUtil().make_url(self.base_url, ["v0.1", "recebidos", vehicle_id, start, end])
|
|
32
|
+
raw, attempts = self._authorized(url, "recebidos")
|
|
33
|
+
try:
|
|
34
|
+
body = json.loads(raw)
|
|
35
|
+
except ValueError:
|
|
36
|
+
raise GetrakError("resposta de recebidos não é JSON") from None
|
|
37
|
+
if isinstance(body, dict):
|
|
38
|
+
message = body.get("error") if isinstance(body.get("error"), str) else ""
|
|
39
|
+
if "não encontrado" in message.casefold():
|
|
40
|
+
raise VehicleNotFound(message)
|
|
41
|
+
if not isinstance(body, list):
|
|
42
|
+
raise GetrakError("resposta de recebidos não é uma lista")
|
|
43
|
+
if not all(isinstance(item, dict) for item in body):
|
|
44
|
+
raise GetrakError("resposta de recebidos contém itens que não são objetos")
|
|
45
|
+
return body, attempts
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class GetrakError(Exception):
|
|
2
|
+
"""Erro da integração com a Getrak."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidVehicleId(GetrakError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VehicleNotFound(GetrakError):
|
|
10
|
+
"""A conta não tem esse veículo. Não há lista de posições para gravar."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthenticationFailed(GetrakError):
|
|
14
|
+
"""A Getrak recusou as credenciais. Vale para as chamadas seguintes desta instância."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getrak-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Client de comunicaçao c/ API da Getrak
|
|
5
5
|
Author-email: Filipe Coelho <filipe@fmconsult.com.br>
|
|
6
6
|
License: MIT License
|
|
@@ -18,4 +18,6 @@ Classifier: Operating System :: OS Independent
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: fmconsult-utils-python-sdk>=2.2.0
|
|
22
|
+
Requires-Dist: jsonpickle>=3.0
|
|
21
23
|
Dynamic: license-file
|
{getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak_python_sdk.egg-info/SOURCES.txt
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
-
src/__init__.py
|
|
5
4
|
src/getrak/__init__.py
|
|
6
5
|
src/getrak/api.py
|
|
6
|
+
src/getrak/errors.py
|
|
7
7
|
src/getrak/apis/__init__.py
|
|
8
8
|
src/getrak/apis/localizacoes.py
|
|
9
9
|
src/getrak/apis/recebidos.py
|
|
@@ -14,4 +14,6 @@ src/getrak/database/models/vehicle.py
|
|
|
14
14
|
src/getrak_python_sdk.egg-info/PKG-INFO
|
|
15
15
|
src/getrak_python_sdk.egg-info/SOURCES.txt
|
|
16
16
|
src/getrak_python_sdk.egg-info/dependency_links.txt
|
|
17
|
-
src/getrak_python_sdk.egg-info/
|
|
17
|
+
src/getrak_python_sdk.egg-info/requires.txt
|
|
18
|
+
src/getrak_python_sdk.egg-info/top_level.txt
|
|
19
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
getrak
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from urllib.parse import parse_qs
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import responses
|
|
6
|
+
from responses import registries
|
|
7
|
+
|
|
8
|
+
from getrak.apis.recebidos import Recebidos
|
|
9
|
+
from getrak.errors import AuthenticationFailed, GetrakError, InvalidVehicleId, VehicleNotFound
|
|
10
|
+
|
|
11
|
+
BASE = "https://api.getrak.com"
|
|
12
|
+
AUTH = f"{BASE}/newkoauth/oauth/token"
|
|
13
|
+
POS = f"{BASE}/v0.1/recebidos/12345/2026-10-04T03:00:00/2026-10-05T02:59:59"
|
|
14
|
+
PASSWORD = "SenhaSuperSecreta123"
|
|
15
|
+
AUTH_CODE = base64.b64encode(b"cliente:segredo").decode()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def env(monkeypatch):
|
|
20
|
+
monkeypatch.setenv("getrak.api.auth_code", AUTH_CODE)
|
|
21
|
+
monkeypatch.setenv("getrak.api.login", "usuario")
|
|
22
|
+
monkeypatch.setenv("getrak.api.password", PASSWORD)
|
|
23
|
+
monkeypatch.setenv("getrak.api.grant_type", "password")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_recebidos_keeps_constructor_and_sends_password_in_the_body(env):
|
|
27
|
+
with responses.RequestsMock(registry=registries.OrderedRegistry) as http:
|
|
28
|
+
http.add(responses.POST, AUTH, json={"access_token": "tok", "token_type": "bearer"}, status=200)
|
|
29
|
+
http.add(responses.GET, POS, json=[{"lat": 1}], status=200)
|
|
30
|
+
records = Recebidos().get("12345", "2026-10-04T03:00:00", "2026-10-05T02:59:59")
|
|
31
|
+
sent = http.calls[0].request
|
|
32
|
+
assert "password" not in sent.url
|
|
33
|
+
assert parse_qs(sent.body)["password"] == [PASSWORD]
|
|
34
|
+
assert sent.headers["Authorization"] == f"Basic {AUTH_CODE}"
|
|
35
|
+
assert http.calls[1].request.headers["Authorization"] == "Bearer tok"
|
|
36
|
+
|
|
37
|
+
assert records == [{"lat": 1}]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_password_is_not_logged(env, caplog):
|
|
41
|
+
caplog.set_level("INFO")
|
|
42
|
+
with responses.RequestsMock() as http:
|
|
43
|
+
http.add(
|
|
44
|
+
responses.POST,
|
|
45
|
+
AUTH,
|
|
46
|
+
json={"access_token": "tok-secreto-xyz", "token_type": "bearer"},
|
|
47
|
+
status=200,
|
|
48
|
+
)
|
|
49
|
+
Recebidos()
|
|
50
|
+
assert PASSWORD not in caplog.text
|
|
51
|
+
assert "tok-secreto-xyz" not in caplog.text
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_fetch_reuses_token_and_rejects_bad_id():
|
|
55
|
+
sleeps = []
|
|
56
|
+
with responses.RequestsMock(registry=registries.OrderedRegistry) as http:
|
|
57
|
+
http.add(responses.POST, AUTH, json={"access_token": "tok-1", "token_type": "Bearer"}, status=200)
|
|
58
|
+
http.add(responses.GET, POS, json=[{"a": 1}], status=200)
|
|
59
|
+
client = Recebidos(
|
|
60
|
+
auth_code=AUTH_CODE,
|
|
61
|
+
login="usuario",
|
|
62
|
+
password=PASSWORD,
|
|
63
|
+
grant_type="password",
|
|
64
|
+
authenticate_on_init=False,
|
|
65
|
+
verbose=False,
|
|
66
|
+
log_requests=False,
|
|
67
|
+
retry_transient=True,
|
|
68
|
+
sleep=sleeps.append,
|
|
69
|
+
)
|
|
70
|
+
with pytest.raises(InvalidVehicleId):
|
|
71
|
+
client.fetch("12/34", "2026-10-04T03:00:00", "2026-10-05T02:59:59")
|
|
72
|
+
assert len(http.calls) == 0
|
|
73
|
+
positions, attempts = client.fetch("12345", "2026-10-04T03:00:00", "2026-10-05T02:59:59")
|
|
74
|
+
posts = [call for call in http.calls if call.request.method == "POST"]
|
|
75
|
+
|
|
76
|
+
assert positions == [{"a": 1}]
|
|
77
|
+
assert attempts == 1
|
|
78
|
+
assert len(posts) == 1
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_rejected_credentials_are_not_retried():
|
|
82
|
+
with responses.RequestsMock() as http:
|
|
83
|
+
http.add(responses.POST, AUTH, status=401)
|
|
84
|
+
client = Recebidos(
|
|
85
|
+
auth_code=AUTH_CODE,
|
|
86
|
+
login="usuario",
|
|
87
|
+
password=PASSWORD,
|
|
88
|
+
grant_type="password",
|
|
89
|
+
authenticate_on_init=False,
|
|
90
|
+
verbose=False,
|
|
91
|
+
log_requests=False,
|
|
92
|
+
retry_transient=True,
|
|
93
|
+
)
|
|
94
|
+
with pytest.raises(AuthenticationFailed):
|
|
95
|
+
client.fetch("12345", "inicio", "fim")
|
|
96
|
+
with pytest.raises(AuthenticationFailed):
|
|
97
|
+
client.fetch("12345", "inicio", "fim")
|
|
98
|
+
assert len(http.calls) == 1
|
|
99
|
+
assert PASSWORD not in str(http.calls[0].request.url)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_non_list_is_an_error():
|
|
103
|
+
with responses.RequestsMock() as http:
|
|
104
|
+
http.add(responses.POST, AUTH, json={"access_token": "tok"}, status=200)
|
|
105
|
+
http.add(responses.GET, POS, json={"erro": "x"}, status=200)
|
|
106
|
+
client = Recebidos(
|
|
107
|
+
auth_code=AUTH_CODE,
|
|
108
|
+
login="usuario",
|
|
109
|
+
password=PASSWORD,
|
|
110
|
+
grant_type="password",
|
|
111
|
+
authenticate_on_init=False,
|
|
112
|
+
verbose=False,
|
|
113
|
+
log_requests=False,
|
|
114
|
+
)
|
|
115
|
+
with pytest.raises(GetrakError):
|
|
116
|
+
client.fetch("12345", "2026-10-04T03:00:00", "2026-10-05T02:59:59")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_unknown_vehicle_is_a_specific_error():
|
|
120
|
+
with responses.RequestsMock() as http:
|
|
121
|
+
http.add(responses.POST, AUTH, json={"access_token": "tok"}, status=200)
|
|
122
|
+
http.add(
|
|
123
|
+
responses.GET,
|
|
124
|
+
POS,
|
|
125
|
+
json={"error": "Veículo não encontrado para o cliente informado", "status": 404},
|
|
126
|
+
status=200,
|
|
127
|
+
)
|
|
128
|
+
client = Recebidos(
|
|
129
|
+
auth_code=AUTH_CODE,
|
|
130
|
+
login="usuario",
|
|
131
|
+
password=PASSWORD,
|
|
132
|
+
grant_type="password",
|
|
133
|
+
authenticate_on_init=False,
|
|
134
|
+
verbose=False,
|
|
135
|
+
log_requests=False,
|
|
136
|
+
)
|
|
137
|
+
with pytest.raises(VehicleNotFound):
|
|
138
|
+
client.fetch("12345", "2026-10-04T03:00:00", "2026-10-05T02:59:59")
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import logging, os, jsonpickle
|
|
2
|
-
from http import HTTPMethod
|
|
3
|
-
from fmconsult.http.api import ApiBase
|
|
4
|
-
from fmconsult.utils.url import UrlUtil
|
|
5
|
-
|
|
6
|
-
class GetrakApi(ApiBase):
|
|
7
|
-
|
|
8
|
-
def __init__(self):
|
|
9
|
-
try:
|
|
10
|
-
self.api_auth_code = os.environ['getrak.api.auth_code']
|
|
11
|
-
self.api_login = os.environ['getrak.api.login']
|
|
12
|
-
self.api_password = os.environ['getrak.api.password']
|
|
13
|
-
self.api_grant_type = os.environ['getrak.api.grant_type']
|
|
14
|
-
|
|
15
|
-
self.base_url = 'https://api.getrak.com'
|
|
16
|
-
self.headers = {
|
|
17
|
-
'Authorization': f'Basic {self.api_auth_code}'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
self.__login()
|
|
21
|
-
except:
|
|
22
|
-
raise
|
|
23
|
-
|
|
24
|
-
def __login(self):
|
|
25
|
-
try:
|
|
26
|
-
logging.info('api authentication...')
|
|
27
|
-
res = self.call_request(
|
|
28
|
-
http_method=HTTPMethod.POST,
|
|
29
|
-
request_url=UrlUtil().make_url(self.base_url, ['newkoauth','oauth','token']),
|
|
30
|
-
params={
|
|
31
|
-
'username': self.api_login,
|
|
32
|
-
'password': self.api_password,
|
|
33
|
-
'grant_type': self.api_grant_type
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
res = jsonpickle.decode(res)
|
|
37
|
-
if not('error' in res):
|
|
38
|
-
self.token_type = res['token_type']
|
|
39
|
-
self.access_token = res['access_token']
|
|
40
|
-
self.headers = {
|
|
41
|
-
'Authorization': f'{self.token_type} {self.access_token}'
|
|
42
|
-
}
|
|
43
|
-
else:
|
|
44
|
-
raise Exception(res['error_description'])
|
|
45
|
-
except:
|
|
46
|
-
raise
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import logging, jsonpickle
|
|
2
|
-
from http import HTTPMethod
|
|
3
|
-
from fmconsult.utils.url import UrlUtil
|
|
4
|
-
from getrak.api import GetrakApi
|
|
5
|
-
|
|
6
|
-
class Recebidos(GetrakApi):
|
|
7
|
-
|
|
8
|
-
def get(self, id_veiculo, data_inicio, data_fim):
|
|
9
|
-
logging.info(f'get vehicle {id_veiculo} records from {data_inicio} to {data_fim}...')
|
|
10
|
-
res = self.call_request(
|
|
11
|
-
http_method=HTTPMethod.GET,
|
|
12
|
-
request_url=UrlUtil().make_url(self.base_url, ['v0.1','recebidos', id_veiculo, data_inicio, data_fim])
|
|
13
|
-
)
|
|
14
|
-
return jsonpickle.decode(res)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{getrak_python_sdk-1.0.0/src/getrak/apis → getrak_python_sdk-1.2.0/src/getrak/database}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{getrak_python_sdk-1.0.0 → getrak_python_sdk-1.2.0}/src/getrak/database/models/dataexplained.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|