csc-cia-stne 0.0.64__py3-none-any.whl → 0.0.66__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.
- csc_cia_stne/bc_sta.py +212 -3
- {csc_cia_stne-0.0.64.dist-info → csc_cia_stne-0.0.66.dist-info}/METADATA +1 -1
- {csc_cia_stne-0.0.64.dist-info → csc_cia_stne-0.0.66.dist-info}/RECORD +6 -6
- {csc_cia_stne-0.0.64.dist-info → csc_cia_stne-0.0.66.dist-info}/LICENCE +0 -0
- {csc_cia_stne-0.0.64.dist-info → csc_cia_stne-0.0.66.dist-info}/WHEEL +0 -0
- {csc_cia_stne-0.0.64.dist-info → csc_cia_stne-0.0.66.dist-info}/top_level.txt +0 -0
csc_cia_stne/bc_sta.py
CHANGED
@@ -2,8 +2,55 @@ from requests.auth import HTTPBasicAuth
|
|
2
2
|
import requests
|
3
3
|
import xml.etree.ElementTree as ET
|
4
4
|
import hashlib
|
5
|
-
from pydantic import BaseModel, ValidationError, field_validator
|
6
|
-
from typing import Literal, Dict, Union, Optional
|
5
|
+
from pydantic import BaseModel, ValidationError, field_validator, Field, HttpUrl
|
6
|
+
from typing import Literal, Dict, Union, Optional, List
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
|
10
|
+
log = logging.getLogger('__main__')
|
11
|
+
|
12
|
+
def xml_to_dict(element):
|
13
|
+
"""Converte um elemento XML recursivamente para um dicionário."""
|
14
|
+
if not list(element): # Se não tem filhos, retorna apenas o texto
|
15
|
+
return element.text.strip() if element.text else ""
|
16
|
+
|
17
|
+
result = {}
|
18
|
+
for child in element:
|
19
|
+
child_data = xml_to_dict(child)
|
20
|
+
if child.tag in result:
|
21
|
+
if isinstance(result[child.tag], list):
|
22
|
+
result[child.tag].append(child_data)
|
23
|
+
else:
|
24
|
+
result[child.tag] = [result[child.tag], child_data]
|
25
|
+
else:
|
26
|
+
result[child.tag] = child_data
|
27
|
+
|
28
|
+
if element.attrib:
|
29
|
+
result["@atributos"] = element.attrib # Adiciona atributos XML se existirem
|
30
|
+
|
31
|
+
return result
|
32
|
+
|
33
|
+
def xml_response_to_json(response_text):
|
34
|
+
"""Converte a resposta XML para um dicionário JSON válido."""
|
35
|
+
|
36
|
+
root = ET.fromstring(response_text)
|
37
|
+
lista = xml_to_dict(root)
|
38
|
+
if not isinstance(lista, dict):
|
39
|
+
return []
|
40
|
+
return list(lista.values())[0] # Agora retorna um dicionário em vez de uma string JSON
|
41
|
+
|
42
|
+
def print_element(element, indent=0):
|
43
|
+
"""Função recursiva para exibir campos e subcampos"""
|
44
|
+
prefix = " " * (indent * 2) # Indentação para visualização hierárquica
|
45
|
+
print(f"{prefix}- {element.tag}: {element.text.strip() if element.text else ''}")
|
46
|
+
|
47
|
+
# Se o elemento tiver atributos, exibir
|
48
|
+
if element.attrib:
|
49
|
+
print(f"{prefix} Atributos: {element.attrib}")
|
50
|
+
|
51
|
+
# Percorrer subelementos
|
52
|
+
for child in element:
|
53
|
+
print_element(child, indent + 1)
|
7
54
|
|
8
55
|
# Validações dos inputs
|
9
56
|
class InitParamsValidator(BaseModel):
|
@@ -118,7 +165,44 @@ class EnviarArquivoValidator(BaseModel):
|
|
118
165
|
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
166
|
|
120
167
|
return value
|
121
|
-
|
168
|
+
|
169
|
+
class ListarArquivosParams(BaseModel):
|
170
|
+
"""
|
171
|
+
Parâmetros para listar arquivos disponíveis na API STA.
|
172
|
+
|
173
|
+
Atributos:
|
174
|
+
nivel (str): Nível de detalhe da consulta. Aceita apenas 'RES', 'BAS' ou 'COMPL'.
|
175
|
+
inicio (str): Data e hora de início no formato ISO 8601 (yyyy-MM-ddTHH:mm:ss).
|
176
|
+
fim (str): Data e hora de fim no formato ISO 8601 (yyyy-MM-ddTHH:mm:ss).
|
177
|
+
situacao (Optional[str]): Situação da transmissão, podendo ser 'REC' ou 'A_REC'.
|
178
|
+
identificadorDocumento (Optional[str]): Identificador do documento, se aplicável.
|
179
|
+
qtd (int): Quantidade máxima de resultados (valor padrão: 100, máximo permitido: 100).
|
180
|
+
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']
|
181
|
+
"""
|
182
|
+
nivel: Literal['RES', 'BAS', 'COMPL']
|
183
|
+
#inicio: str = Field(..., regex=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
184
|
+
inicio: str = Field(..., pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
185
|
+
fim: Optional[str] = Field(None, pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
186
|
+
situacao: Optional[Literal['REC', 'A_REC']] = None
|
187
|
+
identificadorDocumento: Optional[str] = None
|
188
|
+
qtd: int = Field(default=100, le=100)
|
189
|
+
#tipo_arquivo: Optional[str] = None
|
190
|
+
tipo_arquivo: Optional[List[
|
191
|
+
Literal[
|
192
|
+
'ACCS002', 'ACCS003', 'AJUD301', 'AJUD302', 'AJUD303', 'AJUD304', 'AJUD305', 'AJUD308',
|
193
|
+
'AJUD309', 'AJUD310', 'AJUD331', 'AMES102', 'AMTF102', 'ASVR9810', 'ATXB001'
|
194
|
+
]
|
195
|
+
]] = None
|
196
|
+
|
197
|
+
class DownloadArquivoParams(BaseModel):
|
198
|
+
protocolo: str = Field(..., min_length=1, description="Código do protocolo")
|
199
|
+
filename: Optional[str] = Field(None, description="Nome e caminho do arquivo")
|
200
|
+
|
201
|
+
class DownloadArquivoResponse(BaseModel):
|
202
|
+
success: bool = Field(..., description="Indica se o download foi bem-sucedido")
|
203
|
+
status_code: int = Field(..., ge=100, le=599, description="Código de status HTTP")
|
204
|
+
content: Union[bytes, str] = Field(..., description="Conteúdo do arquivo (em bytes se sucesso, em string se erro)")
|
205
|
+
|
122
206
|
class BC_STA:
|
123
207
|
|
124
208
|
def __init__(self, usuario:str, senha:str, ambiente:str):
|
@@ -414,3 +498,128 @@ class BC_STA:
|
|
414
498
|
#return f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
|
415
499
|
return resposta
|
416
500
|
|
501
|
+
def listar_arquivos(self, nivel: Literal['RES', 'BAS', 'COMPL'], inicio: str, fim: str = None,situacao: Optional[Literal['REC', 'A_REC']] = None, identificadorDocumento: Optional[str] = None, qtd: int = 100, tipo_arquivo:list=None):
|
502
|
+
"""Lista os arquivos do STA com os parâmetros informados.
|
503
|
+
|
504
|
+
Args:
|
505
|
+
nivel (Literal['RES', 'BAS', 'COMPL']): nivel de detalhamento da consulta
|
506
|
+
inicio (str): data de inicio, no padrao 'AAAA-MM-DDTHH:MM:SS'
|
507
|
+
fim (str, optional): data de fim da consulta, no padrao 'AAAA-MM-DDTHH:MM:SS'
|
508
|
+
situacao (Optional[Literal['REC', 'A_REC']], optional): Arquivos já recebidos ou ainda não recebidos. Default=None (Todos).
|
509
|
+
identificadorDocumento (Optional[str], optional): Identificador documento. Default=None (Todos).
|
510
|
+
qtd (int, optional): Quantidade de arquivos na resposta. Default=100 (máximo=100).
|
511
|
+
tipo_arquivo (list, optional): Tipo de arquivo para fazer download. Default=None (Todos). Opções: ['ACCS002', 'ACCS003', 'AJUD301', 'AJUD302', 'AJUD303', 'AJUD304', 'AJUD305', 'AJUD308', 'AJUD309', 'AJUD310', 'AJUD331', 'AMES102', 'AMTF102', 'ASVR9810', 'ATXB001'].
|
512
|
+
|
513
|
+
Returns:
|
514
|
+
list: Lista de arquivos do STA
|
515
|
+
"""
|
516
|
+
resultados = []
|
517
|
+
ultima_data = inicio
|
518
|
+
|
519
|
+
while True:
|
520
|
+
params = ListarArquivosParams(nivel=nivel, inicio=ultima_data, fim=fim, situacao=situacao, identificadorDocumento=identificadorDocumento, qtd=qtd, tipo_arquivo=tipo_arquivo)
|
521
|
+
|
522
|
+
url = f"{self.base_url}/arquivos?tipoConsulta=AVANC&nivelDetalhe={params.nivel}"
|
523
|
+
url += f"&dataHoraInicio={params.inicio}&situacaoTransmissao={params.situacao}" if params.situacao else ""
|
524
|
+
url += f"&identificadorDocumento={params.identificadorDocumento}" if params.identificadorDocumento else ""
|
525
|
+
url += f"&dataHoraFim={params.fim}" if params.fim else ""
|
526
|
+
url += f"&qtdMaxResultados={params.qtd}"
|
527
|
+
|
528
|
+
response = requests.get(
|
529
|
+
url,
|
530
|
+
headers=self.headers,
|
531
|
+
auth=self.auth,
|
532
|
+
timeout=60,
|
533
|
+
)
|
534
|
+
|
535
|
+
if response.status_code == 200:
|
536
|
+
|
537
|
+
try:
|
538
|
+
|
539
|
+
dados = xml_response_to_json(response.text)
|
540
|
+
|
541
|
+
if not dados: # Verifica se a lista está vazia
|
542
|
+
|
543
|
+
break # Sai do loop se não houver mais dados
|
544
|
+
|
545
|
+
# Filtra apenas os arquivos do tipo 'AJUD308'
|
546
|
+
if tipo_arquivo is not None:
|
547
|
+
|
548
|
+
dados_filtrados = [arquivo for arquivo in dados if arquivo.get("TipoArquivo") in tipo_arquivo]
|
549
|
+
resultados.extend(dados_filtrados)
|
550
|
+
|
551
|
+
else:
|
552
|
+
|
553
|
+
resultados.extend(dados)
|
554
|
+
|
555
|
+
# Verifica se o campo 'DataHoraDisponibilizacao' existe no último registro
|
556
|
+
if dados and isinstance(dados, list) and 'DataHoraDisponibilizacao' in dados[-1]:
|
557
|
+
|
558
|
+
ultima_data = dados[-1]['DataHoraDisponibilizacao'] # Atualiza a data para a próxima requisição
|
559
|
+
|
560
|
+
else:
|
561
|
+
|
562
|
+
log.error("Campo 'DataHoraDisponibilizacao' não encontrado ou estrutura inesperada.")
|
563
|
+
return False
|
564
|
+
|
565
|
+
except ET.ParseError as e:
|
566
|
+
|
567
|
+
log.error(f"Erro ao processar XML: {e}")
|
568
|
+
return False
|
569
|
+
|
570
|
+
else:
|
571
|
+
|
572
|
+
log.error(f"Erro ao listar arquivos: {response.status_code}")
|
573
|
+
return False
|
574
|
+
|
575
|
+
return resultados
|
576
|
+
|
577
|
+
def download_arquivo(self,protocolo:str,filename:str=None):
|
578
|
+
"""Faz o download de um arquivo de um protocolo especifico
|
579
|
+
|
580
|
+
Args:
|
581
|
+
protocolo (str): protocolo
|
582
|
+
filename (str): path+nome do arquivo
|
583
|
+
|
584
|
+
Returns:
|
585
|
+
dict: {"success": bool, "status_code": int, "content": bytes/str}
|
586
|
+
"""
|
587
|
+
|
588
|
+
# Validação dos parâmetros
|
589
|
+
try:
|
590
|
+
|
591
|
+
params = DownloadArquivoParams(protocolo=protocolo, filename=filename)
|
592
|
+
|
593
|
+
except ValidationError as e:
|
594
|
+
|
595
|
+
return Exception(str(e))
|
596
|
+
|
597
|
+
url = f"/arquivos/{protocolo}/conteudo"
|
598
|
+
response = requests.get(
|
599
|
+
self.base_url + url,
|
600
|
+
auth=self.auth,
|
601
|
+
timeout=60,
|
602
|
+
headers={"Connection": "keep-alive"},
|
603
|
+
)
|
604
|
+
|
605
|
+
if response.status_code == 200:
|
606
|
+
|
607
|
+
if filename is not None:
|
608
|
+
|
609
|
+
try:
|
610
|
+
|
611
|
+
with open(filename, "wb") as arquivo:
|
612
|
+
|
613
|
+
arquivo.write(response.content)
|
614
|
+
|
615
|
+
except Exception as e:
|
616
|
+
|
617
|
+
raise Exception(f"Falha ao salvar o arquivo em disco\n{str(e)}")
|
618
|
+
|
619
|
+
return {"success": True, "status_code": int(response.status_code), "content": response.content }
|
620
|
+
|
621
|
+
else:
|
622
|
+
|
623
|
+
return {"success": False, "status_code": int(response.status_code), "content": response.text}
|
624
|
+
|
625
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
csc_cia_stne/__init__.py,sha256=Io-gKis1evws5cHUqyOrcsZKNCQRviYj3zbp__5lgKU,2512
|
2
2
|
csc_cia_stne/bc_correios.py,sha256=pQAnRrcXEMrx3N1MWydZVIhEQLerh3x8-0B045zZIzk,24174
|
3
|
-
csc_cia_stne/bc_sta.py,sha256=
|
3
|
+
csc_cia_stne/bc_sta.py,sha256=KWHTytM3msAFayt8y5_97RwWHE8Dv8ssfwyh6zU7xI0,25836
|
4
4
|
csc_cia_stne/email.py,sha256=RK_TzWBVnUfpP-s5NvjTJJjzhICy8e2fME9EuaiySMY,8162
|
5
5
|
csc_cia_stne/gcp_bigquery.py,sha256=jYxvqrWDOPkxc05U4aef7V5lL8ptqsE93lfn0dLFyvc,7385
|
6
6
|
csc_cia_stne/google_drive.py,sha256=lgcOd27vk2Mb_wP_fAWIbec-S3MIBKyh4TpRth6REXc,12788
|
@@ -26,8 +26,8 @@ csc_cia_stne/utilitarios/validations/GoogleDriveValidator.py,sha256=PBo-AV2bjR__
|
|
26
26
|
csc_cia_stne/utilitarios/validations/ServiceNowValidator.py,sha256=yleKUIo1ZfyloP9fDPNjv3JJXdLcocT81WIgRSYmqEA,14423
|
27
27
|
csc_cia_stne/utilitarios/validations/__init__.py,sha256=O_qyEU2ji3u6LHUXZCXvUFsMpoMWL625qqHTXyXivTA,106
|
28
28
|
csc_cia_stne/utilitarios/validations/web_validator.py,sha256=HYKYSpDv1RvRjZIuwTPt-AbEz-9392MxM_O329iYuSA,5722
|
29
|
-
csc_cia_stne-0.0.
|
30
|
-
csc_cia_stne-0.0.
|
31
|
-
csc_cia_stne-0.0.
|
32
|
-
csc_cia_stne-0.0.
|
33
|
-
csc_cia_stne-0.0.
|
29
|
+
csc_cia_stne-0.0.66.dist-info/LICENCE,sha256=LPGMtgKki2C3KEZP7hDhA1HBrlq5JCHkIeStUCLEMx4,1073
|
30
|
+
csc_cia_stne-0.0.66.dist-info/METADATA,sha256=xTDZdZe5LPIZRHdvcJ3Vhk0C6KLrIXMopa7JXmSdXaY,1312
|
31
|
+
csc_cia_stne-0.0.66.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
32
|
+
csc_cia_stne-0.0.66.dist-info/top_level.txt,sha256=ldo7GVv3tQx5KJvwBzdZzzQmjPys2NDVVn1rv0BOF2Q,13
|
33
|
+
csc_cia_stne-0.0.66.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|