csc-cia-stne 0.0.56__tar.gz → 0.1.49__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.
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/PKG-INFO +13 -2
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/pyproject.toml +12 -2
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/__init__.py +11 -1
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/bc_correios.py +22 -16
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/bc_sta.py +288 -6
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/email.py +15 -6
- csc_cia_stne-0.1.49/src/csc_cia_stne/ftp.py +333 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/gcp_bigquery.py +4 -11
- csc_cia_stne-0.1.49/src/csc_cia_stne/gcp_bucket.py +319 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/gcp_document_ai.py +100 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/google_drive.py +990 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/jerry.py +694 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/logger_rich.py +10 -119
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/servicenow.py +362 -209
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/slack.py +63 -23
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/stne_admin.py +131 -20
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/__init__.py +17 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/__init__.py +11 -1
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/functions/func_datetime.py +8 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/functions/func_delete.py +96 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/functions/func_get_secret.py +77 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/functions/func_pdf_extract.py +115 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/functions/func_validate_json.py +49 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/validations/GcpBigQueryValidator.py +6 -11
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/validations/GoogleDriveValidator.py +7 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/validations/ftp.py +109 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/validations/gcp_bucket.py +170 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/validations/waccess.py +215 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/validations/web_validator.py +10 -1
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/web_screen/__init__.py +2 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/web_screen/web_screen_abstract.py +84 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/web_screen/web_screen_botcity.py +288 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/utilitarios/web_screen/web_screen_selenium.py +391 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/wacess.py +836 -0
- csc_cia_stne-0.1.49/src/csc_cia_stne/web.py +82 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne.egg-info/PKG-INFO +13 -2
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne.egg-info/SOURCES.txt +17 -1
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne.egg-info/requires.txt +10 -0
- csc_cia_stne-0.0.56/src/csc_cia_stne/google_drive.py +0 -268
- csc_cia_stne-0.0.56/src/csc_cia_stne/utilitarios/__init__.py +0 -10
- csc_cia_stne-0.0.56/src/csc_cia_stne/utilitarios/functions/func_get_secret.py +0 -67
- csc_cia_stne-0.0.56/src/csc_cia_stne/web.py +0 -513
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/LICENCE +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/README.md +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/README_PYPI.md +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/setup.cfg +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/karavela.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/logger_json.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/provio.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/func_b64.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/func_converters.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/func_recriar_pastas.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/func_settings.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/functions/func_titulo.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/validations/ServiceNowValidator.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne/utilitarios/validations/__init__.py +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne.egg-info/dependency_links.txt +0 -0
- {csc_cia_stne-0.0.56 → csc_cia_stne-0.1.49}/src/csc_cia_stne.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: csc_cia_stne
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.49
|
|
4
4
|
Summary: Biblioteca do time CSC-CIA utilizada no desenvolvimento de RPAs
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: karavela,csc,cia,stone,rpa,botcity,stne
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENCE
|
|
9
9
|
Requires-Dist: setuptools
|
|
10
|
+
Requires-Dist: tzdata
|
|
10
11
|
Requires-Dist: python-json-logger
|
|
11
12
|
Requires-Dist: rich
|
|
12
13
|
Requires-Dist: requests
|
|
@@ -15,17 +16,27 @@ Requires-Dist: pydantic_settings
|
|
|
15
16
|
Requires-Dist: zeep
|
|
16
17
|
Requires-Dist: google-cloud-bigquery
|
|
17
18
|
Requires-Dist: google-cloud-storage
|
|
19
|
+
Requires-Dist: google-cloud-bigquery-storage
|
|
20
|
+
Requires-Dist: google-auth
|
|
18
21
|
Requires-Dist: google-auth-oauthlib
|
|
19
22
|
Requires-Dist: google-auth-httplib2
|
|
20
23
|
Requires-Dist: google-api-python-client
|
|
24
|
+
Requires-Dist: google-cloud-documentai
|
|
21
25
|
Requires-Dist: pyjwt
|
|
22
26
|
Requires-Dist: PyYAML
|
|
23
27
|
Requires-Dist: python-dotenv
|
|
24
28
|
Requires-Dist: slack_sdk
|
|
25
29
|
Requires-Dist: webdriver-manager
|
|
30
|
+
Requires-Dist: botcity-framework-core
|
|
26
31
|
Requires-Dist: botcity-framework-web
|
|
27
32
|
Requires-Dist: email-validator
|
|
28
33
|
Requires-Dist: botcity-maestro-sdk
|
|
34
|
+
Requires-Dist: psutil
|
|
35
|
+
Requires-Dist: cryptography
|
|
36
|
+
Requires-Dist: PyPDF2
|
|
37
|
+
Requires-Dist: openai
|
|
38
|
+
Requires-Dist: pycurl
|
|
39
|
+
Dynamic: license-file
|
|
29
40
|
|
|
30
41
|
Essa biblioteca é desenvolvida e atualizada pelo time **CSC-CIA** da **Stone**
|
|
31
42
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "csc_cia_stne"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.1.49"
|
|
8
8
|
license = { text = "MIT" }
|
|
9
9
|
description = "Biblioteca do time CSC-CIA utilizada no desenvolvimento de RPAs"
|
|
10
10
|
keywords = ["karavela", "csc", "cia", "stone", "rpa", "botcity", "stne"]
|
|
@@ -13,6 +13,7 @@ readme = "README_PYPI.md" # Referência ao arquivo README
|
|
|
13
13
|
# Dependências
|
|
14
14
|
dependencies = [
|
|
15
15
|
"setuptools",
|
|
16
|
+
"tzdata",
|
|
16
17
|
"python-json-logger",
|
|
17
18
|
"rich",
|
|
18
19
|
"requests",
|
|
@@ -21,15 +22,24 @@ dependencies = [
|
|
|
21
22
|
"zeep",
|
|
22
23
|
"google-cloud-bigquery",
|
|
23
24
|
"google-cloud-storage",
|
|
25
|
+
"google-cloud-bigquery-storage",
|
|
26
|
+
"google-auth",
|
|
24
27
|
"google-auth-oauthlib",
|
|
25
28
|
"google-auth-httplib2",
|
|
26
29
|
"google-api-python-client",
|
|
30
|
+
"google-cloud-documentai",
|
|
27
31
|
"pyjwt",
|
|
28
32
|
"PyYAML",
|
|
29
33
|
"python-dotenv",
|
|
30
34
|
"slack_sdk",
|
|
31
35
|
"webdriver-manager",
|
|
36
|
+
"botcity-framework-core",
|
|
32
37
|
"botcity-framework-web",
|
|
33
38
|
"email-validator",
|
|
34
|
-
"botcity-maestro-sdk"
|
|
39
|
+
"botcity-maestro-sdk",
|
|
40
|
+
"psutil",
|
|
41
|
+
"cryptography",
|
|
42
|
+
"PyPDF2",
|
|
43
|
+
"openai",
|
|
44
|
+
"pycurl"
|
|
35
45
|
]
|
|
@@ -13,6 +13,11 @@ from .provio import Provio
|
|
|
13
13
|
from .google_drive import GoogleDrive
|
|
14
14
|
from .slack import Slack
|
|
15
15
|
from .web import web_screen
|
|
16
|
+
from .wacess import Waccess
|
|
17
|
+
from .gcp_bucket import GCPBucket
|
|
18
|
+
from .ftp import FTP
|
|
19
|
+
from .gcp_document_ai import GCPDocumentAIClient
|
|
20
|
+
from .jerry import JerryClient
|
|
16
21
|
|
|
17
22
|
# Define os itens disponíveis para importação
|
|
18
23
|
__all__ = [
|
|
@@ -27,8 +32,13 @@ __all__ = [
|
|
|
27
32
|
"Provio",
|
|
28
33
|
"Email",
|
|
29
34
|
"GoogleDrive",
|
|
35
|
+
"GCPDocumentAIClient"
|
|
30
36
|
"Slack",
|
|
31
|
-
"web_screen"
|
|
37
|
+
"web_screen",
|
|
38
|
+
"Waccess",
|
|
39
|
+
"GCPBucket",
|
|
40
|
+
"JerryClient"
|
|
41
|
+
"FTP"
|
|
32
42
|
]
|
|
33
43
|
|
|
34
44
|
_diretorio_inicial = os.getcwd()
|
|
@@ -7,7 +7,7 @@ from requests.auth import HTTPBasicAuth
|
|
|
7
7
|
from datetime import timedelta, datetime, time
|
|
8
8
|
from typing import List, Tuple, Union, Optional
|
|
9
9
|
from pydantic import BaseModel
|
|
10
|
-
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
11
|
class PastasAutorizadas(BaseModel):
|
|
12
12
|
MensagemErro: Optional[str] = None
|
|
13
13
|
OcorreuErro: bool
|
|
@@ -218,7 +218,7 @@ class BC_Correios:
|
|
|
218
218
|
break
|
|
219
219
|
|
|
220
220
|
for correio in correios:
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
item_de_lista_de_correios = ItemListaCorreio(
|
|
223
223
|
Assunto=correio.Assunto,
|
|
224
224
|
Data=correio.Data,
|
|
@@ -248,17 +248,18 @@ class BC_Correios:
|
|
|
248
248
|
|
|
249
249
|
if len(correios_filtrados) >= proxima_centena:
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
log.info(f"Página #{pagina_atual}: {len(correios_filtrados)} correios armazenados no total")
|
|
252
252
|
proxima_centena = proxima_centena + 100
|
|
253
253
|
|
|
254
254
|
# Avança para a próxima página
|
|
255
255
|
pagina_atual += 1
|
|
256
256
|
|
|
257
|
-
return True, correios_filtrados
|
|
258
|
-
|
|
257
|
+
#return True, correios_filtrados
|
|
258
|
+
return {"success": True, "data": correios_filtrados, "error": None}
|
|
259
|
+
|
|
259
260
|
except Exception as e:
|
|
260
|
-
|
|
261
|
-
return False, e
|
|
261
|
+
|
|
262
|
+
return {"success": False, "data": None, "error": str(e)}
|
|
262
263
|
|
|
263
264
|
def ler_correio(self, numero:int,data_rebimento:datetime,tipo:str,transicao:int,versao:int,pasta:str)-> Tuple[bool, Union[ItemMsgCorreio, Exception]]:
|
|
264
265
|
"""
|
|
@@ -425,7 +426,8 @@ class BC_Correios:
|
|
|
425
426
|
#novo end
|
|
426
427
|
|
|
427
428
|
#return True, response
|
|
428
|
-
return True, msg_detail
|
|
429
|
+
#return True, msg_detail
|
|
430
|
+
return {"success": True, "data": msg_detail, "error": None}
|
|
429
431
|
|
|
430
432
|
# Serializa o objeto SOAP em um dicionário Python
|
|
431
433
|
#serialized_response = serialize_object(response)
|
|
@@ -437,7 +439,8 @@ class BC_Correios:
|
|
|
437
439
|
|
|
438
440
|
except Exception as e:
|
|
439
441
|
|
|
440
|
-
return False, e
|
|
442
|
+
#return False, e
|
|
443
|
+
return {"success": False, "data": None, "error": str(e)}
|
|
441
444
|
|
|
442
445
|
def obter_anexo(self, numero:int, versao:int, transicao:int,pasta:str,anexo_id:int,file_name:str,conteudo:str)-> Tuple[bool,Union[dict, Exception]]:
|
|
443
446
|
"""
|
|
@@ -504,17 +507,20 @@ class BC_Correios:
|
|
|
504
507
|
conteudo_anexo = response.Anexo.Conteudo
|
|
505
508
|
|
|
506
509
|
# Retorna os dados capturados como um dicionário
|
|
507
|
-
return True, {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
510
|
+
#return True, {
|
|
511
|
+
# 'IdAnexo': id_anexo,
|
|
512
|
+
# 'NomeAnexo': nome_anexo,
|
|
513
|
+
# 'Conteudo': conteudo_anexo
|
|
514
|
+
#}
|
|
515
|
+
return {"success": True, "error": None, "data": conteudo_anexo}
|
|
512
516
|
else:
|
|
513
|
-
return
|
|
517
|
+
return {"success": True, "error": "Correio não possui anexo", "data": None}
|
|
518
|
+
#return False, Exception("Correio não possui anexo")
|
|
514
519
|
|
|
515
520
|
except Exception as e:
|
|
516
521
|
|
|
517
|
-
return False, e
|
|
522
|
+
return {"success": False, "error": str(e), "data": None}
|
|
523
|
+
#return False, e
|
|
518
524
|
|
|
519
525
|
def encerrar(self):
|
|
520
526
|
"""Fecha o cliente e libera a sessão."""
|
|
@@ -1,9 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
|
+
import hashlib
|
|
2
3
|
import requests
|
|
4
|
+
from requests.auth import HTTPBasicAuth
|
|
3
5
|
import xml.etree.ElementTree as ET
|
|
4
|
-
import
|
|
5
|
-
from pydantic import BaseModel, ValidationError, field_validator
|
|
6
|
-
from typing import Literal, Dict, Union, Optional
|
|
6
|
+
from xml.dom.minidom import parseString
|
|
7
|
+
from pydantic import BaseModel, ValidationError, field_validator, Field, HttpUrl
|
|
8
|
+
from typing import Literal, Dict, Union, Optional, List
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger('__main__')
|
|
12
|
+
|
|
13
|
+
def xml_to_dict(element):
|
|
14
|
+
"""Converte um elemento XML recursivamente para um dicionário."""
|
|
15
|
+
if not list(element): # Se não tem filhos, retorna apenas o texto
|
|
16
|
+
return element.text.strip() if element.text else ""
|
|
17
|
+
|
|
18
|
+
result = {}
|
|
19
|
+
for child in element:
|
|
20
|
+
child_data = xml_to_dict(child)
|
|
21
|
+
if child.tag in result:
|
|
22
|
+
if isinstance(result[child.tag], list):
|
|
23
|
+
result[child.tag].append(child_data)
|
|
24
|
+
else:
|
|
25
|
+
result[child.tag] = [result[child.tag], child_data]
|
|
26
|
+
else:
|
|
27
|
+
result[child.tag] = child_data
|
|
28
|
+
|
|
29
|
+
if element.attrib:
|
|
30
|
+
result["@atributos"] = element.attrib # Adiciona atributos XML se existirem
|
|
31
|
+
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
def xml_response_to_json(response_text):
|
|
35
|
+
"""Converte a resposta XML para um dicionário JSON válido."""
|
|
36
|
+
|
|
37
|
+
root = ET.fromstring(response_text)
|
|
38
|
+
lista = xml_to_dict(root)
|
|
39
|
+
if not isinstance(lista, dict):
|
|
40
|
+
return []
|
|
41
|
+
return list(lista.values())[0] # Agora retorna um dicionário em vez de uma string JSON
|
|
42
|
+
|
|
43
|
+
def print_element(element, indent=0):
|
|
44
|
+
"""Função recursiva para exibir campos e subcampos"""
|
|
45
|
+
prefix = " " * (indent * 2) # Indentação para visualização hierárquica
|
|
46
|
+
print(f"{prefix}- {element.tag}: {element.text.strip() if element.text else ''}")
|
|
47
|
+
|
|
48
|
+
# Se o elemento tiver atributos, exibir
|
|
49
|
+
if element.attrib:
|
|
50
|
+
print(f"{prefix} Atributos: {element.attrib}")
|
|
51
|
+
|
|
52
|
+
# Percorrer subelementos
|
|
53
|
+
for child in element:
|
|
54
|
+
print_element(child, indent + 1)
|
|
7
55
|
|
|
8
56
|
# Validações dos inputs
|
|
9
57
|
class InitParamsValidator(BaseModel):
|
|
@@ -118,7 +166,44 @@ class EnviarArquivoValidator(BaseModel):
|
|
|
118
166
|
raise ValueError("Cada destinatário deve conter as chaves 'unidade', 'dependencia' e 'operador'. Verifique a documentação da API BC STA para entender o que colocar cada campo")
|
|
119
167
|
|
|
120
168
|
return value
|
|
121
|
-
|
|
169
|
+
|
|
170
|
+
class ListarArquivosParams(BaseModel):
|
|
171
|
+
"""
|
|
172
|
+
Parâmetros para listar arquivos disponíveis na API STA.
|
|
173
|
+
|
|
174
|
+
Atributos:
|
|
175
|
+
nivel (str): Nível de detalhe da consulta. Aceita apenas 'RES', 'BAS' ou 'COMPL'.
|
|
176
|
+
inicio (str): Data e hora de início no formato ISO 8601 (yyyy-MM-ddTHH:mm:ss).
|
|
177
|
+
fim (str): Data e hora de fim no formato ISO 8601 (yyyy-MM-ddTHH:mm:ss).
|
|
178
|
+
situacao (Optional[str]): Situação da transmissão, podendo ser 'REC' ou 'A_REC'.
|
|
179
|
+
identificadorDocumento (Optional[str]): Identificador do documento, se aplicável.
|
|
180
|
+
qtd (int): Quantidade máxima de resultados (valor padrão: 100, máximo permitido: 100).
|
|
181
|
+
tipo_arquivo (list): lista de tipos de arquivo para filtrar ['ACCS002','ACCS003','AJUD301','AJUD302','AJUD303','AJUD304','AJUD305','AJUD308','AJUD309','AJUD310','AJUD331','AMES102','AMTF102','ASVR9810','ATXB001']
|
|
182
|
+
"""
|
|
183
|
+
nivel: Literal['RES', 'BAS', 'COMPL']
|
|
184
|
+
#inicio: str = Field(..., regex=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
|
185
|
+
inicio: str = Field(..., pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
|
186
|
+
fim: Optional[str] = Field(None, pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
|
187
|
+
situacao: Optional[Literal['REC', 'A_REC']] = None
|
|
188
|
+
identificadorDocumento: Optional[str] = None
|
|
189
|
+
qtd: int = Field(default=100, le=100)
|
|
190
|
+
#tipo_arquivo: Optional[str] = None
|
|
191
|
+
tipo_arquivo: Optional[List[
|
|
192
|
+
Literal[
|
|
193
|
+
'ACCS002', 'ACCS003', 'AJUD301', 'AJUD302', 'AJUD303', 'AJUD304', 'AJUD305', 'AJUD308',
|
|
194
|
+
'AJUD309', 'AJUD310', 'AJUD331', 'AMES102', 'AMTF102', 'ASVR9810', 'ATXB001'
|
|
195
|
+
]
|
|
196
|
+
]] = None
|
|
197
|
+
|
|
198
|
+
class DownloadArquivoParams(BaseModel):
|
|
199
|
+
protocolo: str = Field(..., min_length=1, description="Código do protocolo")
|
|
200
|
+
filename: Optional[str] = Field(None, description="Nome e caminho do arquivo")
|
|
201
|
+
|
|
202
|
+
class DownloadArquivoResponse(BaseModel):
|
|
203
|
+
success: bool = Field(..., description="Indica se o download foi bem-sucedido")
|
|
204
|
+
status_code: int = Field(..., ge=100, le=599, description="Código de status HTTP")
|
|
205
|
+
content: Union[bytes, str] = Field(..., description="Conteúdo do arquivo (em bytes se sucesso, em string se erro)")
|
|
206
|
+
|
|
122
207
|
class BC_STA:
|
|
123
208
|
|
|
124
209
|
def __init__(self, usuario:str, senha:str, ambiente:str):
|
|
@@ -183,11 +268,17 @@ class BC_STA:
|
|
|
183
268
|
|
|
184
269
|
# Verificando o status e retornando a resposta
|
|
185
270
|
if response.status_code == 200:
|
|
186
|
-
|
|
271
|
+
|
|
187
272
|
return True
|
|
188
273
|
|
|
274
|
+
elif response.status_code == 401:
|
|
275
|
+
|
|
276
|
+
log.error(f"Erro de autenticação no BC STA: Verifique o usuário e a senha.\n{response.text}")
|
|
277
|
+
return False
|
|
278
|
+
|
|
189
279
|
else:
|
|
190
280
|
|
|
281
|
+
log.error(f"Erro ao autenticar no BC STA: {response.text}")
|
|
191
282
|
return False
|
|
192
283
|
|
|
193
284
|
|
|
@@ -414,3 +505,194 @@ class BC_STA:
|
|
|
414
505
|
#return f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
|
|
415
506
|
return resposta
|
|
416
507
|
|
|
508
|
+
def listar_arquivos(self,
|
|
509
|
+
nivel: Literal['RES', 'BAS', 'COMPL'],
|
|
510
|
+
inicio: str,
|
|
511
|
+
fim: str = None,
|
|
512
|
+
situacao: Optional[Literal['REC', 'A_REC']] = None,
|
|
513
|
+
identificadorDocumento: Optional[str] = None,
|
|
514
|
+
qtd: int = 100,
|
|
515
|
+
tipo_arquivo: list = None):
|
|
516
|
+
resultados = []
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
# Parse seguro para datetime (assume formato ISO 'AAAA-MM-DDTHH:MM:SS')
|
|
520
|
+
dt_inicio = datetime.fromisoformat(inicio)
|
|
521
|
+
dt_fim = datetime.fromisoformat(fim) if fim else None
|
|
522
|
+
ultima_dt = dt_inicio
|
|
523
|
+
|
|
524
|
+
while True:
|
|
525
|
+
# Se já passamos do fim, encerra antes de chamar a API
|
|
526
|
+
if dt_fim and ultima_dt >= dt_fim:
|
|
527
|
+
break
|
|
528
|
+
|
|
529
|
+
# Monta a URL usando a data atual do cursor
|
|
530
|
+
_inicio_str = ultima_dt.strftime("%Y-%m-%dT%H:%M:%S")
|
|
531
|
+
url = f"{self.base_url}/arquivos?tipoConsulta=AVANC&nivelDetalhe={nivel}"
|
|
532
|
+
url += f"&dataHoraInicio={_inicio_str}"
|
|
533
|
+
if situacao:
|
|
534
|
+
url += f"&situacaoTransmissao={situacao}"
|
|
535
|
+
if identificadorDocumento:
|
|
536
|
+
url += f"&identificadorDocumento={identificadorDocumento}"
|
|
537
|
+
if dt_fim:
|
|
538
|
+
_fim_str = dt_fim.strftime("%Y-%m-%dT%H:%M:%S")
|
|
539
|
+
url += f"&dataHoraFim={_fim_str}"
|
|
540
|
+
url += f"&qtdMaxResultados={qtd}"
|
|
541
|
+
|
|
542
|
+
response = requests.get(
|
|
543
|
+
url, headers=self.headers, auth=self.auth, timeout=60,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
if response.status_code != 200:
|
|
547
|
+
resposta = {"success": False, "status_code": int(response.status_code), "content": f"Erro ao listar arquivos: {response.text}"}
|
|
548
|
+
return resposta
|
|
549
|
+
|
|
550
|
+
try:
|
|
551
|
+
dados = xml_response_to_json(response.text)
|
|
552
|
+
if not dados:
|
|
553
|
+
break # sem mais resultados
|
|
554
|
+
|
|
555
|
+
# Normaliza dados para lista
|
|
556
|
+
itens = dados if isinstance(dados, list) else [dados]
|
|
557
|
+
|
|
558
|
+
# (opcional) filtra por tipo_arquivo
|
|
559
|
+
if tipo_arquivo is not None:
|
|
560
|
+
itens = [a for a in itens if a.get("TipoArquivo") in tipo_arquivo]
|
|
561
|
+
|
|
562
|
+
resultados.extend(itens)
|
|
563
|
+
|
|
564
|
+
# Atualiza o cursor a partir do último item retornado
|
|
565
|
+
# Busca o último com DataHoraDisponibilizacao
|
|
566
|
+
ultimo = None
|
|
567
|
+
for cand in reversed(itens):
|
|
568
|
+
if "DataHoraDisponibilizacao" in cand and cand["DataHoraDisponibilizacao"]:
|
|
569
|
+
ultimo = cand["DataHoraDisponibilizacao"]
|
|
570
|
+
break
|
|
571
|
+
|
|
572
|
+
if not ultimo:
|
|
573
|
+
# fallback: se o JSON bruto vier como dict/list diferente, tente no original
|
|
574
|
+
if isinstance(dados, list) and dados and "DataHoraDisponibilizacao" in dados[-1]:
|
|
575
|
+
ultimo = dados[-1]["DataHoraDisponibilizacao"]
|
|
576
|
+
elif isinstance(dados, dict) and "DataHoraDisponibilizacao" in dados:
|
|
577
|
+
ultimo = dados["DataHoraDisponibilizacao"]
|
|
578
|
+
|
|
579
|
+
if not ultimo:
|
|
580
|
+
resposta = {"success": False, "status_code": 500, "content": "Campo 'DataHoraDisponibilizacao' não encontrado ou estrutura inesperada."}
|
|
581
|
+
return resposta
|
|
582
|
+
|
|
583
|
+
# Cursor: último + 1s para evitar repetir o mesmo registro
|
|
584
|
+
proxima_dt = datetime.fromisoformat(ultimo) + timedelta(seconds=1)
|
|
585
|
+
|
|
586
|
+
# Se a próxima dt já ultrapassa o fim, encerramos sem nova chamada
|
|
587
|
+
if dt_fim and proxima_dt > dt_fim:
|
|
588
|
+
break
|
|
589
|
+
|
|
590
|
+
ultima_dt = proxima_dt
|
|
591
|
+
|
|
592
|
+
except ET.ParseError as e:
|
|
593
|
+
resposta = {"success": False, "status_code": 500, "content": f"Erro ao processar XML: {e}"}
|
|
594
|
+
return resposta
|
|
595
|
+
|
|
596
|
+
return {"success": True, "status_code": 200, "content": resultados}
|
|
597
|
+
|
|
598
|
+
except Exception as e:
|
|
599
|
+
resposta = {"success": False, "status_code": 500, "content": f"Erro em BC_STA:listar_arquivos: {e}"}
|
|
600
|
+
return resposta
|
|
601
|
+
|
|
602
|
+
def download_arquivo(self,protocolo:str,filename:str=None):
|
|
603
|
+
"""Faz o download de um arquivo de um protocolo especifico
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
protocolo (str): protocolo
|
|
607
|
+
filename (str): path+nome do arquivo
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
dict: {"success": bool, "status_code": int, "content": bytes/str}
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
# Validação dos parâmetros
|
|
614
|
+
try:
|
|
615
|
+
|
|
616
|
+
params = DownloadArquivoParams(protocolo=protocolo, filename=filename)
|
|
617
|
+
|
|
618
|
+
except ValidationError as e:
|
|
619
|
+
|
|
620
|
+
return Exception(str(e))
|
|
621
|
+
|
|
622
|
+
url = f"/arquivos/{protocolo}/conteudo"
|
|
623
|
+
response = requests.get(
|
|
624
|
+
self.base_url + url,
|
|
625
|
+
auth=self.auth,
|
|
626
|
+
timeout=60,
|
|
627
|
+
headers={"Connection": "keep-alive"},
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
if response.status_code == 200:
|
|
631
|
+
|
|
632
|
+
if filename is not None:
|
|
633
|
+
|
|
634
|
+
try:
|
|
635
|
+
|
|
636
|
+
with open(filename, "wb") as arquivo:
|
|
637
|
+
|
|
638
|
+
arquivo.write(response.content)
|
|
639
|
+
|
|
640
|
+
except Exception as e:
|
|
641
|
+
|
|
642
|
+
raise Exception(f"Falha ao salvar o arquivo em disco\n{str(e)}")
|
|
643
|
+
|
|
644
|
+
return {"success": True, "status_code": int(response.status_code), "content": response.content }
|
|
645
|
+
|
|
646
|
+
else:
|
|
647
|
+
|
|
648
|
+
return {"success": False, "status_code": int(response.status_code), "content": response.text}
|
|
649
|
+
|
|
650
|
+
def qs_gerar_xml_string_responder_nao_cliente(self,protocolo_inicial_ordem:str,numctrlccs:str,cnpj_cpf:str,numctrlenvio:str,numprocjud:str,identdemissor:str,identddestinatario:str,domsist:str,nuop:str,dtmovto:datetime,cnpjbaseentrespons:str):
|
|
651
|
+
|
|
652
|
+
"""Gera a string do arquivo XML de resposta para nao-cliente para ser enviado ao STA
|
|
653
|
+
Args:
|
|
654
|
+
protocolo_inicial_ordem (str): protocolo inicial da ordem de quebra de sigilo, do arquivo AMES102
|
|
655
|
+
numctrlccs (str): numero de controle ccs
|
|
656
|
+
cnpj_cpf (str): documento do solicitado
|
|
657
|
+
numctrlenvio (str): numero de controle de envio
|
|
658
|
+
numprocjud (str): numero do processo judicial
|
|
659
|
+
identdemissor (str): identificacao do emissor
|
|
660
|
+
identddestinatario (str): identificacao do destinatario
|
|
661
|
+
domsist (str): domsist
|
|
662
|
+
nuop (str): numero da operacao
|
|
663
|
+
dtmovto (datetime): data da movimentacao
|
|
664
|
+
cnpjbaseentrespons (str): cnpjbaseentrespons
|
|
665
|
+
Returns:
|
|
666
|
+
str: string formatada para gerar um arquivo xml de resposta
|
|
667
|
+
"""
|
|
668
|
+
try:
|
|
669
|
+
|
|
670
|
+
ns = "http://www.bcb.gov.br/MES/CCS0012.xsd"
|
|
671
|
+
|
|
672
|
+
doc = ET.Element("DOC", xmlns=ns)
|
|
673
|
+
|
|
674
|
+
bcmsg = ET.SubElement(doc, "BCMSG")
|
|
675
|
+
ET.SubElement(bcmsg, "IdentdEmissor").text = identdemissor
|
|
676
|
+
ET.SubElement(bcmsg, "IdentdDestinatario").text = identddestinatario
|
|
677
|
+
ET.SubElement(bcmsg, "DomSist").text = domsist
|
|
678
|
+
ET.SubElement(bcmsg, "NUOp").text = nuop
|
|
679
|
+
|
|
680
|
+
sismsg = ET.SubElement(doc, "SISMSG")
|
|
681
|
+
ccs0012 = ET.SubElement(sismsg, "CCS0012")
|
|
682
|
+
ET.SubElement(ccs0012, "CodMsg").text = "CCS0012"
|
|
683
|
+
ET.SubElement(ccs0012, "CNPJBaseEntRespons").text = cnpjbaseentrespons
|
|
684
|
+
ET.SubElement(ccs0012, "NumCtrlCCSOr").text = numctrlccs
|
|
685
|
+
ET.SubElement(ccs0012, "SitAtedmntReq").text = "03"
|
|
686
|
+
ET.SubElement(ccs0012, "DtMovto").text = dtmovto.strftime("%Y-%m-%d")
|
|
687
|
+
|
|
688
|
+
#return ET.tostring(doc, encoding="utf-8", xml_declaration=True).decode("utf-8")
|
|
689
|
+
xml_str = ET.tostring(doc, encoding="utf-8", xml_declaration=True).decode("utf-8")
|
|
690
|
+
|
|
691
|
+
# Formatando XML com indentação para melhor visualização
|
|
692
|
+
xml_formatado = parseString(xml_str).toprettyxml(indent=" ")
|
|
693
|
+
return xml_formatado
|
|
694
|
+
|
|
695
|
+
except Exception as e:
|
|
696
|
+
|
|
697
|
+
raise Exception(f"Falha ao gerar XML de resposta de Quebra de Sigilo:\n{e}")
|
|
698
|
+
|
|
@@ -150,7 +150,7 @@ class Email():
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def send_email( self, to : list , message : str , title : str , reply_to: str, attachments : list = [] , cc : list = [] , cco : list = [] ) -> dict:
|
|
153
|
+
def send_email( self, to : list , message : str , title : str , reply_to: str, attachments : list = [] , cc : list = [] , cco : list = [], from_mask: str = "", block_by_attachments:bool=False) -> dict:
|
|
154
154
|
"""
|
|
155
155
|
Envia um email com os parâmetros fornecidos.
|
|
156
156
|
Args:
|
|
@@ -161,6 +161,7 @@ class Email():
|
|
|
161
161
|
attachments (list, optional): Lista de caminhos dos arquivos anexos. Defaults to [].
|
|
162
162
|
cc (list, optional): Lista de destinatários em cópia. Defaults to [].
|
|
163
163
|
cco (list, optional): Lista de destinatários em cópia oculta. Defaults to [].
|
|
164
|
+
from_mask (str, optional): Mascara para definir como se o envio estivesse vindo de outro email
|
|
164
165
|
Returns:
|
|
165
166
|
dict: Dicionário com o status do envio do email. Se o envio for bem-sucedido, o dicionário terá a chave 'status' com valor True. Caso contrário, terá a chave 'status' com valor False e a chave 'error' com a descrição do erro.
|
|
166
167
|
Raises:
|
|
@@ -189,8 +190,14 @@ class Email():
|
|
|
189
190
|
try:
|
|
190
191
|
|
|
191
192
|
msg = MIMEMultipart()
|
|
193
|
+
|
|
194
|
+
if from_mask != "":
|
|
192
195
|
|
|
193
|
-
|
|
196
|
+
msg["From"] = from_mask
|
|
197
|
+
|
|
198
|
+
else:
|
|
199
|
+
|
|
200
|
+
msg["From"] = self.email_sender
|
|
194
201
|
|
|
195
202
|
msg["To"] = (",").join(to)
|
|
196
203
|
|
|
@@ -220,10 +227,12 @@ class Email():
|
|
|
220
227
|
|
|
221
228
|
except Exception as e:
|
|
222
229
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
if block_by_attachments:
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
'status':False,
|
|
234
|
+
'error':str(e)
|
|
235
|
+
}
|
|
227
236
|
|
|
228
237
|
msg.attach(MIMEText(message, 'html'))
|
|
229
238
|
|