documente_shared 0.1.161b5__tar.gz → 0.1.162__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 (135) hide show
  1. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/.gitignore +2 -1
  2. documente_shared-0.1.162/.vscode/settings.json +11 -0
  3. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/Dockerfile +1 -1
  4. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/Makefile +6 -2
  5. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/PKG-INFO +7 -7
  6. documente_shared-0.1.162/documente_shared/application/dates.py +5 -0
  7. documente_shared-0.1.162/documente_shared/application/logging.py +47 -0
  8. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/query_params.py +0 -0
  9. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/retry_utils.py +24 -7
  10. documente_shared-0.1.162/documente_shared/domain/constants.py +10 -0
  11. documente_shared-0.1.162/documente_shared/domain/entities/document_type.py +49 -0
  12. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/workspace.py +23 -23
  13. documente_shared-0.1.162/documente_shared/domain/entities/workspace_document.py +116 -0
  14. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/workspace_document_page.py +13 -10
  15. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/workspace_filters.py +2 -1
  16. documente_shared-0.1.162/documente_shared/domain/filters/document_type.py +24 -0
  17. documente_shared-0.1.162/documente_shared/domain/filters/workspace.py +28 -0
  18. documente_shared-0.1.162/documente_shared/domain/filters/workspace_document.py +46 -0
  19. documente_shared-0.1.162/documente_shared/domain/filters/workspace_document_page.py +27 -0
  20. documente_shared-0.1.162/documente_shared/domain/helpers/dicts.py +71 -0
  21. documente_shared-0.1.162/documente_shared/domain/helpers/encoding.py +13 -0
  22. documente_shared-0.1.162/documente_shared/domain/helpers/lists.py +113 -0
  23. documente_shared-0.1.162/documente_shared/domain/pagination/entities.py +88 -0
  24. documente_shared-0.1.162/documente_shared/domain/pagination/response.py +17 -0
  25. documente_shared-0.1.162/documente_shared/domain/repositories/document_type.py +44 -0
  26. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/repositories/workspace.py +7 -7
  27. documente_shared-0.1.162/documente_shared/domain/repositories/workspace_document.py +40 -0
  28. documente_shared-0.1.162/documente_shared/domain/repositories/workspace_document_page.py +59 -0
  29. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/batch_queue.py +4 -2
  30. documente_shared-0.1.162/documente_shared/infrastructure/documente_client.py +44 -0
  31. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/http_document.py +4 -2
  32. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/http_processing_case.py +4 -2
  33. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/http_processing_case_item.py +5 -3
  34. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/http_processing_record.py +5 -3
  35. documente_shared-0.1.162/documente_shared/infrastructure/repositories/http_workspace.py +65 -0
  36. documente_shared-0.1.162/documente_shared/infrastructure/repositories/http_workspace_document.py +71 -0
  37. documente_shared-0.1.162/documente_shared/infrastructure/repositories/http_workspace_document_page.py +96 -0
  38. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/services/http_scaling.py +5 -3
  39. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/pyproject.toml +7 -7
  40. documente_shared-0.1.162/tests/documente_shared/domain/entities/__init__.py +0 -0
  41. documente_shared-0.1.162/tests/documente_shared/infrastructure/__init__.py +0 -0
  42. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/uv.lock +141 -48
  43. documente_shared-0.1.161b5/documente_shared/application/dates.py +0 -7
  44. documente_shared-0.1.161b5/documente_shared/domain/constants.py +0 -8
  45. documente_shared-0.1.161b5/documente_shared/domain/entities/workspace_document.py +0 -138
  46. documente_shared-0.1.161b5/documente_shared/domain/entities/workspace_document_filters.py +0 -45
  47. documente_shared-0.1.161b5/documente_shared/domain/entities/workspace_document_page_filters.py +0 -32
  48. documente_shared-0.1.161b5/documente_shared/domain/repositories/workspace_document.py +0 -28
  49. documente_shared-0.1.161b5/documente_shared/domain/repositories/workspace_document_page.py +0 -28
  50. documente_shared-0.1.161b5/documente_shared/infrastructure/documente_client.py +0 -27
  51. documente_shared-0.1.161b5/documente_shared/infrastructure/repositories/http_workspace.py +0 -71
  52. documente_shared-0.1.161b5/documente_shared/infrastructure/repositories/http_workspace_document.py +0 -71
  53. documente_shared-0.1.161b5/documente_shared/infrastructure/repositories/http_workspace_document_page.py +0 -71
  54. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/.github/workflows/publish.yml +0 -0
  55. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/README.md +0 -0
  56. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/__init__.py +0 -0
  57. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/cases.py +0 -0
  58. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/docker-compose.yml +0 -0
  59. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/__init__.py +0 -0
  60. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/__init__.py +0 -0
  61. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/digest.py +0 -0
  62. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/exceptions.py +0 -0
  63. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/files.py +0 -0
  64. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/json.py +0 -0
  65. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/numbers.py +0 -0
  66. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/payloads.py +0 -0
  67. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/time_utils.py +0 -0
  68. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/application/timezone.py +0 -0
  69. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/__init__.py +0 -0
  70. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/base_enum.py +0 -0
  71. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/__init__.py +0 -0
  72. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/document.py +0 -0
  73. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/document_metadata.py +0 -0
  74. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/in_memory_document.py +0 -0
  75. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_case.py +0 -0
  76. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_case_filters.py +0 -0
  77. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_case_item.py +0 -0
  78. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_case_item_filters.py +0 -0
  79. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_documents.py +0 -0
  80. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_event.py +0 -0
  81. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/processing_record.py +0 -0
  82. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/entities/scaling.py +0 -0
  83. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/__init__.py +0 -0
  84. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/circular_oficio.py +0 -0
  85. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/common.py +0 -0
  86. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/document.py +0 -0
  87. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/document_type_record.py +0 -0
  88. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/processing_case.py +0 -0
  89. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/processing_case_validator.py +0 -0
  90. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/enums/workspace.py +0 -0
  91. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/exceptions.py +0 -0
  92. {documente_shared-0.1.161b5/documente_shared/domain/helpers → documente_shared-0.1.162/documente_shared/domain/filters}/__init__.py +0 -0
  93. {documente_shared-0.1.161b5/documente_shared/domain/interfaces → documente_shared-0.1.162/documente_shared/domain/helpers}/__init__.py +0 -0
  94. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/helpers/values.py +0 -0
  95. {documente_shared-0.1.161b5/documente_shared/domain/repositories → documente_shared-0.1.162/documente_shared/domain/interfaces}/__init__.py +0 -0
  96. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/interfaces/queue.py +0 -0
  97. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/interfaces/scaling.py +0 -0
  98. {documente_shared-0.1.161b5/documente_shared/infrastructure → documente_shared-0.1.162/documente_shared/domain/pagination}/__init__.py +0 -0
  99. {documente_shared-0.1.161b5/documente_shared/infrastructure → documente_shared-0.1.162/documente_shared/domain}/repositories/__init__.py +0 -0
  100. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/repositories/document.py +0 -0
  101. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/repositories/processing_case.py +0 -0
  102. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/repositories/processing_case_item.py +0 -0
  103. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/domain/repositories/processing_record.py +0 -0
  104. {documente_shared-0.1.161b5/documente_shared/infrastructure/services → documente_shared-0.1.162/documente_shared/infrastructure}/__init__.py +0 -0
  105. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/dynamo_table.py +0 -0
  106. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/lambdas.py +0 -0
  107. {documente_shared-0.1.161b5/documente_shared/presentation → documente_shared-0.1.162/documente_shared/infrastructure/repositories}/__init__.py +0 -0
  108. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/dynamo_document.py +0 -0
  109. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/dynamo_processing_case.py +0 -0
  110. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/dynamo_processing_case_item.py +0 -0
  111. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/mem_document.py +0 -0
  112. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/mem_processing_case.py +0 -0
  113. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/mem_processing_case_item.py +0 -0
  114. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/repositories/mem_processing_record.py +0 -0
  115. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/s3_bucket.py +0 -0
  116. {documente_shared-0.1.161b5/tests → documente_shared-0.1.162/documente_shared/infrastructure/services}/__init__.py +0 -0
  117. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/infrastructure/sqs_queue.py +0 -0
  118. {documente_shared-0.1.161b5/tests/documente_shared → documente_shared-0.1.162/documente_shared/presentation}/__init__.py +0 -0
  119. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/documente_shared/presentation/presenters.py +0 -0
  120. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/publish.sh +0 -0
  121. {documente_shared-0.1.161b5/tests/documente_shared/application → documente_shared-0.1.162/tests}/__init__.py +0 -0
  122. {documente_shared-0.1.161b5/tests/documente_shared/domain → documente_shared-0.1.162/tests/documente_shared}/__init__.py +0 -0
  123. {documente_shared-0.1.161b5/tests/documente_shared/domain/entities → documente_shared-0.1.162/tests/documente_shared/application}/__init__.py +0 -0
  124. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/application/test_files.py +0 -0
  125. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/application/test_payloads.py +0 -0
  126. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/conftest.py +0 -0
  127. {documente_shared-0.1.161b5/tests/documente_shared/infrastructure → documente_shared-0.1.162/tests/documente_shared/domain}/__init__.py +0 -0
  128. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/domain/entities/test_in_memory_document.py +0 -0
  129. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/domain/entities/test_processing_case.py +0 -0
  130. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/domain/entities/test_processing_case_item.py +0 -0
  131. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/domain/entities/test_processing_record.py +0 -0
  132. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/domain/enums/test_processing_case_validator.py +0 -0
  133. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/infrastructure/repositories/__init__.py +0 -0
  134. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documente_shared/infrastructure/repositories/test_http_processing_record.py +0 -0
  135. {documente_shared-0.1.161b5 → documente_shared-0.1.162}/tests/documents.py +0 -0
@@ -2,4 +2,5 @@ dist/
2
2
  tmp/
3
3
  .env
4
4
  .env.*
5
- .idea/
5
+ .idea/
6
+ __pycache__/
@@ -0,0 +1,11 @@
1
+ {
2
+ "python.analysis.extraPaths": [
3
+ "${workspaceFolder}"
4
+ ],
5
+ "python.autoComplete.extraPaths": [
6
+ "${workspaceFolder}"
7
+ ],
8
+ "terminal.integrated.env.osx": {
9
+ "PYTHONPATH": "${workspaceFolder}"
10
+ }
11
+ }
@@ -20,6 +20,6 @@ WORKDIR /package
20
20
 
21
21
  COPY pyproject.toml uv.lock /package/
22
22
 
23
- RUN uv sync --no-install-project
23
+ RUN uv sync --no-install-project --all-groups
24
24
 
25
25
  ENTRYPOINT []
@@ -5,6 +5,10 @@ COMPOSE_PACKAGE := @docker compose -f docker-compose.yml run --rm package
5
5
 
6
6
  ARG=
7
7
 
8
+ setup:
9
+ uv sync --all-groups
10
+
11
+
8
12
  build:
9
13
  $(COMPOSE) build
10
14
 
@@ -22,7 +26,7 @@ clean:
22
26
  docker ps -aq | xargs docker rm
23
27
 
24
28
  test:
25
- $(COMPOSE_PACKAGE) bash -c "pytest --pyargs $(ARG)"
29
+ $(COMPOSE_PACKAGE) bash -c "uv run pytest --pyargs $(ARG)"
26
30
 
27
31
  test_single:
28
- $(COMPOSE_PACKAGE) bash -c "pytest --pyargs -m single"
32
+ $(COMPOSE_PACKAGE) bash -c "uv run pytest --pyargs -m single"
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: documente_shared
3
- Version: 0.1.161b5
4
- Summary: Shared utilities for Documente AI projects
3
+ Version: 0.1.162
4
+ Summary: Shared utilities for LlamitAI projects
5
5
  Author-email: Tech <tech@llamitai.com>
6
6
  License: MIT
7
7
  Requires-Python: <3.13,>=3.10
8
- Requires-Dist: boto3>=1.42.24
9
- Requires-Dist: botocore>=1.42.24
10
- Requires-Dist: loguru>=0.7.3
11
- Requires-Dist: pytz>=2025.2
8
+ Requires-Dist: boto3>=1.42.46
9
+ Requires-Dist: botocore>=1.42.46
10
+ Requires-Dist: pydantic>=2.12.5
12
11
  Requires-Dist: requests>=2.32.5
13
- Requires-Dist: sentry-sdk>=2.49.0
12
+ Requires-Dist: sentry-sdk>=2.52.0
13
+ Requires-Dist: structlog>=25.5.0
14
14
  Requires-Dist: unidecode>=1.4.0
15
15
  Description-Content-Type: text/markdown
16
16
 
@@ -0,0 +1,5 @@
1
+ from datetime import datetime, timezone
2
+
3
+
4
+ def utc_now() -> datetime:
5
+ return datetime.now(tz=timezone.utc)
@@ -0,0 +1,47 @@
1
+ import sys
2
+ import logging
3
+ import structlog
4
+
5
+
6
+ def configure_logging(environment: str = "dev") -> None:
7
+ is_production = environment in ("production", "staging")
8
+
9
+ shared_processors = [
10
+ structlog.contextvars.merge_contextvars,
11
+ structlog.stdlib.add_log_level,
12
+ structlog.stdlib.add_logger_name,
13
+ structlog.processors.TimeStamper(fmt="iso"),
14
+ structlog.processors.StackInfoRenderer(),
15
+ structlog.processors.UnicodeDecoder(),
16
+ ]
17
+
18
+ if is_production:
19
+ renderer = structlog.processors.JSONRenderer()
20
+ else:
21
+ renderer = structlog.dev.ConsoleRenderer()
22
+
23
+ structlog.configure(
24
+ processors=[
25
+ *shared_processors,
26
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
27
+ ],
28
+ logger_factory=structlog.stdlib.LoggerFactory(),
29
+ wrapper_class=structlog.stdlib.BoundLogger, # type: ignore[arg-type]
30
+ cache_logger_on_first_use=True,
31
+ )
32
+
33
+ formatter = structlog.stdlib.ProcessorFormatter(
34
+ processors=[
35
+ structlog.stdlib.ProcessorFormatter.remove_processors_meta,
36
+ renderer,
37
+ ],
38
+ foreign_pre_chain=shared_processors,
39
+ )
40
+
41
+ handler = logging.StreamHandler(sys.stdout)
42
+ handler.setFormatter(formatter)
43
+
44
+ root_logger = logging.getLogger()
45
+ root_logger.handlers.clear()
46
+ root_logger.addHandler(handler)
47
+ root_logger.setLevel(logging.INFO)
@@ -2,7 +2,9 @@ import time
2
2
  import random
3
3
  from functools import wraps
4
4
  from typing import Optional, Callable
5
- from loguru import logger
5
+ import structlog
6
+
7
+ logger = structlog.get_logger()
6
8
 
7
9
 
8
10
  def retry_on_size_integrity(base_delay: float = 0.8, max_retries: int = 3):
@@ -15,7 +17,10 @@ def retry_on_size_integrity(base_delay: float = 0.8, max_retries: int = 3):
15
17
 
16
18
  if not file_context:
17
19
  logger.warning(
18
- f"[Intento {attempt+1}/{max_retries}] file_context es None para {file_key}"
20
+ "[shared.retry.file_context_none]",
21
+ attempt=attempt + 1,
22
+ max_retries=max_retries,
23
+ file_key=file_key,
19
24
  )
20
25
  if attempt < max_retries - 1:
21
26
  delay = base_delay * (2 ** attempt) + random.uniform(0, base_delay)
@@ -25,7 +30,10 @@ def retry_on_size_integrity(base_delay: float = 0.8, max_retries: int = 3):
25
30
 
26
31
  if 'Body' not in file_context:
27
32
  logger.warning(
28
- f"[Intento {attempt+1}/{max_retries}] 'Body' no encontrado en file_context para {file_key}"
33
+ "[shared.retry.body_not_found]",
34
+ attempt=attempt + 1,
35
+ max_retries=max_retries,
36
+ file_key=file_key,
29
37
  )
30
38
  if attempt < max_retries - 1:
31
39
  delay = base_delay * (2 ** attempt) + random.uniform(0, base_delay)
@@ -40,12 +48,16 @@ def retry_on_size_integrity(base_delay: float = 0.8, max_retries: int = 3):
40
48
  if expected_size == actual_size:
41
49
  file_context['Body'] = body
42
50
  if attempt > 0:
43
- logger.info(f"Descarga exitosa de {file_key} después de {attempt+1} intentos")
51
+ logger.info("[shared.retry.download_success]", file_key=file_key, attempts=attempt + 1)
44
52
  return file_context
45
53
 
46
54
  logger.warning(
47
- f"[Intento {attempt+1}/{max_retries}] Discrepancia de tamaño para {file_key}: "
48
- f"esperado={expected_size}, recibido={actual_size}"
55
+ "[shared.retry.size_mismatch]",
56
+ attempt=attempt + 1,
57
+ max_retries=max_retries,
58
+ file_key=file_key,
59
+ expected_size=expected_size,
60
+ actual_size=actual_size,
49
61
  )
50
62
 
51
63
  if attempt < max_retries - 1:
@@ -54,7 +66,12 @@ def retry_on_size_integrity(base_delay: float = 0.8, max_retries: int = 3):
54
66
 
55
67
  except Exception as e:
56
68
  logger.error(
57
- f"[Intento {attempt+1}/{max_retries}] Error al descargar {file_key}: {type(e).__name__}: {str(e)}"
69
+ "[shared.retry.download_error]",
70
+ attempt=attempt + 1,
71
+ max_retries=max_retries,
72
+ file_key=file_key,
73
+ error_type=type(e).__name__,
74
+ error=str(e),
58
75
  )
59
76
  if attempt < max_retries - 1:
60
77
  delay = base_delay * (2 ** attempt) + random.uniform(0, base_delay)
@@ -0,0 +1,10 @@
1
+ from os import environ
2
+ from zoneinfo import ZoneInfo
3
+
4
+
5
+ la_paz_tz = ZoneInfo("America/La_Paz")
6
+
7
+ DOCUMENTE_API_URL = environ.get("DOCUMENTE_API_URL")
8
+ DOCUMENTE_API_KEY = environ.get("DOCUMENTE_API_KEY")
9
+
10
+ DEFAULT_PAGINATION_PAGE_SIZE = 50
@@ -0,0 +1,49 @@
1
+ from uuid import UUID
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from documente_shared.domain.enums.processing_case import ProcessingDocumentType
7
+ from documente_shared.domain.helpers.values import optional_str
8
+
9
+
10
+ class DocumentType(BaseModel):
11
+ uuid: UUID
12
+ name: str
13
+ tenant_id: UUID | None = Field(default=None)
14
+ is_shareable: bool = False
15
+ processing_document_type: ProcessingDocumentType | None = Field(default=None)
16
+ metadata: dict | None = Field(default=None)
17
+ created_at: datetime | None = Field(default=None)
18
+ updated_at: datetime | None = Field(default=None)
19
+
20
+ @property
21
+ def to_persist_dict(self) -> dict:
22
+ return {
23
+ "tenant_id": self.tenant_id,
24
+ "name": self.name,
25
+ "is_shareable": self.is_shareable,
26
+ "processing_document_type": optional_str(self.processing_document_type),
27
+ "metadata": self.metadata or {},
28
+ }
29
+
30
+ @property
31
+ def to_dict(self) -> dict:
32
+ return {
33
+ "uuid": str(self.uuid),
34
+ "name": self.name,
35
+ "tenant_id": self
36
+ }
37
+
38
+ @classmethod
39
+ def from_dict(cls, data: dict) -> 'DocumentType':
40
+ return cls(
41
+ uuid=data.get('uuid'),
42
+ name=data.get('name'),
43
+ tenant_id=data.get('tenant_id'),
44
+ is_shareable=data.get('is_shareable', False),
45
+ processing_document_type=ProcessingDocumentType.from_value(data.get('processing_document_type')),
46
+ metadata=data.get('metadata', {}),
47
+ created_at=data.get('created_at'),
48
+ updated_at=data.get('updated_at'),
49
+ )
@@ -1,30 +1,30 @@
1
- from dataclasses import dataclass
2
1
  from datetime import datetime
3
- from typing import Optional
4
2
  from uuid import UUID
5
3
 
4
+ from pydantic import BaseModel, Field
5
+
6
6
  from documente_shared.application.time_utils import get_datetime_from_data
7
+ from documente_shared.domain.entities.document_type import DocumentType
8
+ from documente_shared.domain.helpers.values import optional_datetime_str
9
+
7
10
 
8
11
 
9
- @dataclass
10
- class Workspace(object):
12
+ class Workspace(BaseModel):
11
13
  uuid: UUID
12
14
  name: str
13
- tenant_slug: str
15
+ tenant_id: UUID | None = Field(default=None)
16
+ tenant_slug: str | None = Field(default=None)
14
17
  is_archived: bool = False
15
- metadata: Optional[dict] = None
16
- created_at: Optional[datetime] = None
17
- updated_at: Optional[datetime] = None
18
- document_types: Optional[list] = None
18
+ metadata: dict | None = Field(default_factory=dict)
19
+ created_at: datetime | None = Field(default=None)
20
+ updated_at: datetime | None = Field(default=None)
21
+ document_types: list[DocumentType] | None = Field(default_factory=list)
19
22
 
20
- def __post_init__(self):
21
- self.metadata = self.metadata or {}
22
- self.document_types = self.document_types or []
23
23
 
24
24
  @property
25
25
  def to_persist_dict(self) -> dict:
26
26
  return {
27
- "tenant_slug": self.tenant_slug,
27
+ "tenant_id": self.tenant_id,
28
28
  "name": self.name,
29
29
  "is_archived": self.is_archived,
30
30
  "metadata": self.metadata or {},
@@ -35,29 +35,29 @@ class Workspace(object):
35
35
  return {
36
36
  "uuid": str(self.uuid),
37
37
  "name": self.name,
38
+ "tenant_id": self.tenant_id,
38
39
  "tenant_slug": self.tenant_slug,
39
40
  "is_archived": self.is_archived,
40
41
  "metadata": self.metadata,
41
- "created_at": (
42
- self.created_at.isoformat()
43
- if self.created_at else None
44
- ),
45
- "updated_at": (
46
- self.updated_at.isoformat()
47
- if self.updated_at else None
48
- ),
49
- "document_types": self.document_types,
42
+ "created_at": optional_datetime_str(self.created_at),
43
+ "updated_at": optional_datetime_str(self.updated_at),
44
+ "document_types": [dt.to_dict for dt in self.document_types] if self.document_types else [],
50
45
  }
51
46
 
52
47
  @classmethod
53
48
  def from_dict(cls, data: dict) -> 'Workspace':
49
+ document_types = data.get('document_types', [])
54
50
  return cls(
55
51
  uuid=data.get('uuid'),
56
52
  name=data.get('name'),
53
+ tenant_id=data.get('tenant_id'),
57
54
  tenant_slug=data.get('tenant_slug'),
58
55
  is_archived=data.get('is_archived', False),
59
56
  metadata=data.get('metadata', {}),
60
57
  created_at=get_datetime_from_data(input_datetime=data.get('created_at')),
61
58
  updated_at=get_datetime_from_data(input_datetime=data.get('updated_at')),
62
- document_types=data.get('document_types', []),
59
+ document_types=[
60
+ DocumentType.from_dict(dt)
61
+ for dt in document_types
62
+ ],
63
63
  )
@@ -0,0 +1,116 @@
1
+ from dataclasses import Field, dataclass
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from documente_shared.application.time_utils import get_datetime_from_data
9
+ from documente_shared.domain.entities.in_memory_document import InMemoryDocument
10
+ from documente_shared.domain.enums.common import ProcessingStatus
11
+ from documente_shared.domain.enums.workspace import WorkspaceSource
12
+ from documente_shared.domain.helpers.values import optional_str
13
+
14
+
15
+ class WorkspaceDocument(BaseModel):
16
+ uuid: UUID
17
+ name: str
18
+ status: ProcessingStatus
19
+ workspace_id: UUID
20
+ tenant_id: UUID | None = Field(default=None)
21
+ tenant_slug: str | None = Field(default=None)
22
+ digest: str | None = Field(default=None)
23
+ document_type_id: UUID | None = Field(default=None)
24
+ source: WorkspaceSource | None = Field(default=None)
25
+ document: InMemoryDocument | None = Field(default=None)
26
+ document_url: str | None = Field(default=None)
27
+ processing_time: Decimal | None = Field(default=None)
28
+ metadata: dict | None = Field(default_factory=dict)
29
+ extraction: dict | None = Field(default_factory=dict)
30
+ uploaded_at: datetime | None = Field(default=None)
31
+ created_at: datetime | None = Field(default=None)
32
+ updated_at: datetime | None = Field(default=None)
33
+ started_at: datetime | None = Field(default=None)
34
+ completed_at: datetime | None = Field(default=None)
35
+ failed_at: datetime | None = Field(default=None)
36
+
37
+
38
+ @property
39
+ def to_persist_dict(self) -> dict:
40
+ return {
41
+ "tenant_id": self.tenant_id,
42
+ "workspace_id": str(self.workspace_id),
43
+ "document_type_id": optional_str(self.document_type_id),
44
+ "name": self.name,
45
+ "digest": self.digest,
46
+ "status": str(self.status),
47
+ "source": optional_str(self.source),
48
+ "uploaded_at": self.uploaded_at,
49
+ "started_at": self.started_at,
50
+ "completed_at": self.completed_at,
51
+ "failed_at": self.failed_at,
52
+ "processing_time": self.processing_time,
53
+ "metadata": self.metadata or {},
54
+ "extraction": self.extraction or {},
55
+ }
56
+
57
+ @property
58
+ def to_dict(self) -> dict:
59
+ return {
60
+ "uuid": str(self.uuid),
61
+ "name": self.name,
62
+ "digest": self.digest,
63
+ "status": str(self.status),
64
+ "tenant_id": self.tenant_id,
65
+ "tenant_slug": self.tenant_slug,
66
+ "workspace_id": str(self.workspace_id),
67
+ "document_type_id": optional_str(self.document_type_id),
68
+ "source": optional_str(self.source),
69
+ "document": (self.document.to_dict if self.document else None),
70
+ "document_url": self.document_url,
71
+ "processing_time": optional_str(self.processing_time),
72
+ "metadata": self.metadata,
73
+ "extraction": self.extraction,
74
+ "uploaded_at": (self.uploaded_at.isoformat() if self.uploaded_at else None),
75
+ "created_at": (self.created_at.isoformat() if self.created_at else None),
76
+ "updated_at": (self.updated_at.isoformat() if self.updated_at else None),
77
+ "started_at": (self.started_at.isoformat() if self.started_at else None),
78
+ "completed_at": (
79
+ self.completed_at.isoformat() if self.completed_at else None
80
+ ),
81
+ "failed_at": (self.failed_at.isoformat() if self.failed_at else None),
82
+ }
83
+
84
+ @classmethod
85
+ def from_dict(cls, data: dict) -> "WorkspaceDocument":
86
+ return cls(
87
+ uuid=data.get("uuid"),
88
+ name=data.get("name"),
89
+ digest=data.get("digest"),
90
+ status=ProcessingStatus.from_value(data.get("status")),
91
+ tenant_slug=data.get("tenant_slug"),
92
+ workspace_id=data.get("workspace_id"),
93
+ document_type_id=data.get("document_type_id"),
94
+ source=WorkspaceSource.from_value(data.get("source")),
95
+ document=(
96
+ InMemoryDocument.from_dict(data.get("document"))
97
+ if data.get("document")
98
+ else None
99
+ ),
100
+ document_url=data.get("document_url"),
101
+ processing_time=(
102
+ Decimal(data.get("processing_time"))
103
+ if data.get("processing_time")
104
+ else None
105
+ ),
106
+ metadata=data.get("metadata", {}),
107
+ extraction=data.get("extraction", {}),
108
+ uploaded_at=get_datetime_from_data(input_datetime=data.get("uploaded_at")),
109
+ created_at=get_datetime_from_data(input_datetime=data.get("created_at")),
110
+ updated_at=get_datetime_from_data(input_datetime=data.get("updated_at")),
111
+ started_at=get_datetime_from_data(input_datetime=data.get("started_at")),
112
+ completed_at=get_datetime_from_data(
113
+ input_datetime=data.get("completed_at")
114
+ ),
115
+ failed_at=get_datetime_from_data(input_datetime=data.get("failed_at")),
116
+ )
@@ -1,28 +1,29 @@
1
- from dataclasses import dataclass
2
1
  from datetime import datetime
3
- from typing import Optional
4
2
  from uuid import UUID
5
3
 
4
+ from pydantic import BaseModel, Field
5
+
6
6
  from documente_shared.application.time_utils import get_datetime_from_data
7
7
  from documente_shared.domain.entities.in_memory_document import InMemoryDocument
8
8
 
9
9
 
10
- @dataclass
11
- class WorkspaceDocumentPage(object):
10
+
11
+ class WorkspaceDocumentPage(BaseModel):
12
12
  uuid: UUID
13
- tenant_slug: str
14
13
  workspace_id: UUID
15
14
  workspace_document_id: UUID
16
15
  page_number: int
17
- page_file: Optional[InMemoryDocument] = None
18
- page_file_url: Optional[str] = None
19
- created_at: Optional[datetime] = None
20
- synced_at: Optional[datetime] = None
16
+ tenant_id: UUID | None = Field(default=None)
17
+ tenant_slug: str | None = Field(default=None)
18
+ page_file: InMemoryDocument | None = Field(default=None)
19
+ page_file_url: str | None = Field(default=None)
20
+ created_at: datetime | None = Field(default=None)
21
+ synced_at: datetime | None = Field(default=None)
21
22
 
22
23
  @property
23
24
  def to_persist_dict(self) -> dict:
24
25
  return {
25
- "tenant_slug": self.tenant_slug,
26
+ "tenant_id": self.tenant_id,
26
27
  "workspace_id": str(self.workspace_id),
27
28
  "workspace_document_id": str(self.workspace_document_id),
28
29
  "page_number": self.page_number,
@@ -33,6 +34,7 @@ class WorkspaceDocumentPage(object):
33
34
  def to_dict(self) -> dict:
34
35
  return {
35
36
  "uuid": str(self.uuid),
37
+ "tenant_id": self.tenant_id,
36
38
  "tenant_slug": self.tenant_slug,
37
39
  "workspace_id": str(self.workspace_id),
38
40
  "workspace_document_id": str(self.workspace_document_id),
@@ -56,6 +58,7 @@ class WorkspaceDocumentPage(object):
56
58
  def from_dict(cls, data: dict) -> 'WorkspaceDocumentPage':
57
59
  return cls(
58
60
  uuid=data.get('uuid'),
61
+ tenant_id=data.get('tenant_id'),
59
62
  tenant_slug=data.get('tenant_slug'),
60
63
  workspace_id=data.get('workspace_id'),
61
64
  workspace_document_id=data.get('workspace_document_id'),
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from typing import Optional
3
3
 
4
4
  from documente_shared.application.query_params import QueryParams
5
+ from documente_shared.domain.helpers.dicts import filter_none_values
5
6
 
6
7
 
7
8
  @dataclass
@@ -20,7 +21,7 @@ class WorkspaceFilters(object):
20
21
  "search": self.search,
21
22
  "include_archived": str(self.include_archived).lower(),
22
23
  }
23
- return {k: v for k, v in params.items() if v is not None}
24
+ return filter_none_values(params)
24
25
 
25
26
  @classmethod
26
27
  def from_params(cls, params: QueryParams) -> "WorkspaceFilters":
@@ -0,0 +1,24 @@
1
+ from uuid import UUID
2
+ from pydantic import Field
3
+
4
+ from documente_shared.application.query_params import QueryParams
5
+ from documente_shared.domain.helpers.dicts import filter_none_values
6
+ from documente_shared.domain.pagination.entities import ListFilters
7
+
8
+
9
+ class DocumentTypeFilters(ListFilters):
10
+ tenant_ids: list[UUID] | None = Field(default_factory=list)
11
+
12
+ @property
13
+ def to_params(self) -> dict:
14
+ return filter_none_values({
15
+ **super().to_dict,
16
+ "tenant_ids": self.tenant_ids,
17
+ })
18
+
19
+ @classmethod
20
+ def from_params(cls, params: QueryParams) -> "DocumentTypeFilters":
21
+ return cls(
22
+ cursor=params.get("cursor", None),
23
+ limit=params.get_int(key="limit", default=None),
24
+ )
@@ -0,0 +1,28 @@
1
+ from uuid import UUID
2
+ from pydantic import Field
3
+
4
+ from documente_shared.application.query_params import QueryParams
5
+ from documente_shared.domain.helpers.dicts import filter_none_values
6
+ from documente_shared.domain.pagination.entities import ListFilters
7
+
8
+
9
+ class WorkspaceFilters(ListFilters):
10
+ tenant_ids: list[UUID] | None = Field(default_factory=list)
11
+ search: str | None = Field(default=None)
12
+
13
+ @property
14
+ def to_params(self) -> dict:
15
+ return filter_none_values({
16
+ **super().to_dict,
17
+ "tenant_ids": self.tenant_ids,
18
+ "search": self.search,
19
+ })
20
+
21
+ @classmethod
22
+ def from_params(cls, params: QueryParams) -> "WorkspaceFilters":
23
+ search_term = params.get_str(key="search", default=None)
24
+ return cls(
25
+ cursor=params.get("cursor", None),
26
+ limit=params.get_int(key="limit", default=None),
27
+ search=search_term.strip() if search_term else None,
28
+ )
@@ -0,0 +1,46 @@
1
+ from uuid import UUID
2
+ from pydantic import Field
3
+
4
+ from documente_shared.application.query_params import QueryParams
5
+ from documente_shared.domain.enums.common import ProcessingStatus
6
+ from documente_shared.domain.helpers.dicts import filter_none_values
7
+ from documente_shared.domain.helpers.lists import enum_list_to_comma_string
8
+ from documente_shared.domain.pagination.entities import ListFilters
9
+
10
+
11
+ class WorkspaceDocumentFilters(ListFilters):
12
+ workspace_id: UUID | None = Field(default=None)
13
+ tenant_ids: list[UUID] | None = Field(default_factory=list)
14
+ workspace_ids: list[UUID] | None = Field(default_factory=list)
15
+ statuses: list[ProcessingStatus] | None = Field(default_factory=list)
16
+ search: str | None = Field(default=None)
17
+ digest: str | None = Field(default=None)
18
+
19
+ @property
20
+ def to_params(self) -> dict:
21
+ return filter_none_values({
22
+ **super().to_dict,
23
+ "tenant_ids": self.tenant_ids,
24
+ "workspace_ids": self.workspace_ids,
25
+ "search": self.search,
26
+ "digest": self.digest,
27
+ "statuses": enum_list_to_comma_string(self.statuses),
28
+ })
29
+
30
+ @classmethod
31
+ def from_params(cls, params: QueryParams) -> "WorkspaceDocumentFilters":
32
+ search_term = params.get_str(key="search", default=None)
33
+ return cls(
34
+ cursor=params.get("cursor", None),
35
+ limit=params.get_int(key="limit", default=None),
36
+ workspace_id=params.get_uuid(key="workspace_id", default=None),
37
+ tenant_ids=params.get_uuid_list(key="tenant_ids", default=None),
38
+ workspace_ids=params.get_uuid_list(key="workspace_ids", default=None),
39
+ search=search_term.strip() if search_term else None,
40
+ digest=params.get_str(key="digest", default=None),
41
+ statuses=params.get_enum_list(
42
+ key="statuses",
43
+ enum_class=ProcessingStatus,
44
+ default=None,
45
+ ),
46
+ )
@@ -0,0 +1,27 @@
1
+ from uuid import UUID
2
+
3
+ from pydantic import Field
4
+
5
+ from documente_shared.application.query_params import QueryParams
6
+ from documente_shared.domain.helpers.dicts import filter_none_values
7
+ from documente_shared.domain.helpers.values import optional_str
8
+ from documente_shared.domain.pagination.entities import ListFilters
9
+
10
+
11
+ class WorkspaceDocumentPageFilters(ListFilters):
12
+ workspace_document_id: UUID | None = Field(default=None)
13
+
14
+ @property
15
+ def to_params(self) -> dict:
16
+ return filter_none_values({
17
+ **super().to_dict,
18
+ "workspace_document_id": optional_str(self.workspace_document_id),
19
+ })
20
+
21
+ @classmethod
22
+ def from_params(cls, params: QueryParams) -> "WorkspaceDocumentPageFilters":
23
+ return cls(
24
+ cursor=params.get("cursor", None),
25
+ limit=params.get_int(key="limit", default=None),
26
+ workspace_document_id=params.get_uuid(key="workspace_document_id", default=None),
27
+ )