crewmaster 0.1.0__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 (139) hide show
  1. crewmaster-0.1.0/PKG-INFO +99 -0
  2. crewmaster-0.1.0/README.md +64 -0
  3. crewmaster-0.1.0/crewmaster/__init__.py +7 -0
  4. crewmaster-0.1.0/crewmaster/core/__init__.py +10 -0
  5. crewmaster-0.1.0/crewmaster/core/pydantic/__init__.py +47 -0
  6. crewmaster-0.1.0/crewmaster/core/setup.py +15 -0
  7. crewmaster-0.1.0/crewmaster/core/ultra_ddd/__init__.py +7 -0
  8. crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/__init__.py +7 -0
  9. crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter.py +86 -0
  10. crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_explained.py +95 -0
  11. crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_explained_test.py +90 -0
  12. crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_test.py +52 -0
  13. crewmaster-0.1.0/crewmaster/core/ultra_ddd/use_case/__init__.py +5 -0
  14. crewmaster-0.1.0/crewmaster/core/ultra_ddd/use_case/use_case_base.py +19 -0
  15. crewmaster-0.1.0/crewmaster/core/ultra_result/__init__.py +5 -0
  16. crewmaster-0.1.0/crewmaster/core/ultra_result/either/__init__.py +11 -0
  17. crewmaster-0.1.0/crewmaster/core/ultra_result/either/either.py +9 -0
  18. crewmaster-0.1.0/crewmaster/core/ultra_result/either/left.py +42 -0
  19. crewmaster-0.1.0/crewmaster/core/ultra_result/either/right.py +40 -0
  20. crewmaster-0.1.0/crewmaster/features/__init__.py +97 -0
  21. crewmaster-0.1.0/crewmaster/features/agent/__init__.py +7 -0
  22. crewmaster-0.1.0/crewmaster/features/agent/agent_base.py +450 -0
  23. crewmaster-0.1.0/crewmaster/features/agent/agent_base_test.py +544 -0
  24. crewmaster-0.1.0/crewmaster/features/brain/__init__.py +0 -0
  25. crewmaster-0.1.0/crewmaster/features/brain/brain_base.py +365 -0
  26. crewmaster-0.1.0/crewmaster/features/brain/brain_base_test.py +387 -0
  27. crewmaster-0.1.0/crewmaster/features/brain/brain_types.py +120 -0
  28. crewmaster-0.1.0/crewmaster/features/collaborator/__init__.py +69 -0
  29. crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_base.py +243 -0
  30. crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_base_test.py +146 -0
  31. crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_input.py +54 -0
  32. crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_ouput.py +94 -0
  33. crewmaster-0.1.0/crewmaster/features/collaborator/history_strategy.py +29 -0
  34. crewmaster-0.1.0/crewmaster/features/collaborator/injection_exception.py +8 -0
  35. crewmaster-0.1.0/crewmaster/features/collaborator/message.py +190 -0
  36. crewmaster-0.1.0/crewmaster/features/collaborator/state.py +46 -0
  37. crewmaster-0.1.0/crewmaster/features/collaborator/team_membership.py +21 -0
  38. crewmaster-0.1.0/crewmaster/features/collaborator/types.py +102 -0
  39. crewmaster-0.1.0/crewmaster/features/crew/__init__.py +29 -0
  40. crewmaster-0.1.0/crewmaster/features/crew/crew_base.py +335 -0
  41. crewmaster-0.1.0/crewmaster/features/crew/crew_base_test.py +635 -0
  42. crewmaster-0.1.0/crewmaster/features/crew/crew_input.py +50 -0
  43. crewmaster-0.1.0/crewmaster/features/crew/crew_output.py +5 -0
  44. crewmaster-0.1.0/crewmaster/features/crew/state.py +35 -0
  45. crewmaster-0.1.0/crewmaster/features/crew/types.py +57 -0
  46. crewmaster-0.1.0/crewmaster/features/helpers/__init__.py +19 -0
  47. crewmaster-0.1.0/crewmaster/features/helpers/check_templates_for_valid_placeholders.py +32 -0
  48. crewmaster-0.1.0/crewmaster/features/helpers/create_dynamic_protocol.py +32 -0
  49. crewmaster-0.1.0/crewmaster/features/helpers/read_jsonl_file.py +25 -0
  50. crewmaster-0.1.0/crewmaster/features/helpers/snake_to_camel.py +8 -0
  51. crewmaster-0.1.0/crewmaster/features/http_driver/__init__.py +6 -0
  52. crewmaster-0.1.0/crewmaster/features/http_driver/auth/__init__.py +11 -0
  53. crewmaster-0.1.0/crewmaster/features/http_driver/auth/auth_strategy_interface.py +23 -0
  54. crewmaster-0.1.0/crewmaster/features/http_driver/auth/google_auth_strategy.py +135 -0
  55. crewmaster-0.1.0/crewmaster/features/http_driver/auth/public_access_strategy.py +31 -0
  56. crewmaster-0.1.0/crewmaster/features/http_driver/auth/user_logged.py +14 -0
  57. crewmaster-0.1.0/crewmaster/features/http_driver/crew_dependencies.py +18 -0
  58. crewmaster-0.1.0/crewmaster/features/http_driver/crew_router_base.py +253 -0
  59. crewmaster-0.1.0/crewmaster/features/http_driver/crew_router_base_test.py +617 -0
  60. crewmaster-0.1.0/crewmaster/features/http_driver/crew_settings.py +25 -0
  61. crewmaster-0.1.0/crewmaster/features/http_driver/factories/__init__.py +9 -0
  62. crewmaster-0.1.0/crewmaster/features/http_driver/factories/chatopenai_factory.py +22 -0
  63. crewmaster-0.1.0/crewmaster/features/http_driver/factories/memory_factory.py +17 -0
  64. crewmaster-0.1.0/crewmaster/features/http_driver/http_input.py +111 -0
  65. crewmaster-0.1.0/crewmaster/features/http_driver/stream_conversor.py +193 -0
  66. crewmaster-0.1.0/crewmaster/features/json_serializar_from_custom_models.py +48 -0
  67. crewmaster-0.1.0/crewmaster/features/muscle/__init__.py +0 -0
  68. crewmaster-0.1.0/crewmaster/features/muscle/muscle_base.py +274 -0
  69. crewmaster-0.1.0/crewmaster/features/muscle/muscle_base_test.py +421 -0
  70. crewmaster-0.1.0/crewmaster/features/muscle/muscle_types.py +89 -0
  71. crewmaster-0.1.0/crewmaster/features/performance/__init__.py +48 -0
  72. crewmaster-0.1.0/crewmaster/features/performance/aptitude.py +91 -0
  73. crewmaster-0.1.0/crewmaster/features/performance/aptitude_base.py +65 -0
  74. crewmaster-0.1.0/crewmaster/features/performance/aptitude_result.py +18 -0
  75. crewmaster-0.1.0/crewmaster/features/performance/aptitude_summary.py +13 -0
  76. crewmaster-0.1.0/crewmaster/features/performance/challenge/__init__.py +41 -0
  77. crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge.py +45 -0
  78. crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_base.py +163 -0
  79. crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_result.py +20 -0
  80. crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_summary.py +8 -0
  81. crewmaster-0.1.0/crewmaster/features/performance/challenge/collaboration.py +131 -0
  82. crewmaster-0.1.0/crewmaster/features/performance/challenge/free_response.py +43 -0
  83. crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_interpretation.py +42 -0
  84. crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_selection.py +238 -0
  85. crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_selection_test.py +351 -0
  86. crewmaster-0.1.0/crewmaster/features/performance/challenge/structured_response.py +43 -0
  87. crewmaster-0.1.0/crewmaster/features/performance/evaluator/__init__.py +32 -0
  88. crewmaster-0.1.0/crewmaster/features/performance/evaluator/evaluator.py +38 -0
  89. crewmaster-0.1.0/crewmaster/features/performance/evaluator/evaluator_base.py +26 -0
  90. crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/__init__.py +15 -0
  91. crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/coherence.py +84 -0
  92. crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/correctness.py +47 -0
  93. crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/model_evaluator_base.py +12 -0
  94. crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/toxicity.py +76 -0
  95. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/__init__.py +27 -0
  96. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/contain.py +32 -0
  97. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/is_instance.py +35 -0
  98. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/match.py +32 -0
  99. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_checker.py +45 -0
  100. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_checker_test.py +107 -0
  101. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_equality.py +52 -0
  102. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_equality_test.py +90 -0
  103. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/rule_evaluator_base.py +8 -0
  104. crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/sub_set.py +53 -0
  105. crewmaster-0.1.0/crewmaster/features/performance/execution_context.py +22 -0
  106. crewmaster-0.1.0/crewmaster/features/performance/loader_object.py +127 -0
  107. crewmaster-0.1.0/crewmaster/features/performance/loader_strategy_base.py +29 -0
  108. crewmaster-0.1.0/crewmaster/features/performance/organizer.py +33 -0
  109. crewmaster-0.1.0/crewmaster/features/performance/organizer_base.py +48 -0
  110. crewmaster-0.1.0/crewmaster/features/performance/performance_review.py +50 -0
  111. crewmaster-0.1.0/crewmaster/features/performance/performance_review_base.py +47 -0
  112. crewmaster-0.1.0/crewmaster/features/performance/performance_review_summary.py +14 -0
  113. crewmaster-0.1.0/crewmaster/features/performance/perforrmance_review_result.py +19 -0
  114. crewmaster-0.1.0/crewmaster/features/performance/reporter_adapter_base.py +71 -0
  115. crewmaster-0.1.0/crewmaster/features/performance/reporter_console.py +67 -0
  116. crewmaster-0.1.0/crewmaster/features/performance/reporter_null.py +40 -0
  117. crewmaster-0.1.0/crewmaster/features/performance/score/__init__.py +36 -0
  118. crewmaster-0.1.0/crewmaster/features/performance/score/score.py +130 -0
  119. crewmaster-0.1.0/crewmaster/features/performance/score/score_base.py +26 -0
  120. crewmaster-0.1.0/crewmaster/features/runnables/__init__.py +20 -0
  121. crewmaster-0.1.0/crewmaster/features/runnables/injection_exception.py +8 -0
  122. crewmaster-0.1.0/crewmaster/features/runnables/runnable_streameable.py +26 -0
  123. crewmaster-0.1.0/crewmaster/features/runnables/with_config_verified.py +185 -0
  124. crewmaster-0.1.0/crewmaster/features/setup.py +15 -0
  125. crewmaster-0.1.0/crewmaster/features/skill/__init__.py +48 -0
  126. crewmaster-0.1.0/crewmaster/features/skill/skill.py +39 -0
  127. crewmaster-0.1.0/crewmaster/features/skill/skill_base.py +39 -0
  128. crewmaster-0.1.0/crewmaster/features/skill/skill_computation.py +250 -0
  129. crewmaster-0.1.0/crewmaster/features/skill/skill_contribute.py +31 -0
  130. crewmaster-0.1.0/crewmaster/features/skill/skill_structured_response.py +18 -0
  131. crewmaster-0.1.0/crewmaster/features/skill/types.py +190 -0
  132. crewmaster-0.1.0/crewmaster/features/tap_runnable.py +22 -0
  133. crewmaster-0.1.0/crewmaster/features/team/__init__.py +15 -0
  134. crewmaster-0.1.0/crewmaster/features/team/distribution_strategy.py +41 -0
  135. crewmaster-0.1.0/crewmaster/features/team/team_base.png +0 -0
  136. crewmaster-0.1.0/crewmaster/features/team/team_base.py +303 -0
  137. crewmaster-0.1.0/crewmaster/features/team/team_base_test.py +547 -0
  138. crewmaster-0.1.0/crewmaster/setup.py +42 -0
  139. crewmaster-0.1.0/pyproject.toml +61 -0
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.3
2
+ Name: crewmaster
3
+ Version: 0.1.0
4
+ Summary: Manejador de agentes
5
+ Author: Imolko
6
+ Author-email: info@imolko.com
7
+ Requires-Python: >=3.12.3,<3.14
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Requires-Dist: deepeval (>=2.3.8,<3.0.0)
11
+ Requires-Dist: fastapi (>=0.115.8,<0.116.0)
12
+ Requires-Dist: google (>=3.0.0,<4.0.0)
13
+ Requires-Dist: google-api-python-client (>=2.161.0,<3.0.0)
14
+ Requires-Dist: google-auth-httplib2 (>=0.2.0,<0.3.0)
15
+ Requires-Dist: google-auth-oauthlib (>=1.2.1,<2.0.0)
16
+ Requires-Dist: langchain (>=0.3.18,<0.4.0)
17
+ Requires-Dist: langchain-cli (>=0.0.35,<0.0.36)
18
+ Requires-Dist: langchain-community (>=0.3.17,<0.4.0)
19
+ Requires-Dist: langchain-core (>=0.3.35,<0.4.0)
20
+ Requires-Dist: langchain-openai (>=0.3.5,<0.4.0)
21
+ Requires-Dist: langchain-postgres (>=0.0.13,<0.0.14)
22
+ Requires-Dist: langgraph (>=0.2.71,<0.3.0)
23
+ Requires-Dist: langserve (>=0.3.1,<0.4.0)
24
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
25
+ Requires-Dist: pydantic-settings (>=2.7.1,<3.0.0)
26
+ Requires-Dist: pytest (>=8.3.4,<9.0.0)
27
+ Requires-Dist: pytest-asyncio (>=0.25.3,<0.26.0)
28
+ Requires-Dist: pytest-mock (>=3.14.0,<4.0.0)
29
+ Requires-Dist: pytest-watcher (>=0.4.3,<0.5.0)
30
+ Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
31
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
32
+ Requires-Dist: setuptools (>=75.8.0,<76.0.0)
33
+ Requires-Dist: structlog (>=25.1.0,<26.0.0)
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Crewmaster
37
+
38
+ ## Instalar dependencias
39
+
40
+ 1. Activar entorno virtual
41
+ ```
42
+ poetry env use python
43
+ ```
44
+ 2. Instalar
45
+ ```
46
+ poetry install
47
+ ```
48
+
49
+ ## Ejecutar los test
50
+
51
+ 1. Ejecutar todos los test
52
+ ```
53
+ poetry run pytest
54
+ ```
55
+
56
+ 2. Se puede utilizar pytest para hacer los tests:
57
+ ```
58
+ poetry run pytest ./ruta/a/probar --capture=no
59
+ ```
60
+
61
+ 3. Pytest no ofrece un modo "watch". Si quiere utilizar un modo watch debes ejecutar:
62
+ ```
63
+ poetry run ptw ./ruta/a/monitorear ./ruta/a/probar --capture=no
64
+ ```
65
+
66
+ 4. Si se quiere probar sólo algún test, se puede agregar la marca `@pytest.mark.mi_marca` y luego ejecutar con el parámetro -k=mi_marca.
67
+
68
+ ```
69
+ # test.py
70
+ @pytest.mark.mi_marca
71
+ def test_check...
72
+
73
+ # console
74
+ poetry run ptw ./ruta/a/monitorear ./ruta/a/probar --capture=no -k=mi_marca
75
+ ```
76
+
77
+ ## Publicar libreria
78
+
79
+ 1. Crear cuenta en pypi
80
+ 2. Crear credenciales
81
+ 3. Ir a "Configurar cuenta"
82
+ 3. Dar scroll hasta "Anadir una ficha api" y crear token(si es necesario)
83
+ 4. Agregar token a poetry
84
+ ```
85
+ poetry config pypi-token.pypi your-api-token
86
+ ```
87
+ 5. Construir el paquete
88
+ ```
89
+ poetry build
90
+ ```
91
+ 6. Publicar el paquete
92
+ ```
93
+ poetry publish
94
+ ```
95
+
96
+ * Tambien puedes crear y publicar el paquete en un solo paso
97
+ ```
98
+ poetry publish --build
99
+ ```
@@ -0,0 +1,64 @@
1
+ # Crewmaster
2
+
3
+ ## Instalar dependencias
4
+
5
+ 1. Activar entorno virtual
6
+ ```
7
+ poetry env use python
8
+ ```
9
+ 2. Instalar
10
+ ```
11
+ poetry install
12
+ ```
13
+
14
+ ## Ejecutar los test
15
+
16
+ 1. Ejecutar todos los test
17
+ ```
18
+ poetry run pytest
19
+ ```
20
+
21
+ 2. Se puede utilizar pytest para hacer los tests:
22
+ ```
23
+ poetry run pytest ./ruta/a/probar --capture=no
24
+ ```
25
+
26
+ 3. Pytest no ofrece un modo "watch". Si quiere utilizar un modo watch debes ejecutar:
27
+ ```
28
+ poetry run ptw ./ruta/a/monitorear ./ruta/a/probar --capture=no
29
+ ```
30
+
31
+ 4. Si se quiere probar sólo algún test, se puede agregar la marca `@pytest.mark.mi_marca` y luego ejecutar con el parámetro -k=mi_marca.
32
+
33
+ ```
34
+ # test.py
35
+ @pytest.mark.mi_marca
36
+ def test_check...
37
+
38
+ # console
39
+ poetry run ptw ./ruta/a/monitorear ./ruta/a/probar --capture=no -k=mi_marca
40
+ ```
41
+
42
+ ## Publicar libreria
43
+
44
+ 1. Crear cuenta en pypi
45
+ 2. Crear credenciales
46
+ 3. Ir a "Configurar cuenta"
47
+ 3. Dar scroll hasta "Anadir una ficha api" y crear token(si es necesario)
48
+ 4. Agregar token a poetry
49
+ ```
50
+ poetry config pypi-token.pypi your-api-token
51
+ ```
52
+ 5. Construir el paquete
53
+ ```
54
+ poetry build
55
+ ```
56
+ 6. Publicar el paquete
57
+ ```
58
+ poetry publish
59
+ ```
60
+
61
+ * Tambien puedes crear y publicar el paquete en un solo paso
62
+ ```
63
+ poetry publish --build
64
+ ```
@@ -0,0 +1,7 @@
1
+ from . import core
2
+ from . import features
3
+
4
+ __all__ = [
5
+ "core",
6
+ "features"
7
+ ]
@@ -0,0 +1,10 @@
1
+ from . import ultra_result
2
+ from . import pydantic
3
+ from . import ultra_ddd
4
+
5
+
6
+ __all__ = [
7
+ "ultra_result",
8
+ "pydantic",
9
+ "ultra_ddd"
10
+ ]
@@ -0,0 +1,47 @@
1
+ from pydantic import (
2
+ AnyHttpUrl,
3
+ BaseModel,
4
+ ConfigDict,
5
+ conlist,
6
+ computed_field,
7
+ create_model,
8
+ field_validator,
9
+ Field,
10
+ model_validator,
11
+ PrivateAttr,
12
+ RootModel,
13
+ SecretStr,
14
+ SerializeAsAny,
15
+ StringConstraints,
16
+ TypeAdapter,
17
+ ValidationError,
18
+ )
19
+ from pydantic_settings import (
20
+ BaseSettings,
21
+ )
22
+
23
+ from pydantic_settings import (
24
+ BaseSettings as BaseSettingsV2
25
+ )
26
+
27
+ __all__ = [
28
+ "AnyHttpUrl",
29
+ "BaseModel",
30
+ "BaseSettings",
31
+ "BaseSettingsV2",
32
+ "computed_field",
33
+ "ConfigDict",
34
+ "conlist",
35
+ "create_model",
36
+ "DiscriminatedBaseModel",
37
+ "field_validator",
38
+ "Field",
39
+ "model_validator",
40
+ "PrivateAttr",
41
+ "RootModel",
42
+ "SecretStr",
43
+ "SerializeAsAny",
44
+ "StringConstraints",
45
+ "TypeAdapter",
46
+ "ValidationError",
47
+ ]
@@ -0,0 +1,15 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="core",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ description="Manejador de agentes",
8
+ author=[
9
+ {'name': "Imolko", "email": "info@imolko.com"},
10
+ {'name': "Carlos N", "email": "machinery.e@gmail.com"}
11
+ ],
12
+ contributors=[
13
+ {'name': "Carlos N", "email": "machinery.e@gmail.com"}
14
+ ]
15
+ )
@@ -0,0 +1,7 @@
1
+ from . import use_case
2
+ from . import emitter
3
+
4
+ __all__ = [
5
+ "use_case",
6
+ "emitter",
7
+ ]
@@ -0,0 +1,7 @@
1
+ from .emitter import Emitter
2
+ from .emitter_explained import EmitterExplained
3
+
4
+ __all__ = [
5
+ "Emitter",
6
+ "EmitterExplained",
7
+ ]
@@ -0,0 +1,86 @@
1
+ from asyncio import (
2
+ Queue,
3
+ )
4
+ from typing import (
5
+ AsyncIterator,
6
+ Callable,
7
+ Optional,
8
+ TypeVar,
9
+ Generic,
10
+ )
11
+
12
+
13
+ # Define generic type variables
14
+ OutputType = TypeVar('OutputType')
15
+
16
+
17
+ class NoEmittedValue(Exception):
18
+ pass
19
+
20
+
21
+ EmitterOperator = Callable[
22
+ [OutputType, OutputType],
23
+ OutputType
24
+ ]
25
+
26
+
27
+ class Emitter(Generic[OutputType]):
28
+ """
29
+ Abstract base class for emitter.
30
+
31
+ """
32
+ _queue = Queue()
33
+ _is_finished = False
34
+ operator: Optional[EmitterOperator] = None
35
+ """
36
+ Operador utilizado para el acumular los valores en get_value,
37
+ por defecto se envía siempre el último valor del stream
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ operator: Optional[EmitterOperator] = None
43
+ ):
44
+ self.operator = operator
45
+
46
+ def emit_value(
47
+ self,
48
+ value: OutputType,
49
+ finished: bool = True
50
+ ) -> None:
51
+ self._queue.put_nowait(value)
52
+ if finished:
53
+ self._queue.put_nowait(None)
54
+ self._is_finished = True
55
+
56
+ async def get_stream(
57
+ self,
58
+ ) -> AsyncIterator[OutputType]:
59
+ while not self._is_finished or not self._queue.empty():
60
+ result = await self._queue.get()
61
+ if result is None:
62
+ break
63
+ yield result
64
+
65
+ def _acum(
66
+ self,
67
+ current: Optional[OutputType],
68
+ next: OutputType
69
+ ) -> OutputType:
70
+ # Por defecto siempre enviamos el último valor
71
+ if self.operator is None:
72
+ return next
73
+ return self.operator(current, next)
74
+
75
+ async def get_value(
76
+ self,
77
+ ) -> OutputType:
78
+ response = None
79
+ async for result in self.get_stream():
80
+ if response is None:
81
+ response = result
82
+ else:
83
+ response = self._acum(response, result)
84
+ if response is None:
85
+ raise NoEmittedValue('No value emitted')
86
+ return response
@@ -0,0 +1,95 @@
1
+ from typing import (
2
+ Callable,
3
+ Dict,
4
+ Generic,
5
+ List,
6
+ Type,
7
+ TypeVar,
8
+ TypedDict,
9
+ Union,
10
+ )
11
+ from ...ultra_result.either import (
12
+ Either
13
+ )
14
+ from .emitter import (
15
+ Emitter,
16
+ )
17
+
18
+ LeftType = TypeVar('LeftType')
19
+ RightType = TypeVar('RightType')
20
+
21
+
22
+ class NaturalResponse(TypedDict, Generic[LeftType, RightType]):
23
+ explanation: str
24
+ result: Either[LeftType, RightType]
25
+
26
+
27
+ class EmitterExplained(
28
+ Generic[LeftType, RightType],
29
+ Emitter[NaturalResponse[LeftType, RightType]]
30
+ ):
31
+ right_explanation_fn: Union[
32
+ Callable[[RightType], str],
33
+ str
34
+ ]
35
+ left_exclusive: Dict[Type[Exception], str] = {}
36
+ left_multiple_intro: str = 'Can not process because {count} problems: '
37
+
38
+ def __init__(
39
+ self,
40
+ right_explanation_fn: Union[
41
+ Callable[[RightType], str],
42
+ str
43
+ ],
44
+ left_exclusive: Dict[Type[Exception], str] = {},
45
+ left_multiple_intro: str = 'Can not process because {count} problems: '
46
+ ):
47
+ self.right_explanation_fn = right_explanation_fn
48
+ self.left_exclusive = left_exclusive
49
+ self.left_multiple_intro = left_multiple_intro
50
+
51
+ def _build_left_explanation(
52
+ self,
53
+ errors: List[LeftType]
54
+ ) -> str:
55
+ if len(errors) == 1:
56
+ specials = [value for key, value in self.left_exclusive.items()
57
+ if isinstance(errors[0], key)]
58
+ if len(specials) > 0:
59
+ return specials[0]
60
+ errors_str = [f'"{str(item)}"' for item in errors]
61
+ joined = ", ".join(errors_str)
62
+ intro = self.left_multiple_intro.format(count=len(errors))
63
+ explanation = f'{intro}{joined}'
64
+ return explanation
65
+
66
+ def _build_right_explanation(
67
+ self,
68
+ result: RightType
69
+ ) -> str:
70
+ if isinstance(self.right_explanation_fn, str):
71
+ # If it's a string, return it directly
72
+ return self.right_explanation_fn
73
+ elif callable(self.right_explanation_fn):
74
+ # If it's a callable, call it with the result and return the result
75
+ return self.right_explanation_fn(result)
76
+ else:
77
+ raise ValueError("Invalid type for right_explanation_fn")
78
+
79
+ def emit_value(
80
+ self,
81
+ result: Either[LeftType, RightType]
82
+ ) -> None:
83
+ if result.is_right():
84
+ explanation = self._build_right_explanation(
85
+ result.get_value()
86
+ )
87
+ else:
88
+ explanation = self._build_left_explanation(
89
+ result.get_errors()
90
+ )
91
+ full_response: NaturalResponse = {
92
+ "explanation": explanation,
93
+ "result": result
94
+ }
95
+ super().emit_value(full_response)
@@ -0,0 +1,90 @@
1
+ import structlog
2
+ import pytest
3
+
4
+ from .emitter_explained import (
5
+ EmitterExplained,
6
+ )
7
+ from ...ultra_result.either import (
8
+ right,
9
+ left,
10
+ )
11
+
12
+ log = structlog.get_logger()
13
+ "Logger para el módulo"
14
+
15
+
16
+ @pytest.mark.only
17
+ @pytest.mark.asyncio
18
+ async def test_single_value_as_stream():
19
+ emitter = EmitterExplained[str, None](
20
+ right_explanation_fn='Estoy bien'
21
+ )
22
+ emitter.emit_value(right(None))
23
+ async for value in emitter.get_stream():
24
+ result = value
25
+ assert result.get('explanation', '') == 'Estoy bien'
26
+
27
+
28
+ @pytest.mark.only
29
+ @pytest.mark.asyncio
30
+ async def test_fn_transform_right():
31
+ emitter = EmitterExplained[Exception, str](
32
+ right_explanation_fn=lambda result: f'Hola {result}'
33
+ )
34
+ emitter.emit_value(right('Pedro'))
35
+ result = await emitter.get_value()
36
+ assert result.get('explanation', '') == 'Hola Pedro'
37
+
38
+
39
+ @pytest.mark.only
40
+ @pytest.mark.asyncio
41
+ async def test_exclusive_left():
42
+ emitter = EmitterExplained[Exception, str](
43
+ right_explanation_fn=lambda result: f'Hola {result}',
44
+ left_exclusive={ValueError: 'Sucedio un problemon'}
45
+ )
46
+ emitter.emit_value(left(ValueError('invalid value')))
47
+ result = await emitter.get_value()
48
+ assert result.get('explanation', '') == 'Sucedio un problemon'
49
+
50
+
51
+ @pytest.mark.only
52
+ @pytest.mark.asyncio
53
+ async def test_multiples_errors():
54
+ emitter = EmitterExplained[Exception, str](
55
+ right_explanation_fn=lambda result: f'Hola {result}',
56
+ left_exclusive={ValueError: 'Sucedio un problemon'}
57
+ )
58
+ emitter.emit_value(left([
59
+ ValueError('invalid value'),
60
+ Exception('otro error más')
61
+ ]))
62
+ result = await emitter.get_value()
63
+ explanation = result.get('explanation', '')
64
+ assert (
65
+ 'Can not process because 2 problems'
66
+ in explanation
67
+ )
68
+ assert 'invalid value' in explanation
69
+ assert 'otro error más' in explanation
70
+
71
+
72
+ @pytest.mark.only
73
+ @pytest.mark.asyncio
74
+ async def test_multiples_errors_with_custom_intro():
75
+ emitter = EmitterExplained[Exception, str](
76
+ right_explanation_fn=lambda result: f'Hola {result}',
77
+ left_multiple_intro='Increible, hay {count} errores:'
78
+ )
79
+ emitter.emit_value(left([
80
+ ValueError('invalid value'),
81
+ Exception('otro error más')
82
+ ]))
83
+ result = await emitter.get_value()
84
+ explanation = result.get('explanation', '')
85
+ assert (
86
+ 'Increible, hay 2 errores:'
87
+ in explanation
88
+ )
89
+ assert 'invalid value' in explanation
90
+ assert 'otro error más' in explanation
@@ -0,0 +1,52 @@
1
+ import structlog
2
+ import pytest
3
+ import operator
4
+
5
+ from .emitter import Emitter
6
+
7
+ log = structlog.get_logger()
8
+ "Logger para el módulo"
9
+
10
+
11
+ @pytest.mark.only
12
+ @pytest.mark.asyncio
13
+ async def test_single_value_as_stream():
14
+ emitter = Emitter[str]()
15
+ emitter.emit_value('hola')
16
+ async for value in emitter.get_stream():
17
+ result = value
18
+ assert result == 'hola'
19
+
20
+
21
+ @pytest.mark.only
22
+ @pytest.mark.asyncio
23
+ async def test_multiple_value_as_stream():
24
+ emitter = Emitter[str]()
25
+ emitter.emit_value('hola, ', finished=False)
26
+ emitter.emit_value('como estas?')
27
+ result = ''
28
+ async for value in emitter.get_stream():
29
+ result += value
30
+ assert result == 'hola, como estas?'
31
+
32
+
33
+ @pytest.mark.only
34
+ @pytest.mark.asyncio
35
+ async def test_single_value_as_single():
36
+ emitter = Emitter[str]()
37
+ emitter.emit_value('hola')
38
+ result = await emitter.get_value()
39
+ assert result == 'hola'
40
+
41
+
42
+ @pytest.mark.only
43
+ @pytest.mark.asyncio
44
+ async def test_multiple_value_acummulated():
45
+ emitter = Emitter[str](
46
+ operator=operator.add
47
+ )
48
+ emitter.emit_value('hola, ', finished=False)
49
+ emitter.emit_value('buen dia, ', finished=False)
50
+ emitter.emit_value('como estas?')
51
+ result = await emitter.get_value()
52
+ assert result == 'hola, buen dia, como estas?'
@@ -0,0 +1,5 @@
1
+ from .use_case_base import UseCaseBase
2
+
3
+ __all__ = [
4
+ "UseCaseBase"
5
+ ]
@@ -0,0 +1,19 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TypeVar, Generic
3
+
4
+ # Define generic type variables
5
+ IRequest = TypeVar('IRequest')
6
+
7
+
8
+ class UseCaseBase(ABC, Generic[IRequest]):
9
+ """
10
+ Abstract base class for use cases.
11
+
12
+ All use cases must implement this interface.
13
+
14
+ Only one method is offered to "execute" the use case.
15
+ """
16
+
17
+ @abstractmethod
18
+ async def execute(self, request: IRequest) -> None:
19
+ pass
@@ -0,0 +1,5 @@
1
+ from . import either
2
+
3
+ __all__ = [
4
+ "either"
5
+ ]
@@ -0,0 +1,11 @@
1
+ from .right import Right, right
2
+ from .left import Left, left
3
+ from .either import Either
4
+
5
+ __all__ = [
6
+ "Right",
7
+ "right",
8
+ "Left",
9
+ "left",
10
+ "Either",
11
+ ]
@@ -0,0 +1,9 @@
1
+ from typing import TypeVar, Union
2
+
3
+ from .right import Right
4
+ from .left import Left
5
+
6
+ T = TypeVar('T')
7
+ E = TypeVar('E')
8
+
9
+ Either = Union[Left[E], Right[T]]
@@ -0,0 +1,42 @@
1
+ from typing import TypeVar, Generic, List, Callable, Union
2
+
3
+ T = TypeVar('T')
4
+ E = TypeVar('E')
5
+
6
+
7
+ class Left(Generic[E]):
8
+ def __init__(self, errors: List[E]) -> None:
9
+ if not errors or errors is None:
10
+ raise ValueError(
11
+ 'Errors list is null or undefined. Must pass errors list.'
12
+ )
13
+ if len(errors) == 0:
14
+ raise ValueError('Errors list is empty. Must pass errors list.')
15
+ self.errors: List[E] = errors
16
+
17
+ def get_value(self):
18
+ raise TypeError('Cant access value from a Left value')
19
+
20
+ def get_errors(self) -> List[E]:
21
+ return self.errors
22
+
23
+ def is_left(self) -> bool:
24
+ return True
25
+
26
+ def is_right(self) -> bool:
27
+ return False
28
+
29
+ def fold(
30
+ self, left_fn: Callable[[List[E]], T],
31
+ right_fn: Callable[[T], T]
32
+ ) -> T:
33
+ return left_fn(self.get_errors())
34
+
35
+
36
+ def left(errorOrErrors: Union[E, list[E]]) -> Left[E]:
37
+ """
38
+ Creates a Left instance from a list of errors.
39
+ """
40
+ errors = errorOrErrors if isinstance(errorOrErrors, list) \
41
+ else [errorOrErrors]
42
+ return Left(errors)