positiva-consultas-python-sdk 1.0.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.
- positiva/consultas/__init__.py +12 -0
- positiva/consultas/api.py +106 -0
- positiva/consultas/apis/__init__.py +4 -0
- positiva/consultas/apis/juridico360.py +169 -0
- positiva/consultas/dtos/__init__.py +40 -0
- positiva/consultas/dtos/auth.py +173 -0
- positiva/consultas/dtos/enums.py +28 -0
- positiva/consultas/dtos/juridico360.py +676 -0
- positiva/consultas/dtos/serde.py +62 -0
- positiva_consultas_python_sdk-1.0.0.dist-info/METADATA +114 -0
- positiva_consultas_python_sdk-1.0.0.dist-info/RECORD +14 -0
- positiva_consultas_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- positiva_consultas_python_sdk-1.0.0.dist-info/licenses/LICENSE +5 -0
- positiva_consultas_python_sdk-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""SDK Python para a API Positiva Consultas (PositivaConnect)."""
|
|
3
|
+
from positiva.consultas.api import PositivaConsultasApi
|
|
4
|
+
from positiva.consultas.apis.juridico360 import Juridico360Api
|
|
5
|
+
from positiva.consultas.dtos.enums import Juridico360ProductCode, MpKind
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'PositivaConsultasApi',
|
|
9
|
+
'Juridico360Api',
|
|
10
|
+
'Juridico360ProductCode',
|
|
11
|
+
'MpKind',
|
|
12
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Cliente base Positiva Consultas — autenticacao OAuth 2.0 Client Credentials."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import jsonpickle
|
|
10
|
+
from fmconsult.http.api import ApiBase
|
|
11
|
+
from fmconsult.utils.url import UrlUtil
|
|
12
|
+
from http import HTTPMethod
|
|
13
|
+
|
|
14
|
+
from positiva.consultas.dtos.auth import (
|
|
15
|
+
ClientMeResponse,
|
|
16
|
+
LogoutResponse,
|
|
17
|
+
TokenResponse,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PositivaConsultasApi(ApiBase):
|
|
22
|
+
"""API base com login automatico via ``POST /client/token`` (Basic Auth).
|
|
23
|
+
|
|
24
|
+
Variaveis de ambiente:
|
|
25
|
+
- ``positiva.consultas.api.client_id``
|
|
26
|
+
- ``positiva.consultas.api.client_secret``
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
API_BASE_URL = 'https://positivaconnect.positivaconsultas.com.br/api'
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.api_client_id = os.environ['positiva.consultas.api.client_id']
|
|
33
|
+
self.api_client_secret = os.environ['positiva.consultas.api.client_secret']
|
|
34
|
+
self.base_url = self.API_BASE_URL
|
|
35
|
+
self.access_token: str | None = None
|
|
36
|
+
self.token_expires_in: int | None = None
|
|
37
|
+
self.headers: dict[str, str] = {
|
|
38
|
+
'Accept': 'application/json',
|
|
39
|
+
}
|
|
40
|
+
self.__login()
|
|
41
|
+
|
|
42
|
+
def __basic_auth_header(self) -> str:
|
|
43
|
+
raw = f'{self.api_client_id}:{self.api_client_secret}'.encode()
|
|
44
|
+
return f'Basic {base64.b64encode(raw).decode()}'
|
|
45
|
+
|
|
46
|
+
def __apply_bearer(self, access_token: str, expires_in: int | None = None) -> None:
|
|
47
|
+
self.access_token = access_token
|
|
48
|
+
self.token_expires_in = expires_in
|
|
49
|
+
self.headers['Authorization'] = f'Bearer {access_token}'
|
|
50
|
+
|
|
51
|
+
def __login(self) -> TokenResponse:
|
|
52
|
+
"""POST /client/token — Authorization Basic (client_id:client_secret)."""
|
|
53
|
+
logging.info('authenticating via Positiva Consultas /client/token...')
|
|
54
|
+
self.headers = {
|
|
55
|
+
'Accept': 'application/json',
|
|
56
|
+
'Authorization': self.__basic_auth_header(),
|
|
57
|
+
}
|
|
58
|
+
url = UrlUtil().make_url(self.base_url, ['client', 'token'])
|
|
59
|
+
res = self.call_request(http_method=HTTPMethod.POST, request_url=url)
|
|
60
|
+
data = jsonpickle.decode(res)
|
|
61
|
+
token = TokenResponse.from_dict(data)
|
|
62
|
+
if token is None or not token.access_token:
|
|
63
|
+
raise Exception(
|
|
64
|
+
data.get('error_description')
|
|
65
|
+
or data.get('message')
|
|
66
|
+
or 'Falha ao obter access_token'
|
|
67
|
+
)
|
|
68
|
+
self.__apply_bearer(token.access_token, token.expires_in)
|
|
69
|
+
return token
|
|
70
|
+
|
|
71
|
+
def me(self) -> ClientMeResponse:
|
|
72
|
+
"""GET /client/me — dados do cliente autenticado."""
|
|
73
|
+
logging.info('fetching Positiva Consultas /client/me...')
|
|
74
|
+
url = UrlUtil().make_url(self.base_url, ['client', 'me'])
|
|
75
|
+
res = self.call_request(http_method=HTTPMethod.GET, request_url=url)
|
|
76
|
+
parsed = ClientMeResponse.from_dict(jsonpickle.decode(res))
|
|
77
|
+
if parsed is None or parsed.client is None:
|
|
78
|
+
raise Exception('Resposta inesperada de /client/me')
|
|
79
|
+
return parsed
|
|
80
|
+
|
|
81
|
+
def refresh(self) -> TokenResponse:
|
|
82
|
+
"""POST /client/refresh — renova o Bearer atual."""
|
|
83
|
+
logging.info('refreshing Positiva Consultas token...')
|
|
84
|
+
url = UrlUtil().make_url(self.base_url, ['client', 'refresh'])
|
|
85
|
+
res = self.call_request(http_method=HTTPMethod.POST, request_url=url)
|
|
86
|
+
data = jsonpickle.decode(res)
|
|
87
|
+
token = TokenResponse.from_dict(data)
|
|
88
|
+
if token is None or not token.access_token:
|
|
89
|
+
raise Exception(
|
|
90
|
+
data.get('error_description')
|
|
91
|
+
or data.get('message')
|
|
92
|
+
or 'Falha ao renovar access_token'
|
|
93
|
+
)
|
|
94
|
+
self.__apply_bearer(token.access_token, token.expires_in)
|
|
95
|
+
return token
|
|
96
|
+
|
|
97
|
+
def logout(self) -> LogoutResponse:
|
|
98
|
+
"""POST /client/logout — invalida o token atual."""
|
|
99
|
+
logging.info('logging out Positiva Consultas token...')
|
|
100
|
+
url = UrlUtil().make_url(self.base_url, ['client', 'logout'])
|
|
101
|
+
res = self.call_request(http_method=HTTPMethod.POST, request_url=url)
|
|
102
|
+
parsed = LogoutResponse.from_dict(jsonpickle.decode(res))
|
|
103
|
+
self.access_token = None
|
|
104
|
+
self.token_expires_in = None
|
|
105
|
+
self.headers.pop('Authorization', None)
|
|
106
|
+
return parsed or LogoutResponse()
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Cliente HTTP Positiva Juridico 360."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Sequence
|
|
7
|
+
|
|
8
|
+
import jsonpickle
|
|
9
|
+
from fmconsult.utils.url import UrlUtil
|
|
10
|
+
from http import HTTPMethod
|
|
11
|
+
|
|
12
|
+
from positiva.consultas.api import PositivaConsultasApi
|
|
13
|
+
from positiva.consultas.dtos.enums import Juridico360ProductCode
|
|
14
|
+
from positiva.consultas.dtos.juridico360 import (
|
|
15
|
+
Juridico360Options,
|
|
16
|
+
Juridico360PdfResponse,
|
|
17
|
+
Juridico360Request,
|
|
18
|
+
Juridico360Response,
|
|
19
|
+
)
|
|
20
|
+
from positiva.consultas.dtos.serde import digits_only, to_wire
|
|
21
|
+
|
|
22
|
+
_VALID_PRODUCTS = {p.value for p in Juridico360ProductCode}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_product_codes(
|
|
26
|
+
product_code: Sequence[str | Juridico360ProductCode],
|
|
27
|
+
) -> list[str]:
|
|
28
|
+
if not product_code:
|
|
29
|
+
raise ValueError('product_code é obrigatório e não pode ser vazio')
|
|
30
|
+
normalized: list[str] = []
|
|
31
|
+
for code in product_code:
|
|
32
|
+
value = str(to_wire(code))
|
|
33
|
+
if value not in _VALID_PRODUCTS:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f'product_code inválido: {value!r}. '
|
|
36
|
+
f'Valores aceitos: {sorted(_VALID_PRODUCTS)}'
|
|
37
|
+
)
|
|
38
|
+
if value not in normalized:
|
|
39
|
+
normalized.append(value)
|
|
40
|
+
return normalized
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _require_document(document: str | None) -> str:
|
|
44
|
+
digits = digits_only(document)
|
|
45
|
+
if len(digits) not in (11, 14):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
'document deve ser CPF (11 dígitos) ou CNPJ (14 dígitos)'
|
|
48
|
+
)
|
|
49
|
+
return digits
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _coerce_request(
|
|
53
|
+
request: Juridico360Request | dict,
|
|
54
|
+
) -> Juridico360Request:
|
|
55
|
+
if isinstance(request, Juridico360Request):
|
|
56
|
+
return request
|
|
57
|
+
parsed = Juridico360Request.from_dict(request)
|
|
58
|
+
if parsed is None:
|
|
59
|
+
raise ValueError('request inválido')
|
|
60
|
+
return parsed
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Juridico360Api(PositivaConsultasApi):
|
|
64
|
+
"""Consulta processual Positiva Juridico 360."""
|
|
65
|
+
|
|
66
|
+
def consultar(
|
|
67
|
+
self, request: Juridico360Request | dict
|
|
68
|
+
) -> Juridico360Response:
|
|
69
|
+
"""POST /api/client/provider/juridico360."""
|
|
70
|
+
logging.info('consulting Positiva Juridico 360...')
|
|
71
|
+
req = _coerce_request(request)
|
|
72
|
+
payload = {
|
|
73
|
+
'document': _require_document(req.document),
|
|
74
|
+
'product_code': _normalize_product_codes(req.product_code),
|
|
75
|
+
}
|
|
76
|
+
if req.options is not None:
|
|
77
|
+
options = req.options.to_dict() if isinstance(req.options, Juridico360Options) else dict(req.options)
|
|
78
|
+
if options:
|
|
79
|
+
if options.get('page_size') is not None and int(options['page_size']) > 100:
|
|
80
|
+
raise ValueError('options.page_size máximo é 100')
|
|
81
|
+
payload['options'] = options
|
|
82
|
+
|
|
83
|
+
url = UrlUtil().make_url(
|
|
84
|
+
self.base_url, ['client', 'provider', 'juridico360']
|
|
85
|
+
)
|
|
86
|
+
res = self.call_request(
|
|
87
|
+
http_method=HTTPMethod.POST,
|
|
88
|
+
request_url=url,
|
|
89
|
+
payload=payload,
|
|
90
|
+
)
|
|
91
|
+
parsed = Juridico360Response.from_dict(jsonpickle.decode(res))
|
|
92
|
+
if parsed is None:
|
|
93
|
+
raise Exception('Resposta inesperada de Juridico 360')
|
|
94
|
+
return parsed
|
|
95
|
+
|
|
96
|
+
def consultar_por_documento(
|
|
97
|
+
self,
|
|
98
|
+
document: str,
|
|
99
|
+
product_code: Sequence[str | Juridico360ProductCode],
|
|
100
|
+
*,
|
|
101
|
+
options: Juridico360Options | dict | None = None,
|
|
102
|
+
) -> Juridico360Response:
|
|
103
|
+
"""Atalho tipado para ``consultar``."""
|
|
104
|
+
opts = None
|
|
105
|
+
if isinstance(options, Juridico360Options):
|
|
106
|
+
opts = options
|
|
107
|
+
elif isinstance(options, dict):
|
|
108
|
+
opts = Juridico360Options.from_dict(options)
|
|
109
|
+
return self.consultar(
|
|
110
|
+
Juridico360Request(
|
|
111
|
+
document=document,
|
|
112
|
+
product_code=list(product_code),
|
|
113
|
+
options=opts,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def gerar_pdf(
|
|
118
|
+
self,
|
|
119
|
+
request: Juridico360Request | dict | None = None,
|
|
120
|
+
*,
|
|
121
|
+
document: str | None = None,
|
|
122
|
+
product_code: Sequence[str | Juridico360ProductCode] | None = None,
|
|
123
|
+
options: Juridico360Options | dict | None = None,
|
|
124
|
+
) -> Juridico360PdfResponse:
|
|
125
|
+
"""POST /api/client/provider/juridico360/pdf — retorna PDF em Base64."""
|
|
126
|
+
logging.info('generating Positiva Juridico 360 PDF...')
|
|
127
|
+
if request is not None:
|
|
128
|
+
req = _coerce_request(request)
|
|
129
|
+
else:
|
|
130
|
+
if document is None or product_code is None:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
'informe request ou (document + product_code) para gerar PDF'
|
|
133
|
+
)
|
|
134
|
+
opts = None
|
|
135
|
+
if isinstance(options, Juridico360Options):
|
|
136
|
+
opts = options
|
|
137
|
+
elif isinstance(options, dict):
|
|
138
|
+
opts = Juridico360Options.from_dict(options)
|
|
139
|
+
req = Juridico360Request(
|
|
140
|
+
document=document,
|
|
141
|
+
product_code=list(product_code),
|
|
142
|
+
options=opts,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
payload = {
|
|
146
|
+
'document': _require_document(req.document),
|
|
147
|
+
'product_code': _normalize_product_codes(req.product_code),
|
|
148
|
+
}
|
|
149
|
+
if req.options is not None:
|
|
150
|
+
options_dict = (
|
|
151
|
+
req.options.to_dict()
|
|
152
|
+
if isinstance(req.options, Juridico360Options)
|
|
153
|
+
else dict(req.options)
|
|
154
|
+
)
|
|
155
|
+
if options_dict:
|
|
156
|
+
payload['options'] = options_dict
|
|
157
|
+
|
|
158
|
+
url = UrlUtil().make_url(
|
|
159
|
+
self.base_url, ['client', 'provider', 'juridico360', 'pdf']
|
|
160
|
+
)
|
|
161
|
+
res = self.call_request(
|
|
162
|
+
http_method=HTTPMethod.POST,
|
|
163
|
+
request_url=url,
|
|
164
|
+
payload=payload,
|
|
165
|
+
)
|
|
166
|
+
parsed = Juridico360PdfResponse.from_dict(jsonpickle.decode(res))
|
|
167
|
+
if parsed is None:
|
|
168
|
+
raise Exception('Resposta inesperada de Juridico 360 PDF')
|
|
169
|
+
return parsed
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from positiva.consultas.dtos.auth import (
|
|
3
|
+
AvailableProduct,
|
|
4
|
+
ClientInfo,
|
|
5
|
+
ClientMeResponse,
|
|
6
|
+
LogoutResponse,
|
|
7
|
+
ProductFeature,
|
|
8
|
+
ProviderProduct,
|
|
9
|
+
TokenResponse,
|
|
10
|
+
)
|
|
11
|
+
from positiva.consultas.dtos.enums import DocumentType, Juridico360ProductCode, MpKind
|
|
12
|
+
from positiva.consultas.dtos.juridico360 import (
|
|
13
|
+
Juridico360Options,
|
|
14
|
+
Juridico360PdfResponse,
|
|
15
|
+
Juridico360ProductData,
|
|
16
|
+
Juridico360ProductResult,
|
|
17
|
+
Juridico360Request,
|
|
18
|
+
Juridico360Response,
|
|
19
|
+
PenaPrevista,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'AvailableProduct',
|
|
24
|
+
'ClientInfo',
|
|
25
|
+
'ClientMeResponse',
|
|
26
|
+
'DocumentType',
|
|
27
|
+
'Juridico360Options',
|
|
28
|
+
'Juridico360PdfResponse',
|
|
29
|
+
'Juridico360ProductCode',
|
|
30
|
+
'Juridico360ProductData',
|
|
31
|
+
'Juridico360ProductResult',
|
|
32
|
+
'Juridico360Request',
|
|
33
|
+
'Juridico360Response',
|
|
34
|
+
'LogoutResponse',
|
|
35
|
+
'MpKind',
|
|
36
|
+
'PenaPrevista',
|
|
37
|
+
'ProductFeature',
|
|
38
|
+
'ProviderProduct',
|
|
39
|
+
'TokenResponse',
|
|
40
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""DTOs de autenticacao (/client/token, /client/me, /client/refresh, /client/logout)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from positiva.consultas.dtos.serde import as_wire_dict, from_dataclass, omit_none, to_wire
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ProductFeature:
|
|
12
|
+
code: str | None = None
|
|
13
|
+
name: str | None = None
|
|
14
|
+
|
|
15
|
+
def to_dict(self) -> dict:
|
|
16
|
+
return as_wire_dict(self)
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_dict(cls, data: dict | ProductFeature | None) -> ProductFeature | None:
|
|
20
|
+
return from_dataclass(cls, data)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class AvailableProduct:
|
|
25
|
+
code: str | None = None
|
|
26
|
+
name: str | None = None
|
|
27
|
+
has_additionals: bool | None = None
|
|
28
|
+
features: list[ProductFeature] | None = None
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict:
|
|
31
|
+
return omit_none({
|
|
32
|
+
'code': self.code,
|
|
33
|
+
'name': self.name,
|
|
34
|
+
'has_additionals': self.has_additionals,
|
|
35
|
+
'features': to_wire(self.features),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, data: dict | AvailableProduct | None) -> AvailableProduct | None:
|
|
40
|
+
if data is None:
|
|
41
|
+
return None
|
|
42
|
+
if isinstance(data, cls):
|
|
43
|
+
return data
|
|
44
|
+
if not isinstance(data, dict):
|
|
45
|
+
raise TypeError(f'AvailableProduct.from_dict espera dict, recebeu {type(data)!r}')
|
|
46
|
+
features = data.get('features')
|
|
47
|
+
return cls(
|
|
48
|
+
code=data.get('code'),
|
|
49
|
+
name=data.get('name'),
|
|
50
|
+
has_additionals=data.get('has_additionals'),
|
|
51
|
+
features=[ProductFeature.from_dict(f) for f in features] if features else None,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ProviderProduct:
|
|
57
|
+
"""Produto de provider (ex.: Juridico 360) exposto em ``provider_products``."""
|
|
58
|
+
|
|
59
|
+
code: str | None = None
|
|
60
|
+
name: str | None = None
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict:
|
|
63
|
+
return as_wire_dict(self)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: dict | ProviderProduct | None) -> ProviderProduct | None:
|
|
67
|
+
return from_dataclass(cls, data)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ClientInfo:
|
|
72
|
+
name: str | None = None
|
|
73
|
+
email: str | None = None
|
|
74
|
+
cnpj: str | None = None
|
|
75
|
+
phone: str | None = None
|
|
76
|
+
active: bool | None = None
|
|
77
|
+
|
|
78
|
+
def to_dict(self) -> dict:
|
|
79
|
+
return as_wire_dict(self)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_dict(cls, data: dict | ClientInfo | None) -> ClientInfo | None:
|
|
83
|
+
return from_dataclass(cls, data)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class TokenResponse:
|
|
88
|
+
access_token: str | None = None
|
|
89
|
+
token_type: str | None = None
|
|
90
|
+
expires_in: int | None = None
|
|
91
|
+
client: ClientInfo | None = None
|
|
92
|
+
products: list[AvailableProduct] | None = None
|
|
93
|
+
# Ambiente por provider, ex.: {"juridico360": "homolog"|"production"}
|
|
94
|
+
provider_environments: dict[str, str] | None = None
|
|
95
|
+
# Produtos contratados por provider, ex.: {"juridico360": [{"code","name"}, ...]}
|
|
96
|
+
provider_products: dict[str, list[ProviderProduct]] | None = None
|
|
97
|
+
|
|
98
|
+
def to_dict(self) -> dict:
|
|
99
|
+
return omit_none({
|
|
100
|
+
'access_token': self.access_token,
|
|
101
|
+
'token_type': self.token_type,
|
|
102
|
+
'expires_in': self.expires_in,
|
|
103
|
+
'client': to_wire(self.client),
|
|
104
|
+
'products': to_wire(self.products),
|
|
105
|
+
'provider_environments': self.provider_environments,
|
|
106
|
+
'provider_products': {
|
|
107
|
+
key: to_wire(value)
|
|
108
|
+
for key, value in (self.provider_products or {}).items()
|
|
109
|
+
}
|
|
110
|
+
if self.provider_products is not None
|
|
111
|
+
else None,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def from_dict(cls, data: dict | TokenResponse | None) -> TokenResponse | None:
|
|
116
|
+
if data is None:
|
|
117
|
+
return None
|
|
118
|
+
if isinstance(data, cls):
|
|
119
|
+
return data
|
|
120
|
+
if not isinstance(data, dict):
|
|
121
|
+
raise TypeError(f'TokenResponse.from_dict espera dict, recebeu {type(data)!r}')
|
|
122
|
+
products = data.get('products')
|
|
123
|
+
raw_provider_products = data.get('provider_products')
|
|
124
|
+
provider_products = None
|
|
125
|
+
if isinstance(raw_provider_products, dict):
|
|
126
|
+
provider_products = {
|
|
127
|
+
key: [
|
|
128
|
+
ProviderProduct.from_dict(item)
|
|
129
|
+
for item in (value or [])
|
|
130
|
+
if isinstance(item, dict)
|
|
131
|
+
]
|
|
132
|
+
for key, value in raw_provider_products.items()
|
|
133
|
+
}
|
|
134
|
+
environments = data.get('provider_environments')
|
|
135
|
+
return cls(
|
|
136
|
+
access_token=data.get('access_token'),
|
|
137
|
+
token_type=data.get('token_type'),
|
|
138
|
+
expires_in=data.get('expires_in'),
|
|
139
|
+
client=ClientInfo.from_dict(data.get('client')),
|
|
140
|
+
products=[AvailableProduct.from_dict(p) for p in products] if products else None,
|
|
141
|
+
provider_environments=dict(environments) if isinstance(environments, dict) else None,
|
|
142
|
+
provider_products=provider_products,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class ClientMeResponse:
|
|
148
|
+
client: ClientInfo | None = None
|
|
149
|
+
|
|
150
|
+
def to_dict(self) -> dict:
|
|
151
|
+
return omit_none({'client': to_wire(self.client)})
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_dict(cls, data: dict | ClientMeResponse | None) -> ClientMeResponse | None:
|
|
155
|
+
if data is None:
|
|
156
|
+
return None
|
|
157
|
+
if isinstance(data, cls):
|
|
158
|
+
return data
|
|
159
|
+
if not isinstance(data, dict):
|
|
160
|
+
raise TypeError(f'ClientMeResponse.from_dict espera dict, recebeu {type(data)!r}')
|
|
161
|
+
return cls(client=ClientInfo.from_dict(data.get('client')))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass
|
|
165
|
+
class LogoutResponse:
|
|
166
|
+
message: str | None = None
|
|
167
|
+
|
|
168
|
+
def to_dict(self) -> dict:
|
|
169
|
+
return as_wire_dict(self)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def from_dict(cls, data: dict | LogoutResponse | None) -> LogoutResponse | None:
|
|
173
|
+
return from_dataclass(cls, data)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Enums de domínio da API Positiva Consultas."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Juridico360ProductCode(StrEnum):
|
|
9
|
+
"""Codigos de produto do endpoint Juridico 360."""
|
|
10
|
+
|
|
11
|
+
CRIMINAL = 'CRIMINAL'
|
|
12
|
+
CIVIL = 'CIVIL'
|
|
13
|
+
TRABALHISTA = 'TRABALHISTA'
|
|
14
|
+
MP = 'MP'
|
|
15
|
+
BNMP = 'BNMP'
|
|
16
|
+
EMPREGADOR_IRREGULAR = 'EMPREGADOR_IRREGULAR'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MpKind(StrEnum):
|
|
20
|
+
"""Filtro opcional ``options.kind`` para produtos MP/BNMP."""
|
|
21
|
+
|
|
22
|
+
CRIMINAL = 'CRIMINAL'
|
|
23
|
+
CIVIL = 'CIVIL'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DocumentType(StrEnum):
|
|
27
|
+
CPF = 'CPF'
|
|
28
|
+
CNPJ = 'CNPJ'
|