csc-cia-stne 0.0.13__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.
@@ -0,0 +1,423 @@
1
+ import requests
2
+ import jwt
3
+ from datetime import datetime, timedelta
4
+ import time
5
+ #import subprocess
6
+ import json
7
+ #from modules.pdf import gerar_pdf_extrato
8
+ #import csv
9
+ #import io
10
+ #import sys
11
+ #import logging
12
+ from pydantic import BaseModel, StrictStr, StrictInt, ValidationError, field_validator, FieldValidationInfo
13
+ from typing import Literal
14
+
15
+ # Validações dos inputs
16
+ class InitParamsValidator(BaseModel):
17
+ client_id: str
18
+ user_agent: str
19
+ private_key: str
20
+ ambiente: Literal["prd", "sdx"] # Aceita apenas "prd" ou "sdx"
21
+
22
+ # Validação para garantir que cada parâmetro é uma string não vazia
23
+ @field_validator('client_id', 'user_agent', 'private_key')
24
+ def check_non_empty_string(cls, value, info):
25
+ if not isinstance(value, str) or not value.strip():
26
+
27
+ raise ValueError(f"O parâmetro '{info.field_name}' deve ser uma string não vazia.")
28
+
29
+ return value
30
+
31
+ class DocumentoValidator(BaseModel):
32
+
33
+ documento: StrictStr | StrictInt # Aceita apenas str ou int
34
+
35
+ # Valida se 'documento' não é vazio
36
+ @field_validator('documento')
37
+ def documento_nao_vazio(cls, value: StrictStr | StrictInt, info: FieldValidationInfo):
38
+
39
+ if isinstance(value, str) and not value.strip():
40
+
41
+ raise ValueError("O parâmetro 'documento' não pode ser uma string vazia.")
42
+
43
+ return value
44
+
45
+ class AccountIDValidator(BaseModel):
46
+
47
+ account_id: StrictStr # Aceita apenas str
48
+
49
+ # Valida se 'client_id' não é vazio
50
+ @field_validator('account_id')
51
+ def account_id_nao_vazio(cls, value: StrictStr, info: FieldValidationInfo):
52
+
53
+ if isinstance(value, str) and not value.strip():
54
+
55
+ raise ValueError("O parâmetro 'account_id' não pode ser uma string vazia.")
56
+
57
+ return value
58
+
59
+ class ExtratoParamsValidator(BaseModel):
60
+
61
+ account_id: str
62
+ data_inicio: datetime
63
+ data_fim: datetime
64
+ async_mode: bool
65
+
66
+ # Valida se 'client_id' não é vazio
67
+ @field_validator('account_id')
68
+ def account_id_nao_vazio(cls, value: StrictStr, info: FieldValidationInfo):
69
+
70
+ if isinstance(value, str) and not value.strip():
71
+
72
+ raise ValueError("O parâmetro 'account_id' não pode ser uma string vazia.")
73
+
74
+ return value
75
+
76
+ # Valida se 'data_inicio' está no formato datetime
77
+ @field_validator('data_inicio', 'data_fim')
78
+ def check_datetime_format(cls, value, info: FieldValidationInfo):
79
+
80
+ if not isinstance(value, datetime):
81
+
82
+ raise ValueError(f"O parâmetro '{info.field_name}' deve estar no formato datetime.")
83
+
84
+ return value
85
+
86
+ # Valida se 'data_fim' é posterior a 'data_inicio'
87
+ @field_validator('data_fim')
88
+ def check_data_fim_posterior(cls, data_fim, values):
89
+ data_inicio = values.get('data_inicio')
90
+
91
+ if data_inicio and data_fim and data_fim <= data_inicio:
92
+
93
+ raise ValueError("O parâmetro 'data_fim' deve ser posterior a data_inicio.")
94
+
95
+ return data_fim
96
+
97
+ # Valida se 'async_mode' é um valor booleano
98
+ @field_validator('async_mode')
99
+ def check_async_mode(cls, async_mode):
100
+
101
+ if not isinstance(async_mode, bool):
102
+
103
+ raise ValueError("O parâmetro 'async_mode' deve ser um valor booleano.")
104
+
105
+ return async_mode
106
+
107
+ class ReceiptIDValidator(BaseModel):
108
+
109
+ receipt_id: StrictStr # Aceita apenas str
110
+
111
+ # Valida se 'receipt_id' não é vazio
112
+ @field_validator('receipt_id')
113
+ def receipt_id_nao_vazio(cls, value: StrictStr, info: FieldValidationInfo):
114
+
115
+ if isinstance(value, str) and not value.strip():
116
+
117
+ raise ValueError("O parâmetro 'receipt_id' não pode ser uma string vazia.")
118
+
119
+ return value
120
+
121
+ class StoneAdmin:
122
+
123
+ def __init__(self, client_id:str, user_agent:str, private_key:str, ambiente:str):
124
+
125
+ try:
126
+
127
+ InitParamsValidator(client_id=client_id, user_agent=user_agent, private_key=private_key, ambiente=ambiente)
128
+
129
+ except ValidationError as e:
130
+
131
+ raise ValueError("Erro na validação dos dados de input da inicialização da instância:", e.errors())
132
+
133
+ # Produção
134
+ if ambiente == 'prd':
135
+
136
+ self.base_url = 'https://api.openbank.stone.com.br/resources/v1'
137
+ self.base_auth_url = 'https://accounts.openbank.stone.com.br'
138
+
139
+ # Sandbox
140
+ else:
141
+
142
+ self.base_url = 'https://sandbox-api.openbank.stone.com.br/resources/v1'
143
+ self.base_auth_url = 'https://sandbox-accounts.openbank.stone.com.br'
144
+
145
+ self.client_id = client_id
146
+ self.user_agent = user_agent
147
+ self.private_key = private_key
148
+ self.token = self.__get_token()
149
+ self.authenticated_header = {
150
+ 'Authorization' : f"Bearer {self.token}",
151
+ 'User-Agent': self.user_agent,
152
+ #'Client-ID': self.client_id
153
+ }
154
+
155
+ def __get_token(self):
156
+ base_url = f'{self.base_auth_url}/auth/realms/stone_bank'
157
+ auth_url = f'{base_url}/protocol/openid-connect/token'
158
+ payload = {
159
+ 'exp': int(time.time()) + 3600,
160
+ 'nbf': int(time.time()),
161
+ 'aud': base_url,
162
+ 'realm': 'stone_bank',
163
+ 'sub': self.client_id,
164
+ 'clientId': self.client_id,
165
+ 'jti': str(time.time()),
166
+ 'iat': int(time.time()),
167
+ 'iss': self.client_id
168
+ }
169
+
170
+ token = jwt.encode(payload, self.private_key, algorithm='RS256')
171
+
172
+ headers = {
173
+ 'Content-Type': 'application/x-www-form-urlencoded',
174
+ 'User-Agent': self.user_agent
175
+ }
176
+
177
+ token_payload = {
178
+ 'client_id': self.client_id,
179
+ 'grant_type': 'client_credentials',
180
+ 'client_assertion': token,
181
+ 'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
182
+ }
183
+
184
+ response = requests.post(auth_url, data=token_payload, headers=headers, timeout=60)
185
+ #print(response.json())
186
+ return response.json()['access_token']
187
+
188
+ def renew_authorization(self):
189
+ self.token = self.__get_token()
190
+ self.authenticated_header = {
191
+ 'Authorization' : f"Bearer {self.token}",
192
+ 'User-Agent': self.user_agent,
193
+ #'Client-ID': self.client_id
194
+ }
195
+
196
+ def verificar_cliente(self,documento:str)->dict:
197
+
198
+ try:
199
+
200
+ DocumentoValidator(documento=documento)
201
+
202
+ except ValidationError as e:
203
+
204
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
205
+
206
+ params_conta_ativa = {'owner_document': documento}
207
+ params_conta_inativa = {'owner_document': documento,'status':'closed'}
208
+
209
+ try:
210
+
211
+ # Verificando se existe cliente com esse documento, com a conta ativa
212
+ response = requests.get(f"{self.base_url}/accounts", params=params_conta_ativa, headers=self.authenticated_header, timeout=60)
213
+
214
+ # Retorno esperado pela API Stone Admin - consulta cliente ativo
215
+ if response.status_code == 200:
216
+
217
+ # Não existe cliente com esse documento e com a conta ativa
218
+ if len(response.json()) == 0:
219
+
220
+ # Verificando se existe cliente com esse documento, com a conta inativa
221
+ encontrado = False
222
+ response = requests.get(f"{self.base_url}/accounts", params=params_conta_inativa, headers=self.authenticated_header, timeout=60)
223
+
224
+ # Retorno esperado pela API Stone Admin - consulta cliente inativo
225
+ if response.status_code == 200:
226
+
227
+ resposta = response.json()
228
+
229
+ # Existe cliente com esse documento, mas com a conta inativa
230
+ if len(resposta) != 0:
231
+
232
+ encontrado = True
233
+
234
+ # Algum erro na API Stone Admin - retorna erro
235
+ else:
236
+
237
+ return False, ValueError(response.json())
238
+
239
+ # Cliente econtrado e com a conta ativa
240
+ else:
241
+
242
+ encontrado = True
243
+ resposta = response.json()
244
+
245
+ retorno = []
246
+
247
+ # Monta JSON , pode ter mais de uma conta
248
+ for registro in resposta:
249
+
250
+ retorno_item = {}
251
+ account_code = registro["account_code"]
252
+ account_id = registro["id"]
253
+ owner_id = registro["owner_id"]
254
+ closed_at = registro["closed_at"]
255
+ created_at = registro["created_at"]
256
+
257
+ # Status atual da conta
258
+ if closed_at is None:
259
+
260
+ registro["conta_ativa"] = True
261
+
262
+ else:
263
+
264
+ registro["conta_ativa"] = False
265
+
266
+ retorno.append(registro)
267
+
268
+ retorno_json = {
269
+ "success":True,
270
+ "status_code": response.status_code,
271
+ "error": None,
272
+ "encontrado": encontrado,
273
+ "detalhes": retorno
274
+ }
275
+ return retorno_json
276
+
277
+ # Retorno inesperado pela API Stone Admin - consulta cliente ativo, retorna erro
278
+ else:
279
+
280
+ retorno_json = {
281
+ "success":False,
282
+ "status_code": response.status_code,
283
+ "error": ValueError(response.json())
284
+ }
285
+ return retorno_json
286
+
287
+ # Erro inesperado como a requisição à API Stone Admin - consulta cliente ativo, retorna erro
288
+ except Exception as e:
289
+
290
+ retorno_json = {
291
+ "success":False,
292
+ "status_code": response.status_code,
293
+ "error": e
294
+ }
295
+ return retorno_json
296
+
297
+ def balance_da_conta(self,account_id:str):
298
+ try:
299
+
300
+ AccountIDValidator(account_id=account_id)
301
+
302
+ except ValidationError as e:
303
+
304
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
305
+
306
+ # Captura o balance da conta
307
+ response = requests.get(f"{self.base_url}/accounts/{account_id}", headers=self.authenticated_header)
308
+ return response
309
+
310
+ def detalhar_titular_cpf(self,documento:str):
311
+
312
+ try:
313
+
314
+ DocumentoValidator(documento=documento)
315
+
316
+ except ValidationError as e:
317
+
318
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
319
+
320
+ # Detalha o titular
321
+
322
+ # Verificar na rota /users (CPF)
323
+ filtro = {'document': documento}
324
+ param = {
325
+ 'filter': json.dumps(filtro) # Transforma o dicionário em uma string JSON
326
+ }
327
+ response = requests.get(f"{self.base_url}/users", params=param, headers=self.authenticated_header)
328
+ return response
329
+
330
+ def detalhar_titular_cnpj(self,documento:str):
331
+
332
+ try:
333
+
334
+ DocumentoValidator(documento=documento)
335
+
336
+ except ValidationError as e:
337
+
338
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
339
+
340
+ # Verificar na rota /organizations (CNPJ)
341
+ filtro = {'document': documento}
342
+ param = {
343
+ 'filter': json.dumps(filtro) # Transforma o dicionário em uma string JSON
344
+ }
345
+ response = requests.get(f"{self.base_url}/organizations", params=param, headers=self.authenticated_header)
346
+ return response
347
+
348
+ def extrair_extrato(self,account_id:str,data_inicio:datetime,data_fim:datetime,async_mode:bool=False):
349
+
350
+ try:
351
+
352
+ ExtratoParamsValidator(account_id=account_id, data_inicio=data_inicio, data_fim=data_fim, async_mode=async_mode)
353
+
354
+ except ValidationError as e:
355
+
356
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
357
+
358
+ # Validação do async_mode
359
+ if not isinstance(async_mode, bool):
360
+
361
+ raise ValueError("async_mode deve ser um valor booleano.")
362
+
363
+
364
+ data_inicio = data_inicio.strftime('%Y-%m-%d')
365
+ data_fim = data_fim + timedelta(days=1)
366
+ data_fim = data_fim.strftime('%Y-%m-%d')
367
+
368
+ try:
369
+
370
+ if async_mode:
371
+
372
+ response = requests.get(f"{self.base_url}/exports/accounts/{account_id}/statement?start_date={data_inicio}&end_date={data_fim}&format=pdf&async=true", headers=self.authenticated_header, timeout=60)
373
+
374
+ else:
375
+
376
+ response = requests.get(f"{self.base_url}/exports/accounts/{account_id}/statement?start_date={data_inicio}&end_date={data_fim}&format=pdf&async=false", headers=self.authenticated_header, timeout=120)
377
+
378
+ if response.status_code == 200 and not async_mode:
379
+
380
+ return {"success":True, "status_code": response.status_code, "error": None, "pdf_content": response.content}
381
+
382
+ elif response.status_code == 202 and async_mode:
383
+
384
+ return {"success":True, "status_code": response.status_code, "error": None, "receipt_id":response.json()["id"]}
385
+
386
+ else:
387
+
388
+ return {"success":False, "status_code": response.status_code, "error": str(response.text), "pdf_content": None}
389
+
390
+ except Exception as e:
391
+
392
+ return {"success": False, "status_code": response.status_code, "error": e, "pdf_content": None}
393
+
394
+ def download_receipt(self,receipt_id:str):
395
+
396
+ try:
397
+
398
+ ReceiptIDValidator(receipt_id=receipt_id)
399
+
400
+ except ValidationError as e:
401
+
402
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
403
+
404
+ try:
405
+
406
+ response = requests.get(f"https://api.openbank.stone.com.br/resources/v1/exports/receipt_requests/download/{receipt_id}", headers=self.authenticated_header, timeout=120)
407
+
408
+ if response.status_code == 200:
409
+
410
+ # Decodificando o conteúdo usando UTF-8
411
+ #decoded_content = response.content.decode('utf-8')
412
+ print("header:",response.headers['Content-Type'])
413
+ print(f"type do content: {type(response.content)}")
414
+ return {'result':True, 'status_code': response.status_code, 'error': None, 'pdf_content':response.content}
415
+ #return {'result':True, 'status_code': response.status_code, 'error': None, 'pdf_content':decoded_content}
416
+
417
+ else:
418
+
419
+ return {'result': False, 'status_code': response.status_code, 'error': response.json(), 'pdf_content': None}
420
+
421
+ except Exception as e:
422
+
423
+ return {'result': False, 'status_code': None, 'error': e, 'pdf_content': None}
@@ -0,0 +1,72 @@
1
+ import base64
2
+
3
+ class Util():
4
+ """Classe Util com necessidades de rotina
5
+ """
6
+
7
+ def b64decode(self,b64_string:str=None)->str:
8
+ """Faz o decode de uma string em base64
9
+
10
+ Args:
11
+ b64_string (str): string em base64
12
+
13
+ Returns:
14
+ str: string em texto
15
+ """
16
+
17
+ if b64_string is None or b64_string == "":
18
+
19
+ raise ValueError("Uma string Base64 precisa ser informada com o parâmetro 'b64_string'")
20
+
21
+ try:
22
+
23
+ b64_decode_output = base64.b64decode(b64_string).decode('utf-8')
24
+
25
+ except:
26
+
27
+ raise TypeError("A string informada não está em formato Base64")
28
+
29
+ return b64_decode_output
30
+
31
+ def b64encode(self,string_to_convert:str=None)->base64:
32
+ """Faz o encode de uma string para base64
33
+
34
+ Args:
35
+ string_to_convert (str): string para converter em base64
36
+
37
+ Returns:
38
+ base64: string convertida para base64
39
+ """
40
+
41
+ if string_to_convert is None or string_to_convert == "":
42
+
43
+ raise ValueError("Uma string precisa ser informada com o parâmetro 'string_to_convert'")
44
+
45
+ try:
46
+
47
+ b64_encode_output = base64.b64encode(str(string_to_convert).encode('utf-8')).decode('utf-8')
48
+
49
+ except Exception as e:
50
+
51
+ raise TypeError(f"Erro ao converter a string para Base64: {e}")
52
+
53
+ return b64_encode_output
54
+
55
+ def convert_query_result_to_json(self,query_result:list)->list:
56
+ """Converte o resultado de uma query do BigQuery em uma lista de dicionários JSON
57
+
58
+ Args:
59
+ query_result (list): resultado da query do BigQuery (query.result)
60
+
61
+ Returns:
62
+ list: lista de dicionários JSON
63
+ """
64
+
65
+ try:
66
+
67
+ #return [dict(row) for row in query_result]
68
+ return [dict(row._asdict()) for row in query_result]
69
+
70
+ except Exception as e:
71
+
72
+ raise e
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 CSC-CIA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.1
2
+ Name: csc_cia_stne
3
+ Version: 0.0.13
4
+ Summary: Biblioteca do time CSC-CIA utilizada no desenvolvimento de RPAs
5
+ License: MIT
6
+ Keywords: karavela,csc,cia,stone,rpa
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENCE
9
+ Requires-Dist: python-json-logger
10
+ Requires-Dist: rich
11
+ Requires-Dist: requests
12
+ Requires-Dist: pydantic
13
+ Requires-Dist: zeep
14
+ Requires-Dist: google-cloud-bigquery
15
+ Requires-Dist: google-cloud-storage
16
+ Requires-Dist: google-auth-oauthlib
17
+ Requires-Dist: google-auth-httplib2
18
+
19
+ Essa biblioteca é desenvolvida e atualizada pelo time **CSC-CIA** da **Stone**
20
+
21
+ Utilizada no desenvolvimento dos RPAs, possui classes que são utilizadas com frequência
22
+
23
+ O intuito é não precisar desenvolver, novamente, funcionalidades que são utilizadas com frequência
24
+
25
+ Contato: **cia@stone.com.br**
@@ -0,0 +1,15 @@
1
+ csc_cia_stne/__init__.py,sha256=VYD8Lj4LgMqM5d3f2dgS5oqThEo9czi-wIDPaw3b3Ec,261
2
+ csc_cia_stne/bc_correios.py,sha256=ANsvLyL7wdkM0MvjjBHB2Ih4eyTcyWgt5IqiK0Rv89E,23014
3
+ csc_cia_stne/bc_sta.py,sha256=I9N29wjTbd4ZmoM2yIW-xp3X-dMENZdSb0JhapfCegY,10988
4
+ csc_cia_stne/cia_logging.py,sha256=GbxpcjtF_7tiRez2awIKZJXdo5CvM0n_mo1RKpfvAnU,5100
5
+ csc_cia_stne/gcp_bigquery.py,sha256=GBI7BDyranwr4IQGzLbbZRgD2Km6tlkqqVCI3R89-bE,5651
6
+ csc_cia_stne/karavela.py,sha256=Q7MbQXXz_jtrLHM7QeenbSzcro07EpoFk4lKglivJ_I,3564
7
+ csc_cia_stne/logger.py,sha256=cadiXHeOv8OaB4o8byznsks4VDTfR0cCccLIWxn2R48,4612
8
+ csc_cia_stne/servicenow.py,sha256=d7HysnJU1Q22hy0e3cIW90S8_5IsQGETiJndnGOI_i8,23309
9
+ csc_cia_stne/stne_admin.py,sha256=G5ozXt18VjKL2BHtROQk4GnfVY1xM14RXSQ-rra_D54,15487
10
+ csc_cia_stne/utilitarios.py,sha256=LU8YrELiRIW4UxqULjiUPYmgbEx79NGenK5QKRdSh_E,2050
11
+ csc_cia_stne-0.0.13.dist-info/LICENCE,sha256=LPGMtgKki2C3KEZP7hDhA1HBrlq5JCHkIeStUCLEMx4,1073
12
+ csc_cia_stne-0.0.13.dist-info/METADATA,sha256=2_oSdzjh5F7344uGFXIxqRxm2xHmCN-TclJVjeurYOY,816
13
+ csc_cia_stne-0.0.13.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
14
+ csc_cia_stne-0.0.13.dist-info/top_level.txt,sha256=ldo7GVv3tQx5KJvwBzdZzzQmjPys2NDVVn1rv0BOF2Q,13
15
+ csc_cia_stne-0.0.13.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ csc_cia_stne