getrak-python-sdk 1.0.0__py3-none-any.whl → 1.2.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.
getrak/api.py CHANGED
@@ -1,46 +1,151 @@
1
- import logging, os, jsonpickle
1
+ import json
2
+ import logging
3
+ import os
4
+ import threading
5
+ import time
2
6
  from http import HTTPMethod
3
- from fmconsult.http.api import ApiBase
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
4
12
  from fmconsult.utils.url import UrlUtil
5
13
 
14
+ from getrak.errors import AuthenticationFailed, GetrakError
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+ AUTH_REJECTED = (BadRequestException, UnauthorizedException, ForbiddenException)
19
+
20
+
6
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)
7
138
 
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
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
getrak/apis/recebidos.py CHANGED
@@ -1,7 +1,15 @@
1
- import logging, jsonpickle
1
+ import json
2
+ import logging
3
+ import re
4
+
5
+ import jsonpickle
2
6
  from http import HTTPMethod
3
7
  from fmconsult.utils.url import UrlUtil
4
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
+
5
13
 
6
14
  class Recebidos(GetrakApi):
7
15
 
@@ -11,4 +19,27 @@ class Recebidos(GetrakApi):
11
19
  http_method=HTTPMethod.GET,
12
20
  request_url=UrlUtil().make_url(self.base_url, ['v0.1','recebidos', id_veiculo, data_inicio, data_fim])
13
21
  )
14
- return jsonpickle.decode(res)
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
getrak/errors.py ADDED
@@ -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.0.0
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
@@ -1,15 +1,15 @@
1
- __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
1
  getrak/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- getrak/api.py,sha256=IlO0dlqdUjJ3ErfPJhiMmIU-GQs4CU2Tt8VA3nLrz_M,1239
2
+ getrak/api.py,sha256=sRhJdXUknljeuvEsJPO1gTBscoUlm0KD_NwS-9BbhyA,5890
3
+ getrak/errors.py,sha256=mobmS8zzjzL1giAlj8o6OTX21gkz1OVbmd2n3CMNZOc,381
4
4
  getrak/apis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  getrak/apis/localizacoes.py,sha256=UuRqEHRMecUQRdSUVq7gRXLPeKe4IZ0byYFlGbDn-6U,654
6
- getrak/apis/recebidos.py,sha256=GDwLphfGcYSvFzmYS6oZ7aQVGDTPaSA3nduKOM6KFQw,516
6
+ getrak/apis/recebidos.py,sha256=pUWFyXiu-JWy0fykEKBETA7DTGM5jYbpwuqXsAQoZz0,1792
7
7
  getrak/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  getrak/database/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  getrak/database/models/dataexplained.py,sha256=9qjXvccphYUVK0LTNvxjMXRzMOsynbMmcYDkaK41Tdo,212
10
10
  getrak/database/models/vehicle.py,sha256=04SVD0TVwnF-4bLbMmdN0dKd9KboMa1-KfxkJ8Fwpv8,2941
11
- getrak_python_sdk-1.0.0.dist-info/licenses/LICENSE,sha256=YZjQnFjzXGFa0D3iCQkNDwGBNuTGJREkRmItGX1B3YM,122
12
- getrak_python_sdk-1.0.0.dist-info/METADATA,sha256=Ggz2gFc6tasbfOVQMELK9JKlmnmVYhujVmSH8qBY2RY,769
13
- getrak_python_sdk-1.0.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
14
- getrak_python_sdk-1.0.0.dist-info/top_level.txt,sha256=LvUv9DvjYEQ08xdxNLM8xrBtFwGphZIKlX1S0KE43oY,16
15
- getrak_python_sdk-1.0.0.dist-info/RECORD,,
11
+ getrak_python_sdk-1.2.0.dist-info/licenses/LICENSE,sha256=YZjQnFjzXGFa0D3iCQkNDwGBNuTGJREkRmItGX1B3YM,122
12
+ getrak_python_sdk-1.2.0.dist-info/METADATA,sha256=r_VOClVT49asa7dbWvWf0-6qDpwFfA4mT6D2PSR8Ku4,849
13
+ getrak_python_sdk-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
14
+ getrak_python_sdk-1.2.0.dist-info/top_level.txt,sha256=FTZAzVY_kDvwVRXKSWipeV7H-1ztlyuOMVHhyCEkQO0,7
15
+ getrak_python_sdk-1.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1 @@
1
+ getrak
__init__.py DELETED
File without changes
@@ -1,2 +0,0 @@
1
- __init__
2
- getrak