libretificacaotjcore 0.1.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.
Potentially problematic release.
This version of libretificacaotjcore might be problematic. Click here for more details.
- libretificacaotjcore/__init__.py +0 -0
- libretificacaotjcore/database/__init__.py +1 -0
- libretificacaotjcore/database/arquivo_repository.py +31 -0
- libretificacaotjcore/database/config_db.py +33 -0
- libretificacaotjcore/dtos/solicitacao_dto.py +63 -0
- libretificacaotjcore/services/__init__.py +0 -0
- libretificacaotjcore/services/rabbitmq_consumer.py +54 -0
- libretificacaotjcore/services/s3_service.py +83 -0
- libretificacaotjcore-0.1.0.dist-info/METADATA +27 -0
- libretificacaotjcore-0.1.0.dist-info/RECORD +12 -0
- libretificacaotjcore-0.1.0.dist-info/WHEEL +5 -0
- libretificacaotjcore-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class ArquivoRepository:
|
|
2
|
+
def __init__(self, db):
|
|
3
|
+
self.__db = db
|
|
4
|
+
|
|
5
|
+
async def inserir_arquivo(self, arquivo: dict) -> bool:
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
arquivo_no_db = await self.__db.arquivos.find_one(
|
|
9
|
+
{"SolicitacaoId": arquivo["solicitacaoId"], "cpf": arquivo["cpf"]}
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
if arquivo_no_db is None:
|
|
13
|
+
await self.__db.arquivos.insert_one(arquivo)
|
|
14
|
+
return True
|
|
15
|
+
|
|
16
|
+
await self.__db.arquivos.delete_one(
|
|
17
|
+
{"SolicitacaoId": arquivo["solicitacaoId"], "cpf": arquivo["cpf"]}
|
|
18
|
+
)
|
|
19
|
+
await self.__db.arquivos.insert_one(arquivo)
|
|
20
|
+
return True
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(f"❌ Erro ao inserir o arquivo: {e}")
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
async def remover_arquivo(self, solicitacaoId: int) -> bool:
|
|
26
|
+
try:
|
|
27
|
+
await self.__db.arquivos.delete_many({"SolicitacaoId": solicitacaoId})
|
|
28
|
+
return True
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"❌ Erro ao remover o arquivo: {e}")
|
|
31
|
+
return False
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import motor.motor_asyncio
|
|
2
|
+
from bson import ObjectId
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
class ConfigDb:
|
|
6
|
+
def __init__(self, host: str, user: str, password: str, port: int, db_name: str):
|
|
7
|
+
self.host = host
|
|
8
|
+
self.user = user
|
|
9
|
+
self.password = password
|
|
10
|
+
self.port = port
|
|
11
|
+
self.db_name = db_name
|
|
12
|
+
|
|
13
|
+
self.client = motor.motor_asyncio.AsyncIOMotorClient(
|
|
14
|
+
f"mongodb://{self.host}:{self.port}"
|
|
15
|
+
)
|
|
16
|
+
self.db = self.client[self.db_name]
|
|
17
|
+
self.db_initialized = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def criar_schema(self):
|
|
21
|
+
global _db_initialized
|
|
22
|
+
if not self.db_initialized:
|
|
23
|
+
if "arquivos" not in (await self.db.list_collection_names()):
|
|
24
|
+
await self.db.create_collection("arquivos")
|
|
25
|
+
|
|
26
|
+
await self.db.arquivos.create_index([("cnpj", 1)])
|
|
27
|
+
await self.db.arquivos.create_index([("SolicitacaoId", 1)])
|
|
28
|
+
await self.db.arquivos.create_index([("id", 1)], unique=True)
|
|
29
|
+
self.db_initialized = True
|
|
30
|
+
|
|
31
|
+
async def get_db(self):
|
|
32
|
+
await self.criar_schema()
|
|
33
|
+
return self.db
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from datetime import datetime, date, timedelta
|
|
2
|
+
import re
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SolicitacaoDTO(BaseModel):
|
|
7
|
+
solicitacaoId: int = Field(..., description="ID da solicitação")
|
|
8
|
+
cnpj: str = Field(..., description="CNPJ da empresa")
|
|
9
|
+
dataInicio: str = Field(..., description="Data de início no formato YYYY-MM-DD")
|
|
10
|
+
dataFim: str = Field(..., description="Data de fim no formato YYYY-MM-DD")
|
|
11
|
+
certificadoId: int = Field(..., description="ID do certificado")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Validar e transformar solicitacaoId
|
|
15
|
+
@field_validator('solicitacaoId')
|
|
16
|
+
@classmethod
|
|
17
|
+
def validar_solicitacao_id(cls, v: int) -> int:
|
|
18
|
+
if v <= 0:
|
|
19
|
+
raise ValueError("O solicitacaoId deve ser um inteiro positivo.")
|
|
20
|
+
return v
|
|
21
|
+
|
|
22
|
+
# Validar e transformar certificadoId
|
|
23
|
+
@field_validator('certificadoId')
|
|
24
|
+
@classmethod
|
|
25
|
+
def validar_certificado_id(cls, v: int) -> int:
|
|
26
|
+
if v <= 0:
|
|
27
|
+
raise ValueError("O certificadoId deve ser um inteiro positivo.")
|
|
28
|
+
return v
|
|
29
|
+
|
|
30
|
+
# Validar CNPJ (apenas estrutura com 14 dígitos)
|
|
31
|
+
@field_validator('cnpj')
|
|
32
|
+
@classmethod
|
|
33
|
+
def validar_cnpj(cls, v: str) -> str:
|
|
34
|
+
cnpj_limpo = re.sub(r'\D', '', v)
|
|
35
|
+
if len(cnpj_limpo) != 14 or not cnpj_limpo.isdigit():
|
|
36
|
+
raise ValueError("O CNPJ deve conter 14 dígitos numéricos.")
|
|
37
|
+
return cnpj_limpo
|
|
38
|
+
|
|
39
|
+
# Validar e transformar dataInicio
|
|
40
|
+
@field_validator('dataInicio', mode='before')
|
|
41
|
+
@classmethod
|
|
42
|
+
def formatar_data_inicio(cls, value: str) -> date:
|
|
43
|
+
try:
|
|
44
|
+
return datetime.strptime(value, "%Y-%m-%d").date()
|
|
45
|
+
except ValueError:
|
|
46
|
+
raise ValueError("A dataInicio deve estar no formato YYYY-MM-DD.")
|
|
47
|
+
|
|
48
|
+
# Validar e transformar dataFim (último dia do mês)
|
|
49
|
+
@field_validator('dataFim', mode='before')
|
|
50
|
+
@classmethod
|
|
51
|
+
def ajustar_data_fim(cls, value: str) -> date:
|
|
52
|
+
try:
|
|
53
|
+
ano, mes = map(int, value.split("-")[:2])
|
|
54
|
+
if mes < 1 or mes > 12:
|
|
55
|
+
raise ValueError
|
|
56
|
+
if mes == 12:
|
|
57
|
+
proximo_mes = datetime(ano + 1, 1, 1)
|
|
58
|
+
else:
|
|
59
|
+
proximo_mes = datetime(ano, mes + 1, 1)
|
|
60
|
+
return (proximo_mes - timedelta(days=1)).date()
|
|
61
|
+
except Exception:
|
|
62
|
+
raise ValueError("A dataFim deve estar no formato YYYY-MM-DD e conter um mês válido.")
|
|
63
|
+
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import pika
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RabbitMQConsumer:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
host,
|
|
9
|
+
queue,
|
|
10
|
+
username,
|
|
11
|
+
password,
|
|
12
|
+
vhost="/",
|
|
13
|
+
):
|
|
14
|
+
self.host = host
|
|
15
|
+
self.queue = queue
|
|
16
|
+
self.username = username
|
|
17
|
+
self.password = password
|
|
18
|
+
self.vhost = vhost
|
|
19
|
+
self.connection = None
|
|
20
|
+
self.channel = None
|
|
21
|
+
|
|
22
|
+
def connect(self):
|
|
23
|
+
credentials = pika.PlainCredentials(self.username, self.password)
|
|
24
|
+
parameters = pika.ConnectionParameters(
|
|
25
|
+
host=self.host,
|
|
26
|
+
credentials=credentials,
|
|
27
|
+
virtual_host=self.vhost,
|
|
28
|
+
heartbeat=60,
|
|
29
|
+
blocked_connection_timeout=300,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self.connection = pika.BlockingConnection(parameters)
|
|
33
|
+
self.channel = self.connection.channel()
|
|
34
|
+
self.channel.queue_declare(queue=self.queue, durable=True)
|
|
35
|
+
|
|
36
|
+
def start_consuming(self, callback):
|
|
37
|
+
def on_message(ch, method, properties, body):
|
|
38
|
+
mensagem = json.loads(body)
|
|
39
|
+
callback(mensagem)
|
|
40
|
+
ch.basic_ack(delivery_tag=method.delivery_tag)
|
|
41
|
+
|
|
42
|
+
if not self.channel:
|
|
43
|
+
raise RuntimeError("❌ Canal RabbitMQ não conectado. Chame connect() antes.")
|
|
44
|
+
|
|
45
|
+
self.channel.basic_qos(prefetch_count=1)
|
|
46
|
+
self.channel.basic_consume(queue=self.queue, on_message_callback=on_message)
|
|
47
|
+
print(
|
|
48
|
+
f'[*] Aguardando mensagens na fila "{self.queue}". Para sair pressione CTRL+C'
|
|
49
|
+
)
|
|
50
|
+
self.channel.start_consuming()
|
|
51
|
+
|
|
52
|
+
def close(self):
|
|
53
|
+
if self.connection:
|
|
54
|
+
self.connection.close()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import partial
|
|
3
|
+
import os
|
|
4
|
+
import boto3
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
|
|
7
|
+
class S3Service:
|
|
8
|
+
def __init__(self, aws_access_key_id, aws_secret_access_key, region_name, bucket_name, bucket_path):
|
|
9
|
+
self.s3 = boto3.client(
|
|
10
|
+
"s3",
|
|
11
|
+
aws_access_key_id=aws_access_key_id,
|
|
12
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
13
|
+
region_name=region_name,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
self.bucket_name = bucket_name
|
|
17
|
+
self.bucket_path = bucket_path
|
|
18
|
+
|
|
19
|
+
self.loop = asyncio.get_running_loop()
|
|
20
|
+
|
|
21
|
+
async def save_on_s3(self, file_path, file_name):
|
|
22
|
+
try:
|
|
23
|
+
await self.loop.run_in_executor(
|
|
24
|
+
None,
|
|
25
|
+
self.s3.upload_file,
|
|
26
|
+
file_path,
|
|
27
|
+
self.bucket_name,
|
|
28
|
+
self.bucket_path + file_name,
|
|
29
|
+
)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"❌ Erro ao salvar o arquivo no S3: {e}")
|
|
32
|
+
|
|
33
|
+
async def save_many_paths_on_s3(self, file_paths):
|
|
34
|
+
"""
|
|
35
|
+
Salva vários arquivos no S3 informando apenas os caminhos locais.
|
|
36
|
+
O nome do arquivo no S3 será o mesmo do arquivo local.
|
|
37
|
+
"""
|
|
38
|
+
tasks = [
|
|
39
|
+
self.save_on_s3(
|
|
40
|
+
file_path["caminho_arquivo_local"], file_path["nome_arquivo"]
|
|
41
|
+
)
|
|
42
|
+
for file_path in file_paths
|
|
43
|
+
]
|
|
44
|
+
await asyncio.gather(*tasks)
|
|
45
|
+
|
|
46
|
+
async def file_on_s3(self, file_name):
|
|
47
|
+
try:
|
|
48
|
+
head_object_func = partial(
|
|
49
|
+
self.s3.head_object,
|
|
50
|
+
Bucket=self.bucket_name,
|
|
51
|
+
Key=self.bucket_path + file_name,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
await self.loop.run_in_executor(None, head_object_func)
|
|
55
|
+
return True
|
|
56
|
+
except ClientError as e:
|
|
57
|
+
if e.response["Error"]["Code"] == "404":
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
raise
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"❌ Erro ao obter o arquivo do S3: {e}")
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
async def get_file_from_s3(self, file_name, destination_path):
|
|
66
|
+
try:
|
|
67
|
+
os.makedirs(os.path.dirname(destination_path), exist_ok=True)
|
|
68
|
+
|
|
69
|
+
key = self.bucket_path + file_name
|
|
70
|
+
print(f"🔍 Verificando chave: {key}")
|
|
71
|
+
download_func = partial(
|
|
72
|
+
self.s3.download_file, self.bucket_name, key, destination_path
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
await self.loop.run_in_executor(None, download_func)
|
|
76
|
+
print(f"📖✅ Arquivo '{file_name}' baixado com sucesso.")
|
|
77
|
+
except ClientError as e:
|
|
78
|
+
if e.response["Error"]["Code"] == "404":
|
|
79
|
+
print(f"⚠️ Arquivo '{file_name}' não encontrado no S3.")
|
|
80
|
+
else:
|
|
81
|
+
print(f"❌ Erro ao baixar o arquivo do S3: {e}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"❌ Erro inesperado ao baixar o arquivo do S3: {e}")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libretificacaotjcore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Biblioteca para centralizar conexao com filas no rabbit e banco de dados no mongodb para os servicos de retificacao da TJ
|
|
5
|
+
Author-email: Jhonatan Azevedo <dev.azevedo@outlook.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/seu-usuario/libretificacaotjcore
|
|
7
|
+
Project-URL: Issues, https://github.com/seu-usuario/libretificacaotjcore/issues
|
|
8
|
+
Project-URL: Repository, https://github.com/seu-usuario/libretificacaotjcore
|
|
9
|
+
Keywords: tj,tributo justo,retificação,automação,pydantic,rabbitmq,boto3,motor
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: boto3>=1.39.16
|
|
25
|
+
Requires-Dist: motor>=3.7.1
|
|
26
|
+
Requires-Dist: pika>=1.3.2
|
|
27
|
+
Requires-Dist: pydantic>=2.11.7
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
libretificacaotjcore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
libretificacaotjcore/database/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
3
|
+
libretificacaotjcore/database/arquivo_repository.py,sha256=Mt-5IWp7GVbMTAsCdFhi3TkIf2d__ccCGgJI_z-PE2Y,1118
|
|
4
|
+
libretificacaotjcore/database/config_db.py,sha256=bixGQjK-QHk8KAqSadHMHZe1mU57FbVXI0rdW0PvNSs,1178
|
|
5
|
+
libretificacaotjcore/dtos/solicitacao_dto.py,sha256=NEbC-A-OgVicw3WO3YfO5M5RrXoResya_20GGO_eJOM,2458
|
|
6
|
+
libretificacaotjcore/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
libretificacaotjcore/services/rabbitmq_consumer.py,sha256=wj6GmNlWJiUpYRWNhfR_vhpN0tRJi5oKfZDyfpnOSQs,1684
|
|
8
|
+
libretificacaotjcore/services/s3_service.py,sha256=HKR_jt2H3XdV1PCzo5R5bnhmoQ3I46Yn5IqAvVPhsjs,2946
|
|
9
|
+
libretificacaotjcore-0.1.0.dist-info/METADATA,sha256=XFNQ8AFWB7ET883XfDwhV1DD5QGf162q1lEyUz4fSjw,1368
|
|
10
|
+
libretificacaotjcore-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
libretificacaotjcore-0.1.0.dist-info/top_level.txt,sha256=J9vnz_X9OUnxC-eXHiAzlc9xIrWBwZ5bgnIDQIIFY4c,21
|
|
12
|
+
libretificacaotjcore-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
libretificacaotjcore
|