documente_shared 0.1.201__tar.gz → 0.1.203b0__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.
Files changed (133) hide show
  1. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/PKG-INFO +1 -1
  2. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/layout.py +8 -0
  3. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/workspace_document.py +1 -7
  4. documente_shared-0.1.203b0/documente_shared/domain/helpers/values.py +13 -0
  5. documente_shared-0.1.203b0/documente_shared/infrastructure/lambda_job_results.py +39 -0
  6. documente_shared-0.1.203b0/documente_shared/infrastructure/lambdas.py +69 -0
  7. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/s3_bucket.py +19 -1
  8. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/pyproject.toml +1 -1
  9. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/uv.lock +25 -25
  10. documente_shared-0.1.201/documente_shared/domain/entities/uif_registry.py +0 -96
  11. documente_shared-0.1.201/documente_shared/domain/helpers/values.py +0 -62
  12. documente_shared-0.1.201/documente_shared/domain/repositories/uif_registry.py +0 -13
  13. documente_shared-0.1.201/documente_shared/infrastructure/lambdas.py +0 -15
  14. documente_shared-0.1.201/documente_shared/infrastructure/repositories/http_uif_registry.py +0 -47
  15. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/.claude/commands/commit.md +0 -0
  16. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/.github/workflows/publish.yml +0 -0
  17. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/.gitignore +0 -0
  18. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/.vscode/settings.json +0 -0
  19. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/Dockerfile +0 -0
  20. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/Makefile +0 -0
  21. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/README.md +0 -0
  22. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/__init__.py +0 -0
  23. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/cases.py +0 -0
  24. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/docker-compose.yml +0 -0
  25. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/__init__.py +0 -0
  26. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/__init__.py +0 -0
  27. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/dates.py +0 -0
  28. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/digest.py +0 -0
  29. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/exceptions.py +0 -0
  30. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/files.py +0 -0
  31. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/json.py +0 -0
  32. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/logging.py +0 -0
  33. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/numbers.py +0 -0
  34. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/payloads.py +0 -0
  35. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/query_params.py +0 -0
  36. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/retry_utils.py +0 -0
  37. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/time_utils.py +0 -0
  38. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/application/timezone.py +0 -0
  39. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/__init__.py +0 -0
  40. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/base_enum.py +0 -0
  41. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/constants.py +0 -0
  42. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/__init__.py +0 -0
  43. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/document.py +0 -0
  44. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/document_metadata.py +0 -0
  45. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/document_type.py +0 -0
  46. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/in_memory_document.py +0 -0
  47. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_case.py +0 -0
  48. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_case_filters.py +0 -0
  49. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_case_item.py +0 -0
  50. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_case_item_filters.py +0 -0
  51. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_documents.py +0 -0
  52. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_event.py +0 -0
  53. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/processing_record.py +0 -0
  54. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/scaling.py +0 -0
  55. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/workspace.py +0 -0
  56. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/entities/workspace_document_page.py +0 -0
  57. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/__init__.py +0 -0
  58. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/circular_oficio.py +0 -0
  59. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/common.py +0 -0
  60. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/document.py +0 -0
  61. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/document_type_record.py +0 -0
  62. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/processing_case.py +0 -0
  63. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/processing_case_validator.py +0 -0
  64. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/vnext.py +0 -0
  65. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/enums/workspace.py +0 -0
  66. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/exceptions.py +0 -0
  67. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/filters/__init__.py +0 -0
  68. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/filters/document_type.py +0 -0
  69. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/filters/workspace.py +0 -0
  70. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/filters/workspace_document.py +0 -0
  71. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/filters/workspace_document_page.py +0 -0
  72. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/helpers/__init__.py +0 -0
  73. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/helpers/dicts.py +0 -0
  74. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/helpers/encoding.py +0 -0
  75. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/helpers/lists.py +0 -0
  76. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/interfaces/__init__.py +0 -0
  77. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/interfaces/queue.py +0 -0
  78. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/interfaces/scaling.py +0 -0
  79. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/pagination/__init__.py +0 -0
  80. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/pagination/entities.py +0 -0
  81. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/pagination/response.py +0 -0
  82. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/__init__.py +0 -0
  83. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/document.py +0 -0
  84. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/document_type.py +0 -0
  85. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/processing_case.py +0 -0
  86. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/processing_case_item.py +0 -0
  87. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/processing_record.py +0 -0
  88. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/workspace.py +0 -0
  89. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/workspace_document.py +0 -0
  90. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/domain/repositories/workspace_document_page.py +0 -0
  91. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/__init__.py +0 -0
  92. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/batch_queue.py +0 -0
  93. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/documente_client.py +0 -0
  94. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/dynamo_table.py +0 -0
  95. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/__init__.py +0 -0
  96. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/dynamo_document.py +0 -0
  97. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/dynamo_processing_case.py +0 -0
  98. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/dynamo_processing_case_item.py +0 -0
  99. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_document.py +0 -0
  100. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_document_type.py +0 -0
  101. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_processing_case.py +0 -0
  102. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_processing_case_item.py +0 -0
  103. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_processing_record.py +0 -0
  104. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_workspace.py +0 -0
  105. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_workspace_document.py +0 -0
  106. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/http_workspace_document_page.py +0 -0
  107. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/mem_document.py +0 -0
  108. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/mem_processing_case.py +0 -0
  109. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/mem_processing_case_item.py +0 -0
  110. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/repositories/mem_processing_record.py +0 -0
  111. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/services/__init__.py +0 -0
  112. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/services/http_scaling.py +0 -0
  113. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/infrastructure/sqs_queue.py +0 -0
  114. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/presentation/__init__.py +0 -0
  115. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/documente_shared/presentation/presenters.py +0 -0
  116. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/publish.sh +0 -0
  117. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/__init__.py +0 -0
  118. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/__init__.py +0 -0
  119. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/application/__init__.py +0 -0
  120. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/application/test_files.py +0 -0
  121. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/application/test_payloads.py +0 -0
  122. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/conftest.py +0 -0
  123. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/__init__.py +0 -0
  124. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/entities/__init__.py +0 -0
  125. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/entities/test_in_memory_document.py +0 -0
  126. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/entities/test_processing_case.py +0 -0
  127. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/entities/test_processing_case_item.py +0 -0
  128. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/entities/test_processing_record.py +0 -0
  129. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/domain/enums/test_processing_case_validator.py +0 -0
  130. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/infrastructure/__init__.py +0 -0
  131. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/infrastructure/repositories/__init__.py +0 -0
  132. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documente_shared/infrastructure/repositories/test_http_processing_record.py +0 -0
  133. {documente_shared-0.1.201 → documente_shared-0.1.203b0}/tests/documents.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: documente_shared
3
- Version: 0.1.201
3
+ Version: 0.1.203b0
4
4
  Summary: Shared utilities for LlamitAI projects
5
5
  Author-email: Tech <tech@llamitai.com>
6
6
  License: MIT
@@ -75,3 +75,11 @@ class PageRange(BaseModel):
75
75
  end: Optional[int] = None
76
76
 
77
77
  model_config = ConfigDict(extra="ignore")
78
+
79
+ @property
80
+ def label(self) -> str:
81
+ # Espeja PageRange.label de las lambdas, que lo usan para nombrar el resultado en S3:
82
+ # start ausente es 1, end ausente es "end".
83
+ start_page = self.start if self.start is not None else 1
84
+ end_page = self.end if self.end is not None else "end"
85
+ return f"{start_page}-{end_page}"
@@ -27,7 +27,6 @@ class WorkspaceDocument(BaseModel):
27
27
  processing_time: Decimal | None = Field(default=None)
28
28
  metadata: dict | None = Field(default_factory=dict)
29
29
  extraction: dict | None = Field(default_factory=dict)
30
- mapped_output: dict | None = Field(default=None)
31
30
  uploaded_at: datetime | None = Field(default=None)
32
31
  created_at: datetime | None = Field(default=None)
33
32
  updated_at: datetime | None = Field(default=None)
@@ -37,7 +36,7 @@ class WorkspaceDocument(BaseModel):
37
36
 
38
37
  @property
39
38
  def to_persist_dict(self) -> dict:
40
- data = {
39
+ return {
41
40
  "tenant_id": self.tenant_id,
42
41
  "workspace_id": str(self.workspace_id),
43
42
  "document_type_id": optional_str(self.document_type_id),
@@ -56,9 +55,6 @@ class WorkspaceDocument(BaseModel):
56
55
  "metadata": self.metadata or {},
57
56
  "extraction": self.extraction or {},
58
57
  }
59
- if self.mapped_output is not None:
60
- data["mapped_output"] = self.mapped_output
61
- return data
62
58
 
63
59
  @property
64
60
  def to_dict(self) -> dict:
@@ -78,7 +74,6 @@ class WorkspaceDocument(BaseModel):
78
74
  "processing_time": optional_str(self.processing_time),
79
75
  "metadata": self.metadata,
80
76
  "extraction": self.extraction,
81
- "mapped_output": self.mapped_output,
82
77
  "uploaded_at": optional_datetime_str(self.uploaded_at),
83
78
  "started_at": optional_datetime_str(self.started_at),
84
79
  "completed_at": optional_datetime_str(self.completed_at),
@@ -106,7 +101,6 @@ class WorkspaceDocument(BaseModel):
106
101
  ),
107
102
  metadata=data.get("metadata", {}),
108
103
  extraction=data.get("extraction", {}),
109
- mapped_output=data.get("mapped_output", {}),
110
104
  uploaded_at=get_datetime_from_data(input_datetime=data.get("uploaded_at")),
111
105
  created_at=get_datetime_from_data(input_datetime=data.get("created_at")),
112
106
  updated_at=get_datetime_from_data(input_datetime=data.get("updated_at")),
@@ -0,0 +1,13 @@
1
+ from datetime import datetime
2
+
3
+
4
+ def optional_str(input_value: object | None = None) -> str | None:
5
+ if input_value is None:
6
+ return None
7
+ return str(input_value)
8
+
9
+
10
+ def optional_datetime_str(input_datetime: datetime | None = None) -> str | None:
11
+ if input_datetime is None:
12
+ return None
13
+ return input_datetime.isoformat()
@@ -0,0 +1,39 @@
1
+ import time
2
+
3
+ from documente_shared.infrastructure.s3_bucket import S3Bucket
4
+
5
+ DEFAULT_JOB_RESULT_TIMEOUT = 960
6
+ DEFAULT_JOB_RESULT_POLL_INTERVAL = 5
7
+
8
+
9
+ def build_job_result_key(job_id: str, step_name: str) -> str:
10
+ # Contrato de S3JobResultStore en las lambdas: el mismo output_uri que devuelve la invocacion
11
+ # sincronica, construido aca para que la asincronica no dependa de una respuesta.
12
+ return f"jobs/{job_id}/{step_name}.json"
13
+
14
+
15
+ def discard_previous_job_result(s3_bucket: S3Bucket, job_result_key: str) -> None:
16
+ # Sin esto la espera puede leer el resultado de una corrida anterior y devolverlo como propio.
17
+ # Lo que se borra es un artefacto derivado que la lambda reescribe en cada corrida, incluso en
18
+ # cache hit: su cache real no vive en este archivo.
19
+ s3_bucket.delete(file_key=job_result_key)
20
+
21
+
22
+ def wait_for_job_result(
23
+ s3_bucket: S3Bucket,
24
+ job_result_key: str,
25
+ timeout_seconds: int = DEFAULT_JOB_RESULT_TIMEOUT,
26
+ poll_interval_seconds: float = DEFAULT_JOB_RESULT_POLL_INTERVAL,
27
+ ) -> dict:
28
+ deadline = time.monotonic() + timeout_seconds
29
+ while True:
30
+ job_result = s3_bucket.get_json(file_key=job_result_key)
31
+ if job_result is not None:
32
+ return job_result
33
+ if time.monotonic() >= deadline:
34
+ timeout_message = (
35
+ f"Lambda job result not found after {timeout_seconds}s: "
36
+ f"s3://{s3_bucket.bucket_name}/{job_result_key}"
37
+ )
38
+ raise TimeoutError(timeout_message)
39
+ time.sleep(poll_interval_seconds)
@@ -0,0 +1,69 @@
1
+ import json
2
+ from functools import cache
3
+
4
+ import boto3
5
+ from botocore.config import Config
6
+
7
+ # Synchronous invocations travel through a PrivateLink interface endpoint, whose idle timeout is a
8
+ # hard, non configurable 350s. A synchronous invoke leaves the flow completely idle while the
9
+ # function runs, so past that point the flow is silently dropped and the response can never arrive.
10
+ # Waiting any longer than the endpoint allows only delays a failure that already happened.
11
+ DEFAULT_LAMBDA_READ_TIMEOUT = 350
12
+
13
+ # An asynchronous invocation only enqueues the event, so the call returns in milliseconds no matter
14
+ # how long the function then runs.
15
+ DEFAULT_LAMBDA_ENQUEUE_TIMEOUT = 60
16
+
17
+
18
+ @cache
19
+ def _get_lambda_client(read_timeout: int):
20
+ # No retries: the read timeout is shorter than the function timeout, so a read timeout means the
21
+ # invocation is still running on the other side. Retrying it would start a second execution of a
22
+ # function that is not idempotent.
23
+ lambda_config = Config(
24
+ read_timeout=read_timeout,
25
+ connect_timeout=30,
26
+ tcp_keepalive=True,
27
+ retries={"max_attempts": 0, "mode": "standard"},
28
+ )
29
+ return boto3.client("lambda", config=lambda_config)
30
+
31
+
32
+ def invoke_lambda(
33
+ function_name: str,
34
+ payload: dict,
35
+ read_timeout: int = DEFAULT_LAMBDA_READ_TIMEOUT,
36
+ ) -> dict | list | None:
37
+ client = _get_lambda_client(read_timeout)
38
+
39
+ response = client.invoke(
40
+ FunctionName=function_name,
41
+ InvocationType="RequestResponse",
42
+ Payload=json.dumps(payload),
43
+ )
44
+
45
+ return json.loads(response["Payload"].read().decode("utf-8"))
46
+
47
+
48
+ def invoke_lambda_async(
49
+ function_name: str,
50
+ payload: dict,
51
+ read_timeout: int = DEFAULT_LAMBDA_ENQUEUE_TIMEOUT,
52
+ ) -> int:
53
+ # There is no response payload to wait for, so the caller never holds a connection open
54
+ # while the function runs. The result travels out of band: the function writes it to
55
+ # its own deterministic output location and the caller reads it from there.
56
+ #
57
+ # Lambda retries a failed asynchronous invocation by itself (MaximumRetryAttempts defaults to 2
58
+ # on the function's event invoke config) and silently drops events it cannot deliver. Both are
59
+ # function side settings, invisible from here, so anything invoked this way has to tolerate
60
+ # running more than once for the same payload.
61
+ client = _get_lambda_client(read_timeout)
62
+
63
+ response = client.invoke(
64
+ FunctionName=function_name,
65
+ InvocationType="Event",
66
+ Payload=json.dumps(payload),
67
+ )
68
+
69
+ return response["StatusCode"]
@@ -1,12 +1,18 @@
1
- import boto3
1
+ import json
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from typing import Optional
5
5
 
6
+ import boto3
7
+ from botocore.exceptions import ClientError
8
+
6
9
  from documente_shared.domain.entities.document import remove_slash_from_path
7
10
  from documente_shared.application.retry_utils import retry_on_size_integrity
8
11
 
9
12
 
13
+ MISSING_OBJECT_ERROR_CODES = ("NoSuchKey", "404")
14
+
15
+
10
16
  def remove_none_values(data: dict) -> dict: # noqa: WPS110
11
17
  return {key: value for key, value in data.items() if value is not None} # noqa: WPS110
12
18
 
@@ -39,6 +45,18 @@ class S3Bucket(object):
39
45
  return None
40
46
  return file_context["Body"] if "Body" in file_context else None
41
47
 
48
+ def get_json(self, file_key: str) -> Optional[dict]:
49
+ # Sin el retry de get(): aca la ausencia del objeto es una respuesta valida y no algo que
50
+ # convenga reintentar, asi que se puede usar para preguntar si un resultado ya esta.
51
+ cleaned_file_key = remove_slash_from_path(file_key)
52
+ try:
53
+ file_context = self._resource.Object(self.bucket_name, cleaned_file_key).get()
54
+ except ClientError as client_error:
55
+ if client_error.response["Error"]["Code"] in MISSING_OBJECT_ERROR_CODES:
56
+ return None
57
+ raise
58
+ return json.loads(file_context["Body"].read().decode("utf-8"))
59
+
42
60
  def upload(self, file_key: str, file_content, content_type: Optional[str] = None):
43
61
  cleaned_file_key = remove_slash_from_path(file_key)
44
62
  optional_params = {"ContentType": content_type}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "documente_shared"
3
- version = "0.1.201"
3
+ version = "0.1.203b"
4
4
  description = "Shared utilities for LlamitAI projects"
5
5
  authors = [{ name = "Tech", email = "tech@llamitai.com" }]
6
6
  license = { text = "MIT" }
@@ -154,7 +154,7 @@ wheels = [
154
154
 
155
155
  [[package]]
156
156
  name = "documente-shared"
157
- version = "0.1.201"
157
+ version = "0.1.203b0"
158
158
  source = { editable = "." }
159
159
  dependencies = [
160
160
  { name = "boto3" },
@@ -200,7 +200,7 @@ name = "exceptiongroup"
200
200
  version = "1.3.1"
201
201
  source = { registry = "https://pypi.org/simple" }
202
202
  dependencies = [
203
- { name = "typing-extensions" },
203
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
204
204
  ]
205
205
  sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
206
206
  wheels = [
@@ -263,17 +263,17 @@ resolution-markers = [
263
263
  "python_full_version < '3.11'",
264
264
  ]
265
265
  dependencies = [
266
- { name = "colorama", marker = "sys_platform == 'win32'" },
267
- { name = "decorator" },
268
- { name = "exceptiongroup" },
269
- { name = "jedi" },
270
- { name = "matplotlib-inline" },
271
- { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
272
- { name = "prompt-toolkit" },
273
- { name = "pygments" },
274
- { name = "stack-data" },
275
- { name = "traitlets" },
276
- { name = "typing-extensions" },
266
+ { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" },
267
+ { name = "decorator", marker = "python_full_version < '3.11'" },
268
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
269
+ { name = "jedi", marker = "python_full_version < '3.11'" },
270
+ { name = "matplotlib-inline", marker = "python_full_version < '3.11'" },
271
+ { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
272
+ { name = "prompt-toolkit", marker = "python_full_version < '3.11'" },
273
+ { name = "pygments", marker = "python_full_version < '3.11'" },
274
+ { name = "stack-data", marker = "python_full_version < '3.11'" },
275
+ { name = "traitlets", marker = "python_full_version < '3.11'" },
276
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
277
277
  ]
278
278
  sdist = { url = "https://files.pythonhosted.org/packages/e5/61/1810830e8b93c72dcd3c0f150c80a00c3deb229562d9423807ec92c3a539/ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39", size = 5513996, upload-time = "2026-01-05T10:59:06.901Z" }
279
279
  wheels = [
@@ -288,17 +288,17 @@ resolution-markers = [
288
288
  "python_full_version >= '3.11'",
289
289
  ]
290
290
  dependencies = [
291
- { name = "colorama", marker = "sys_platform == 'win32'" },
292
- { name = "decorator" },
293
- { name = "ipython-pygments-lexers" },
294
- { name = "jedi" },
295
- { name = "matplotlib-inline" },
296
- { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
297
- { name = "prompt-toolkit" },
298
- { name = "pygments" },
299
- { name = "stack-data" },
300
- { name = "traitlets" },
301
- { name = "typing-extensions", marker = "python_full_version < '3.12'" },
291
+ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
292
+ { name = "decorator", marker = "python_full_version >= '3.11'" },
293
+ { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" },
294
+ { name = "jedi", marker = "python_full_version >= '3.11'" },
295
+ { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" },
296
+ { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
297
+ { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" },
298
+ { name = "pygments", marker = "python_full_version >= '3.11'" },
299
+ { name = "stack-data", marker = "python_full_version >= '3.11'" },
300
+ { name = "traitlets", marker = "python_full_version >= '3.11'" },
301
+ { name = "typing-extensions", marker = "python_full_version == '3.11.*'" },
302
302
  ]
303
303
  sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" }
304
304
  wheels = [
@@ -310,7 +310,7 @@ name = "ipython-pygments-lexers"
310
310
  version = "1.1.1"
311
311
  source = { registry = "https://pypi.org/simple" }
312
312
  dependencies = [
313
- { name = "pygments" },
313
+ { name = "pygments", marker = "python_full_version >= '3.11'" },
314
314
  ]
315
315
  sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
316
316
  wheels = [
@@ -1,96 +0,0 @@
1
- from dataclasses import dataclass, field
2
- from datetime import date, datetime
3
- from decimal import Decimal
4
- from uuid import UUID, uuid4
5
-
6
- from documente_shared.application.payloads import camel_to_snake, snake_to_camel
7
- from documente_shared.domain.helpers.values import (
8
- optional_date,
9
- optional_datetime,
10
- optional_decimal,
11
- optional_text,
12
- optional_uuid,
13
- )
14
-
15
-
16
- @dataclass
17
- class UIFRegistry:
18
- uuid: UUID = field(default_factory=uuid4)
19
- paternal_last_name: str | None = None
20
- maternal_last_name: str | None = None
21
- first_name: str | None = None
22
- identity_document_number: str | None = None
23
- identity_document_extension: str | None = None
24
- court_name: str | None = None
25
- process_type: str | None = None
26
- circular_id: str | None = None
27
- circular_issue_date: date | None = None
28
- involved_amount: Decimal | None = None
29
- relation_type: str | None = None
30
- due_diligence: bool | None = None
31
- illicit_gains_legitimation: bool | None = None
32
- document_digest: str | None = None
33
- created_at: datetime | None = None
34
-
35
- @property
36
- def full_name(self) -> str:
37
- parts = [self.paternal_last_name, self.maternal_last_name, self.first_name]
38
- return " ".join(part for part in parts if part)
39
-
40
- @property
41
- def is_identifiable(self) -> bool:
42
- return bool(self.full_name)
43
-
44
- @property
45
- def to_dict(self) -> dict:
46
- return {
47
- "uuid": str(self.uuid),
48
- "document_digest": self.document_digest,
49
- "paternal_last_name": self.paternal_last_name,
50
- "maternal_last_name": self.maternal_last_name,
51
- "first_name": self.first_name,
52
- "full_name": self.full_name,
53
- "identity_document_number": self.identity_document_number,
54
- "identity_document_extension": self.identity_document_extension,
55
- "court_name": self.court_name,
56
- "process_type": self.process_type,
57
- "circular_id": self.circular_id,
58
- "circular_issue_date": (
59
- self.circular_issue_date.isoformat() if self.circular_issue_date else None
60
- ),
61
- "involved_amount": (
62
- str(self.involved_amount) if self.involved_amount is not None else None
63
- ),
64
- "relation_type": self.relation_type,
65
- "due_diligence": self.due_diligence,
66
- "illicit_gains_legitimation": self.illicit_gains_legitimation,
67
- "created_at": self.created_at.isoformat() if self.created_at else None,
68
- }
69
-
70
- @property
71
- def to_payload(self) -> dict:
72
- return snake_to_camel(self.to_dict)
73
-
74
- @classmethod
75
- def from_dict(cls, data: dict) -> "UIFRegistry":
76
- normalized = camel_to_snake(data)
77
- return cls(
78
- uuid=optional_uuid(normalized.get("uuid")) or uuid4(),
79
- document_digest=optional_text(normalized.get("document_digest")),
80
- paternal_last_name=optional_text(normalized.get("paternal_last_name")),
81
- maternal_last_name=optional_text(normalized.get("maternal_last_name")),
82
- first_name=optional_text(normalized.get("first_name")),
83
- identity_document_number=optional_text(normalized.get("identity_document_number")),
84
- identity_document_extension=optional_text(
85
- normalized.get("identity_document_extension")
86
- ),
87
- court_name=optional_text(normalized.get("court_name")),
88
- process_type=optional_text(normalized.get("process_type")),
89
- circular_id=optional_text(normalized.get("circular_id")),
90
- circular_issue_date=optional_date(normalized.get("circular_issue_date")),
91
- involved_amount=optional_decimal(normalized.get("involved_amount")),
92
- relation_type=optional_text(normalized.get("relation_type")),
93
- due_diligence=normalized.get("due_diligence"),
94
- illicit_gains_legitimation=normalized.get("illicit_gains_legitimation"),
95
- created_at=optional_datetime(normalized.get("created_at")),
96
- )
@@ -1,62 +0,0 @@
1
- from datetime import date, datetime
2
- from decimal import Decimal, InvalidOperation
3
- from uuid import UUID
4
-
5
-
6
- def optional_str(input_value: object | None = None) -> str | None:
7
- if input_value is None:
8
- return None
9
- return str(input_value)
10
-
11
-
12
- def optional_text(input_value: object | None = None) -> str | None:
13
- if input_value is None:
14
- return None
15
- text = str(input_value).strip()
16
- return text or None
17
-
18
-
19
- def optional_datetime_str(input_datetime: datetime | None = None) -> str | None:
20
- if input_datetime is None:
21
- return None
22
- return input_datetime.isoformat()
23
-
24
-
25
- def optional_uuid(input_value: object | None = None) -> UUID | None:
26
- if input_value is None or isinstance(input_value, UUID):
27
- return input_value
28
- try:
29
- return UUID(str(input_value))
30
- except ValueError:
31
- return None
32
-
33
-
34
- def optional_date(input_value: object | None = None) -> date | None:
35
- if input_value is None:
36
- return None
37
- if isinstance(input_value, datetime):
38
- return input_value.date()
39
- if isinstance(input_value, date):
40
- return input_value
41
- try:
42
- return date.fromisoformat(str(input_value))
43
- except ValueError:
44
- return None
45
-
46
-
47
- def optional_datetime(input_value: object | None = None) -> datetime | None:
48
- if input_value is None or isinstance(input_value, datetime):
49
- return input_value
50
- try:
51
- return datetime.fromisoformat(str(input_value))
52
- except ValueError:
53
- return None
54
-
55
-
56
- def optional_decimal(input_value: object | None = None) -> Decimal | None:
57
- if input_value is None or isinstance(input_value, Decimal):
58
- return input_value
59
- try:
60
- return Decimal(str(input_value))
61
- except InvalidOperation:
62
- return None
@@ -1,13 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from documente_shared.domain.entities.uif_registry import UIFRegistry
4
-
5
-
6
- class UIFRegistryRepository(ABC):
7
- @abstractmethod
8
- def set_document_uif_registries(
9
- self,
10
- document_digest: str,
11
- registries: list[UIFRegistry],
12
- ) -> list[UIFRegistry]:
13
- raise NotImplementedError
@@ -1,15 +0,0 @@
1
- import json
2
- import boto3
3
- from botocore.config import Config
4
-
5
-
6
- def invoke_lambda(function_name: str, payload: dict, read_timeout: int = 60) -> dict | list | None:
7
- client = boto3.client("lambda", config=Config(read_timeout=read_timeout))
8
-
9
- response = client.invoke(
10
- FunctionName=function_name,
11
- InvocationType="RequestResponse",
12
- Payload=json.dumps(payload),
13
- )
14
-
15
- return json.loads(response["Payload"].read().decode("utf-8"))
@@ -1,47 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import TYPE_CHECKING
3
-
4
- import structlog
5
-
6
- from documente_shared.domain.entities.uif_registry import UIFRegistry
7
- from documente_shared.domain.repositories.uif_registry import UIFRegistryRepository
8
- from documente_shared.infrastructure.documente_client import DocumenteClientMixin
9
-
10
- if TYPE_CHECKING:
11
- from requests import Response
12
-
13
- logger = structlog.get_logger()
14
-
15
-
16
- @dataclass
17
- class HttpUIFRegistryRepository(
18
- DocumenteClientMixin,
19
- UIFRegistryRepository,
20
- ):
21
- def set_document_uif_registries(
22
- self,
23
- document_digest: str,
24
- registries: list[UIFRegistry],
25
- ) -> list[UIFRegistry]:
26
- payload = {
27
- "document_digest": document_digest,
28
- "registries": [registry.to_payload for registry in registries],
29
- }
30
- logger.info(
31
- "[shared.uif_registry.persisting]",
32
- document_digest=document_digest,
33
- registries=len(registries),
34
- )
35
- response: Response = self.session.post(
36
- url=f"{self.api_url}/v1/documents/uif-registry/",
37
- json=payload,
38
- )
39
- if not self._is_success(response):
40
- logger.warning(
41
- "[shared.uif_registry.persist_error]",
42
- document_digest=document_digest,
43
- status_code=response.status_code,
44
- response=response.text,
45
- )
46
- return []
47
- return self._parse_list(response, UIFRegistry.from_dict)