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.
- crewmaster-0.1.0/PKG-INFO +99 -0
- crewmaster-0.1.0/README.md +64 -0
- crewmaster-0.1.0/crewmaster/__init__.py +7 -0
- crewmaster-0.1.0/crewmaster/core/__init__.py +10 -0
- crewmaster-0.1.0/crewmaster/core/pydantic/__init__.py +47 -0
- crewmaster-0.1.0/crewmaster/core/setup.py +15 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/__init__.py +7 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/__init__.py +7 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter.py +86 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_explained.py +95 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_explained_test.py +90 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/emitter/emitter_test.py +52 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/use_case/__init__.py +5 -0
- crewmaster-0.1.0/crewmaster/core/ultra_ddd/use_case/use_case_base.py +19 -0
- crewmaster-0.1.0/crewmaster/core/ultra_result/__init__.py +5 -0
- crewmaster-0.1.0/crewmaster/core/ultra_result/either/__init__.py +11 -0
- crewmaster-0.1.0/crewmaster/core/ultra_result/either/either.py +9 -0
- crewmaster-0.1.0/crewmaster/core/ultra_result/either/left.py +42 -0
- crewmaster-0.1.0/crewmaster/core/ultra_result/either/right.py +40 -0
- crewmaster-0.1.0/crewmaster/features/__init__.py +97 -0
- crewmaster-0.1.0/crewmaster/features/agent/__init__.py +7 -0
- crewmaster-0.1.0/crewmaster/features/agent/agent_base.py +450 -0
- crewmaster-0.1.0/crewmaster/features/agent/agent_base_test.py +544 -0
- crewmaster-0.1.0/crewmaster/features/brain/__init__.py +0 -0
- crewmaster-0.1.0/crewmaster/features/brain/brain_base.py +365 -0
- crewmaster-0.1.0/crewmaster/features/brain/brain_base_test.py +387 -0
- crewmaster-0.1.0/crewmaster/features/brain/brain_types.py +120 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/__init__.py +69 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_base.py +243 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_base_test.py +146 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_input.py +54 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/collaborator_ouput.py +94 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/history_strategy.py +29 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/injection_exception.py +8 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/message.py +190 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/state.py +46 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/team_membership.py +21 -0
- crewmaster-0.1.0/crewmaster/features/collaborator/types.py +102 -0
- crewmaster-0.1.0/crewmaster/features/crew/__init__.py +29 -0
- crewmaster-0.1.0/crewmaster/features/crew/crew_base.py +335 -0
- crewmaster-0.1.0/crewmaster/features/crew/crew_base_test.py +635 -0
- crewmaster-0.1.0/crewmaster/features/crew/crew_input.py +50 -0
- crewmaster-0.1.0/crewmaster/features/crew/crew_output.py +5 -0
- crewmaster-0.1.0/crewmaster/features/crew/state.py +35 -0
- crewmaster-0.1.0/crewmaster/features/crew/types.py +57 -0
- crewmaster-0.1.0/crewmaster/features/helpers/__init__.py +19 -0
- crewmaster-0.1.0/crewmaster/features/helpers/check_templates_for_valid_placeholders.py +32 -0
- crewmaster-0.1.0/crewmaster/features/helpers/create_dynamic_protocol.py +32 -0
- crewmaster-0.1.0/crewmaster/features/helpers/read_jsonl_file.py +25 -0
- crewmaster-0.1.0/crewmaster/features/helpers/snake_to_camel.py +8 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/__init__.py +6 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/auth/__init__.py +11 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/auth/auth_strategy_interface.py +23 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/auth/google_auth_strategy.py +135 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/auth/public_access_strategy.py +31 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/auth/user_logged.py +14 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/crew_dependencies.py +18 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/crew_router_base.py +253 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/crew_router_base_test.py +617 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/crew_settings.py +25 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/factories/__init__.py +9 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/factories/chatopenai_factory.py +22 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/factories/memory_factory.py +17 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/http_input.py +111 -0
- crewmaster-0.1.0/crewmaster/features/http_driver/stream_conversor.py +193 -0
- crewmaster-0.1.0/crewmaster/features/json_serializar_from_custom_models.py +48 -0
- crewmaster-0.1.0/crewmaster/features/muscle/__init__.py +0 -0
- crewmaster-0.1.0/crewmaster/features/muscle/muscle_base.py +274 -0
- crewmaster-0.1.0/crewmaster/features/muscle/muscle_base_test.py +421 -0
- crewmaster-0.1.0/crewmaster/features/muscle/muscle_types.py +89 -0
- crewmaster-0.1.0/crewmaster/features/performance/__init__.py +48 -0
- crewmaster-0.1.0/crewmaster/features/performance/aptitude.py +91 -0
- crewmaster-0.1.0/crewmaster/features/performance/aptitude_base.py +65 -0
- crewmaster-0.1.0/crewmaster/features/performance/aptitude_result.py +18 -0
- crewmaster-0.1.0/crewmaster/features/performance/aptitude_summary.py +13 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/__init__.py +41 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge.py +45 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_base.py +163 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_result.py +20 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/challenge_summary.py +8 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/collaboration.py +131 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/free_response.py +43 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_interpretation.py +42 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_selection.py +238 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/skill_selection_test.py +351 -0
- crewmaster-0.1.0/crewmaster/features/performance/challenge/structured_response.py +43 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/__init__.py +32 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/evaluator.py +38 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/evaluator_base.py +26 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/__init__.py +15 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/coherence.py +84 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/correctness.py +47 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/model_evaluator_base.py +12 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/model_based/toxicity.py +76 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/__init__.py +27 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/contain.py +32 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/is_instance.py +35 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/match.py +32 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_checker.py +45 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_checker_test.py +107 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_equality.py +52 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/pydantic_model_equality_test.py +90 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/rule_evaluator_base.py +8 -0
- crewmaster-0.1.0/crewmaster/features/performance/evaluator/rule_based/sub_set.py +53 -0
- crewmaster-0.1.0/crewmaster/features/performance/execution_context.py +22 -0
- crewmaster-0.1.0/crewmaster/features/performance/loader_object.py +127 -0
- crewmaster-0.1.0/crewmaster/features/performance/loader_strategy_base.py +29 -0
- crewmaster-0.1.0/crewmaster/features/performance/organizer.py +33 -0
- crewmaster-0.1.0/crewmaster/features/performance/organizer_base.py +48 -0
- crewmaster-0.1.0/crewmaster/features/performance/performance_review.py +50 -0
- crewmaster-0.1.0/crewmaster/features/performance/performance_review_base.py +47 -0
- crewmaster-0.1.0/crewmaster/features/performance/performance_review_summary.py +14 -0
- crewmaster-0.1.0/crewmaster/features/performance/perforrmance_review_result.py +19 -0
- crewmaster-0.1.0/crewmaster/features/performance/reporter_adapter_base.py +71 -0
- crewmaster-0.1.0/crewmaster/features/performance/reporter_console.py +67 -0
- crewmaster-0.1.0/crewmaster/features/performance/reporter_null.py +40 -0
- crewmaster-0.1.0/crewmaster/features/performance/score/__init__.py +36 -0
- crewmaster-0.1.0/crewmaster/features/performance/score/score.py +130 -0
- crewmaster-0.1.0/crewmaster/features/performance/score/score_base.py +26 -0
- crewmaster-0.1.0/crewmaster/features/runnables/__init__.py +20 -0
- crewmaster-0.1.0/crewmaster/features/runnables/injection_exception.py +8 -0
- crewmaster-0.1.0/crewmaster/features/runnables/runnable_streameable.py +26 -0
- crewmaster-0.1.0/crewmaster/features/runnables/with_config_verified.py +185 -0
- crewmaster-0.1.0/crewmaster/features/setup.py +15 -0
- crewmaster-0.1.0/crewmaster/features/skill/__init__.py +48 -0
- crewmaster-0.1.0/crewmaster/features/skill/skill.py +39 -0
- crewmaster-0.1.0/crewmaster/features/skill/skill_base.py +39 -0
- crewmaster-0.1.0/crewmaster/features/skill/skill_computation.py +250 -0
- crewmaster-0.1.0/crewmaster/features/skill/skill_contribute.py +31 -0
- crewmaster-0.1.0/crewmaster/features/skill/skill_structured_response.py +18 -0
- crewmaster-0.1.0/crewmaster/features/skill/types.py +190 -0
- crewmaster-0.1.0/crewmaster/features/tap_runnable.py +22 -0
- crewmaster-0.1.0/crewmaster/features/team/__init__.py +15 -0
- crewmaster-0.1.0/crewmaster/features/team/distribution_strategy.py +41 -0
- crewmaster-0.1.0/crewmaster/features/team/team_base.png +0 -0
- crewmaster-0.1.0/crewmaster/features/team/team_base.py +303 -0
- crewmaster-0.1.0/crewmaster/features/team/team_base_test.py +547 -0
- crewmaster-0.1.0/crewmaster/setup.py +42 -0
- 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,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,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,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,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)
|