vellum-ai 0.9.16rc2__py3-none-any.whl → 0.9.16rc4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. vellum/plugins/__init__.py +0 -0
  2. vellum/plugins/pydantic.py +74 -0
  3. vellum/plugins/utils.py +19 -0
  4. vellum/plugins/vellum_mypy.py +639 -3
  5. vellum/workflows/README.md +90 -0
  6. vellum/workflows/__init__.py +5 -0
  7. vellum/workflows/constants.py +43 -0
  8. vellum/workflows/descriptors/__init__.py +0 -0
  9. vellum/workflows/descriptors/base.py +339 -0
  10. vellum/workflows/descriptors/tests/test_utils.py +83 -0
  11. vellum/workflows/descriptors/utils.py +90 -0
  12. vellum/workflows/edges/__init__.py +5 -0
  13. vellum/workflows/edges/edge.py +23 -0
  14. vellum/workflows/emitters/__init__.py +5 -0
  15. vellum/workflows/emitters/base.py +14 -0
  16. vellum/workflows/environment/__init__.py +5 -0
  17. vellum/workflows/environment/environment.py +7 -0
  18. vellum/workflows/errors/__init__.py +6 -0
  19. vellum/workflows/errors/types.py +20 -0
  20. vellum/workflows/events/__init__.py +31 -0
  21. vellum/workflows/events/node.py +125 -0
  22. vellum/workflows/events/tests/__init__.py +0 -0
  23. vellum/workflows/events/tests/test_event.py +216 -0
  24. vellum/workflows/events/types.py +52 -0
  25. vellum/workflows/events/utils.py +5 -0
  26. vellum/workflows/events/workflow.py +139 -0
  27. vellum/workflows/exceptions.py +15 -0
  28. vellum/workflows/expressions/__init__.py +0 -0
  29. vellum/workflows/expressions/accessor.py +52 -0
  30. vellum/workflows/expressions/and_.py +32 -0
  31. vellum/workflows/expressions/begins_with.py +31 -0
  32. vellum/workflows/expressions/between.py +38 -0
  33. vellum/workflows/expressions/coalesce_expression.py +41 -0
  34. vellum/workflows/expressions/contains.py +30 -0
  35. vellum/workflows/expressions/does_not_begin_with.py +31 -0
  36. vellum/workflows/expressions/does_not_contain.py +30 -0
  37. vellum/workflows/expressions/does_not_end_with.py +31 -0
  38. vellum/workflows/expressions/does_not_equal.py +25 -0
  39. vellum/workflows/expressions/ends_with.py +31 -0
  40. vellum/workflows/expressions/equals.py +25 -0
  41. vellum/workflows/expressions/greater_than.py +33 -0
  42. vellum/workflows/expressions/greater_than_or_equal_to.py +33 -0
  43. vellum/workflows/expressions/in_.py +31 -0
  44. vellum/workflows/expressions/is_blank.py +24 -0
  45. vellum/workflows/expressions/is_not_blank.py +24 -0
  46. vellum/workflows/expressions/is_not_null.py +21 -0
  47. vellum/workflows/expressions/is_not_undefined.py +22 -0
  48. vellum/workflows/expressions/is_null.py +21 -0
  49. vellum/workflows/expressions/is_undefined.py +22 -0
  50. vellum/workflows/expressions/less_than.py +33 -0
  51. vellum/workflows/expressions/less_than_or_equal_to.py +33 -0
  52. vellum/workflows/expressions/not_between.py +38 -0
  53. vellum/workflows/expressions/not_in.py +31 -0
  54. vellum/workflows/expressions/or_.py +32 -0
  55. vellum/workflows/graph/__init__.py +3 -0
  56. vellum/workflows/graph/graph.py +131 -0
  57. vellum/workflows/graph/tests/__init__.py +0 -0
  58. vellum/workflows/graph/tests/test_graph.py +437 -0
  59. vellum/workflows/inputs/__init__.py +5 -0
  60. vellum/workflows/inputs/base.py +55 -0
  61. vellum/workflows/logging.py +14 -0
  62. vellum/workflows/nodes/__init__.py +46 -0
  63. vellum/workflows/nodes/bases/__init__.py +7 -0
  64. vellum/workflows/nodes/bases/base.py +332 -0
  65. vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +5 -0
  66. vellum/workflows/nodes/bases/base_subworkflow_node/node.py +10 -0
  67. vellum/workflows/nodes/bases/tests/__init__.py +0 -0
  68. vellum/workflows/nodes/bases/tests/test_base_node.py +125 -0
  69. vellum/workflows/nodes/core/__init__.py +16 -0
  70. vellum/workflows/nodes/core/error_node/__init__.py +5 -0
  71. vellum/workflows/nodes/core/error_node/node.py +26 -0
  72. vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py +5 -0
  73. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +73 -0
  74. vellum/workflows/nodes/core/map_node/__init__.py +5 -0
  75. vellum/workflows/nodes/core/map_node/node.py +147 -0
  76. vellum/workflows/nodes/core/map_node/tests/__init__.py +0 -0
  77. vellum/workflows/nodes/core/map_node/tests/test_node.py +65 -0
  78. vellum/workflows/nodes/core/retry_node/__init__.py +5 -0
  79. vellum/workflows/nodes/core/retry_node/node.py +106 -0
  80. vellum/workflows/nodes/core/retry_node/tests/__init__.py +0 -0
  81. vellum/workflows/nodes/core/retry_node/tests/test_node.py +93 -0
  82. vellum/workflows/nodes/core/templating_node/__init__.py +5 -0
  83. vellum/workflows/nodes/core/templating_node/custom_filters.py +12 -0
  84. vellum/workflows/nodes/core/templating_node/exceptions.py +2 -0
  85. vellum/workflows/nodes/core/templating_node/node.py +123 -0
  86. vellum/workflows/nodes/core/templating_node/render.py +55 -0
  87. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +21 -0
  88. vellum/workflows/nodes/core/try_node/__init__.py +5 -0
  89. vellum/workflows/nodes/core/try_node/node.py +110 -0
  90. vellum/workflows/nodes/core/try_node/tests/__init__.py +0 -0
  91. vellum/workflows/nodes/core/try_node/tests/test_node.py +82 -0
  92. vellum/workflows/nodes/displayable/__init__.py +31 -0
  93. vellum/workflows/nodes/displayable/api_node/__init__.py +5 -0
  94. vellum/workflows/nodes/displayable/api_node/node.py +44 -0
  95. vellum/workflows/nodes/displayable/bases/__init__.py +11 -0
  96. vellum/workflows/nodes/displayable/bases/api_node/__init__.py +5 -0
  97. vellum/workflows/nodes/displayable/bases/api_node/node.py +70 -0
  98. vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py +5 -0
  99. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +60 -0
  100. vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py +5 -0
  101. vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py +13 -0
  102. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +118 -0
  103. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +98 -0
  104. vellum/workflows/nodes/displayable/bases/search_node.py +90 -0
  105. vellum/workflows/nodes/displayable/code_execution_node/__init__.py +5 -0
  106. vellum/workflows/nodes/displayable/code_execution_node/node.py +197 -0
  107. vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py +0 -0
  108. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py +0 -0
  109. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py +3 -0
  110. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +111 -0
  111. vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -0
  112. vellum/workflows/nodes/displayable/conditional_node/__init__.py +5 -0
  113. vellum/workflows/nodes/displayable/conditional_node/node.py +25 -0
  114. vellum/workflows/nodes/displayable/final_output_node/__init__.py +5 -0
  115. vellum/workflows/nodes/displayable/final_output_node/node.py +43 -0
  116. vellum/workflows/nodes/displayable/guardrail_node/__init__.py +5 -0
  117. vellum/workflows/nodes/displayable/guardrail_node/node.py +97 -0
  118. vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py +5 -0
  119. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +41 -0
  120. vellum/workflows/nodes/displayable/merge_node/__init__.py +5 -0
  121. vellum/workflows/nodes/displayable/merge_node/node.py +10 -0
  122. vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py +5 -0
  123. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +45 -0
  124. vellum/workflows/nodes/displayable/search_node/__init__.py +5 -0
  125. vellum/workflows/nodes/displayable/search_node/node.py +26 -0
  126. vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py +5 -0
  127. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +156 -0
  128. vellum/workflows/nodes/displayable/tests/__init__.py +0 -0
  129. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +148 -0
  130. vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py +134 -0
  131. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +80 -0
  132. vellum/workflows/nodes/utils.py +27 -0
  133. vellum/workflows/outputs/__init__.py +6 -0
  134. vellum/workflows/outputs/base.py +196 -0
  135. vellum/workflows/ports/__init__.py +7 -0
  136. vellum/workflows/ports/node_ports.py +75 -0
  137. vellum/workflows/ports/port.py +75 -0
  138. vellum/workflows/ports/utils.py +40 -0
  139. vellum/workflows/references/__init__.py +17 -0
  140. vellum/workflows/references/environment_variable.py +20 -0
  141. vellum/workflows/references/execution_count.py +20 -0
  142. vellum/workflows/references/external_input.py +49 -0
  143. vellum/workflows/references/input.py +7 -0
  144. vellum/workflows/references/lazy.py +55 -0
  145. vellum/workflows/references/node.py +43 -0
  146. vellum/workflows/references/output.py +78 -0
  147. vellum/workflows/references/state_value.py +23 -0
  148. vellum/workflows/references/vellum_secret.py +15 -0
  149. vellum/workflows/references/workflow_input.py +41 -0
  150. vellum/workflows/resolvers/__init__.py +5 -0
  151. vellum/workflows/resolvers/base.py +15 -0
  152. vellum/workflows/runner/__init__.py +5 -0
  153. vellum/workflows/runner/runner.py +588 -0
  154. vellum/workflows/runner/types.py +18 -0
  155. vellum/workflows/state/__init__.py +5 -0
  156. vellum/workflows/state/base.py +327 -0
  157. vellum/workflows/state/context.py +18 -0
  158. vellum/workflows/state/encoder.py +57 -0
  159. vellum/workflows/state/store.py +28 -0
  160. vellum/workflows/state/tests/__init__.py +0 -0
  161. vellum/workflows/state/tests/test_state.py +113 -0
  162. vellum/workflows/types/__init__.py +0 -0
  163. vellum/workflows/types/core.py +91 -0
  164. vellum/workflows/types/generics.py +14 -0
  165. vellum/workflows/types/stack.py +39 -0
  166. vellum/workflows/types/tests/__init__.py +0 -0
  167. vellum/workflows/types/tests/test_utils.py +76 -0
  168. vellum/workflows/types/utils.py +164 -0
  169. vellum/workflows/utils/__init__.py +0 -0
  170. vellum/workflows/utils/names.py +13 -0
  171. vellum/workflows/utils/tests/__init__.py +0 -0
  172. vellum/workflows/utils/tests/test_names.py +15 -0
  173. vellum/workflows/utils/tests/test_vellum_variables.py +25 -0
  174. vellum/workflows/utils/vellum_variables.py +81 -0
  175. vellum/workflows/vellum_client.py +18 -0
  176. vellum/workflows/workflows/__init__.py +5 -0
  177. vellum/workflows/workflows/base.py +365 -0
  178. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/METADATA +2 -1
  179. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/RECORD +245 -7
  180. vellum_cli/__init__.py +72 -0
  181. vellum_cli/aliased_group.py +103 -0
  182. vellum_cli/config.py +96 -0
  183. vellum_cli/image_push.py +112 -0
  184. vellum_cli/logger.py +36 -0
  185. vellum_cli/pull.py +73 -0
  186. vellum_cli/push.py +121 -0
  187. vellum_cli/tests/test_config.py +100 -0
  188. vellum_cli/tests/test_pull.py +152 -0
  189. vellum_ee/workflows/__init__.py +0 -0
  190. vellum_ee/workflows/display/__init__.py +0 -0
  191. vellum_ee/workflows/display/base.py +73 -0
  192. vellum_ee/workflows/display/nodes/__init__.py +4 -0
  193. vellum_ee/workflows/display/nodes/base_node_display.py +116 -0
  194. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +36 -0
  195. vellum_ee/workflows/display/nodes/get_node_display_class.py +25 -0
  196. vellum_ee/workflows/display/nodes/tests/__init__.py +0 -0
  197. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +47 -0
  198. vellum_ee/workflows/display/nodes/types.py +18 -0
  199. vellum_ee/workflows/display/nodes/utils.py +33 -0
  200. vellum_ee/workflows/display/nodes/vellum/__init__.py +32 -0
  201. vellum_ee/workflows/display/nodes/vellum/api_node.py +205 -0
  202. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +71 -0
  203. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +217 -0
  204. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +61 -0
  205. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +49 -0
  206. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +170 -0
  207. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +99 -0
  208. vellum_ee/workflows/display/nodes/vellum/map_node.py +100 -0
  209. vellum_ee/workflows/display/nodes/vellum/merge_node.py +48 -0
  210. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +68 -0
  211. vellum_ee/workflows/display/nodes/vellum/search_node.py +193 -0
  212. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +58 -0
  213. vellum_ee/workflows/display/nodes/vellum/templating_node.py +67 -0
  214. vellum_ee/workflows/display/nodes/vellum/tests/__init__.py +0 -0
  215. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +106 -0
  216. vellum_ee/workflows/display/nodes/vellum/try_node.py +38 -0
  217. vellum_ee/workflows/display/nodes/vellum/utils.py +76 -0
  218. vellum_ee/workflows/display/tests/__init__.py +0 -0
  219. vellum_ee/workflows/display/tests/workflow_serialization/__init__.py +0 -0
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +426 -0
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +607 -0
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +1175 -0
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +235 -0
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +511 -0
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +372 -0
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +272 -0
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +289 -0
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +354 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +123 -0
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +84 -0
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +233 -0
  232. vellum_ee/workflows/display/types.py +46 -0
  233. vellum_ee/workflows/display/utils/__init__.py +0 -0
  234. vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
  235. vellum_ee/workflows/display/utils/tests/test_uuids.py +16 -0
  236. vellum_ee/workflows/display/utils/uuids.py +24 -0
  237. vellum_ee/workflows/display/utils/vellum.py +121 -0
  238. vellum_ee/workflows/display/vellum.py +357 -0
  239. vellum_ee/workflows/display/workflows/__init__.py +5 -0
  240. vellum_ee/workflows/display/workflows/base_workflow_display.py +302 -0
  241. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +32 -0
  242. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +386 -0
  243. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/LICENSE +0 -0
  244. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/WHEEL +0 -0
  245. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,90 @@
1
+ <p align="center">
2
+ <h1 align="center">
3
+ Vellum Workflows SDK
4
+ </h1>
5
+ <p align="center">
6
+ <a href="https://docs.vellum.ai/developers/workflows-sdk">Learn more</a>
7
+ ·
8
+ <a href="https://www.vellum.ai/landing-pages/request-demo">Talk to us</a>
9
+ </p>
10
+ </p>
11
+
12
+ # Introduction
13
+
14
+ The Vellum Workflows SDK is a framework for defining and executing complex AI systems as graphs.
15
+
16
+ The Vellum Workflows SDK provides a declarative python syntax for both defining and executing the control flow of graphs.
17
+ Unlike other graph execution frameworks, which are functional or event-driven in nature, Vellum's Workflows SDK defines the control flow of a graph
18
+ statically and makes use of strict typing. This means that the structure of the graph is known ahead of time, and you get all the benefits of type
19
+ safety and intellisense. This ultimately makes it easier to build more predictable and robust AI systems.
20
+
21
+ Unique amongst other frameworks, Vellum's Workflows SDK also allows you to _visualize, edit, and execute_ your graph in a UI, pushing and pulling changes from
22
+ code to UI and vice versa.
23
+
24
+
25
+ ## Core Features
26
+ - **Nodes**: Nodes are the basic building blocks of a graph. They represent a single task or function that can be executed.
27
+ - **Graph Syntax**: An intuitive declarative syntax for defining the control flow of a graph.
28
+ - **Inputs and Outputs**: Both the Workflow itslef and individual Nodes can take in inputs and produce outputs, which can be used to pass information between nodes.
29
+ - **State**: Nodes can read and write to the graph's global state, which can be used to share information between nodes without defining explicit inputs and outputs.
30
+ - **Advanced Control Flow**: Support for looping, conditionals, paralellism, state forking, and more.
31
+ - **Streaming**: Nodes can stream output values back to the runner, allowing for long-running tasks like chat completions to return partial results.
32
+ - **Human-in-the-loop**: Nodes can wait for External Inputs, allowing for a pause in the Workflow until a human or external system provides input.
33
+ - **UI Integration**: Push and pull changes from code to Vellum's UI and vice versa, allowing for rapid testing and iteration.
34
+
35
+ ## Quickstart
36
+
37
+ 1. Install the Vellum Workflows SDK:
38
+
39
+ ```bash
40
+ pip install vellum-ai
41
+ ```
42
+
43
+ 2. Import the Vellum Workflows SDK and define your first Workflow:
44
+
45
+ ```python
46
+ # my_workflow.py
47
+
48
+ from workflows import BaseWorkflow
49
+ from workflows.nodes import BaseNode
50
+
51
+
52
+ class MyNode(BaseNode):
53
+
54
+ class Outputs(BaseNode.Outputs):
55
+ result: str
56
+
57
+ def run(self):
58
+ return self.Outputs(result="Hello, World!")
59
+
60
+
61
+ class MyWorkflow(BaseWorkflow):
62
+ graph = MyNode
63
+
64
+ class Outputs(BaseWorkflow.Outputs):
65
+ result = MyNode.Outputs.result
66
+
67
+
68
+ if __name__ == "__main__":
69
+ workflow = MyWorkflow()
70
+ result = workflow.run()
71
+
72
+ print(result.outputs.result)
73
+
74
+ ```
75
+ 3. Run it!
76
+
77
+ ```bash
78
+ python my_workflow.py
79
+
80
+ Note: To use most out-of-box Nodes, and to push/pull to/from the Velłum UI, you'll need a Vellum account and API key. [Talk to us](https://www.vellum.ai/landing-pages/request-demo) or visit our [pricing page](https://www.vellum.ai/pricing) for more information.
81
+
82
+
83
+ ## Documentation
84
+ Complete documentation for the Vellum Workflows SDK can be found at https://docs.vellum.ai/developers/workflows-sdk.
85
+
86
+
87
+ ## Stability
88
+
89
+ This SDK is currently in <Availability type="beta" /> and is subject to change. If you'd like to pariticpate in
90
+ our beta program, please [contact us](https://docs.vellum.ai/home/getting-started/support).
@@ -0,0 +1,5 @@
1
+ from .workflows import BaseWorkflow
2
+
3
+ __all__ = [
4
+ "BaseWorkflow",
5
+ ]
@@ -0,0 +1,43 @@
1
+ from enum import Enum
2
+ from typing import Any, cast
3
+
4
+
5
+ class _UndefMeta(type):
6
+ def __repr__(cls) -> str:
7
+ return "UNDEF"
8
+
9
+ def __getattribute__(cls, name: str) -> Any:
10
+ if name == "__class__":
11
+ # ensures that UNDEF.__class__ == UNDEF
12
+ return cls
13
+
14
+ return super().__getattribute__(name)
15
+
16
+ def __bool__(cls) -> bool:
17
+ return False
18
+
19
+
20
+ class UNDEF(metaclass=_UndefMeta):
21
+ pass
22
+
23
+
24
+ LATEST_RELEASE_TAG = "LATEST"
25
+
26
+ OMIT = cast(Any, ...)
27
+
28
+
29
+ class APIRequestMethod(Enum):
30
+ GET = "GET"
31
+ POST = "POST"
32
+ PUT = "PUT"
33
+ DELETE = "DELETE"
34
+ PATCH = "PATCH"
35
+ OPTIONS = "OPTIONS"
36
+ HEAD = "HEAD"
37
+ CONNECT = "CONNECT"
38
+ TRACE = "TRACE"
39
+
40
+
41
+ class AuthorizationType(Enum):
42
+ BEARER_TOKEN = "BEARER_TOKEN"
43
+ API_KEY = "API_KEY"
File without changes
@@ -0,0 +1,339 @@
1
+ from typing import TYPE_CHECKING, Any, Generic, Optional, Tuple, Type, TypeVar, Union, cast, overload
2
+
3
+ if TYPE_CHECKING:
4
+ from vellum.workflows.expressions.accessor import AccessorExpression
5
+ from vellum.workflows.expressions.and_ import AndExpression
6
+ from vellum.workflows.expressions.begins_with import BeginsWithExpression
7
+ from vellum.workflows.expressions.between import BetweenExpression
8
+ from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
9
+ from vellum.workflows.expressions.contains import ContainsExpression
10
+ from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
11
+ from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
12
+ from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
13
+ from vellum.workflows.expressions.does_not_equal import DoesNotEqualExpression
14
+ from vellum.workflows.expressions.ends_with import EndsWithExpression
15
+ from vellum.workflows.expressions.equals import EqualsExpression
16
+ from vellum.workflows.expressions.greater_than import GreaterThanExpression
17
+ from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
18
+ from vellum.workflows.expressions.in_ import InExpression
19
+ from vellum.workflows.expressions.is_blank import IsBlankExpression
20
+ from vellum.workflows.expressions.is_not_blank import IsNotBlankExpression
21
+ from vellum.workflows.expressions.is_not_null import IsNotNullExpression
22
+ from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpression
23
+ from vellum.workflows.expressions.is_null import IsNullExpression
24
+ from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
25
+ from vellum.workflows.expressions.less_than import LessThanExpression
26
+ from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
27
+ from vellum.workflows.expressions.not_between import NotBetweenExpression
28
+ from vellum.workflows.expressions.not_in import NotInExpression
29
+ from vellum.workflows.expressions.or_ import OrExpression
30
+ from vellum.workflows.nodes.bases import BaseNode
31
+ from vellum.workflows.state.base import BaseState
32
+
33
+ _T = TypeVar("_T")
34
+ _O = TypeVar("_O")
35
+ _O2 = TypeVar("_O2")
36
+
37
+
38
+ class BaseDescriptor(Generic[_T]):
39
+ _name: str
40
+ _types: Tuple[Type[_T], ...]
41
+ _instance: Optional[_T]
42
+
43
+ def __init__(self, *, name: str, types: Tuple[Type[_T], ...], instance: Optional[_T] = None) -> None:
44
+ self._name = name
45
+ self._types = types
46
+ self._instance = instance
47
+
48
+ @property
49
+ def name(self) -> str:
50
+ return self._name
51
+
52
+ @property
53
+ def types(self) -> Tuple[Type[_T], ...]:
54
+ return self._types
55
+
56
+ @property
57
+ def instance(self) -> Optional[_T]:
58
+ return self._instance
59
+
60
+ def resolve(self, state: "BaseState") -> _T:
61
+ raise NotImplementedError("Descriptor must implement resolve method")
62
+
63
+ def __eq__(self, other: object) -> bool:
64
+ if not isinstance(other, type(self)):
65
+ return False
66
+ return self._name == other._name
67
+
68
+ def __hash__(self) -> int:
69
+ return hash(self._name)
70
+
71
+ @overload
72
+ def __get__(self, instance: "BaseNode", owner: Type["BaseNode"]) -> _T: ...
73
+
74
+ @overload
75
+ def __get__(self, instance: Any, owner: Optional[Type]) -> "BaseDescriptor[_T]": ...
76
+
77
+ def __get__(self, instance: Any, owner: Optional[Type]) -> Union["BaseDescriptor[_T]", _T]:
78
+ if not instance:
79
+ return self
80
+
81
+ if instance.__class__.__name__ == "BaseNode":
82
+ node = cast("BaseNode", instance)
83
+ return self.resolve(node.state)
84
+
85
+ return self
86
+
87
+ # TODO: We should figure out how to remove these dynamic imports
88
+ # https://app.shortcut.com/vellum/story/4504
89
+ @overload
90
+ def __or__(self, other: "BaseDescriptor[_O]") -> "OrExpression[_T, _O]": ...
91
+
92
+ @overload
93
+ def __or__(self, other: _O) -> "OrExpression[_T, _O]": ...
94
+
95
+ def __or__(self, other: "Union[BaseDescriptor[_O], _O]") -> "OrExpression[_T, _O]":
96
+ from vellum.workflows.expressions.or_ import OrExpression
97
+
98
+ return OrExpression(lhs=self, rhs=other)
99
+
100
+ @overload
101
+ def __and__(self, other: "BaseDescriptor[_O]") -> "AndExpression[_T, _O]": ...
102
+
103
+ @overload
104
+ def __and__(self, other: _O) -> "AndExpression[_T, _O]": ...
105
+
106
+ def __and__(self, other: "Union[BaseDescriptor[_O], _O]") -> "AndExpression[_T, _O]":
107
+ from vellum.workflows.expressions.and_ import AndExpression
108
+
109
+ return AndExpression(lhs=self, rhs=other)
110
+
111
+ @overload
112
+ def coalesce(self, other: "BaseDescriptor[_O]") -> "CoalesceExpression[_T, _O]": ...
113
+
114
+ @overload
115
+ def coalesce(self, other: _O) -> "CoalesceExpression[_T, _O]": ...
116
+
117
+ def coalesce(self, other: "Union[BaseDescriptor[_O], _O]") -> "CoalesceExpression[_T, _O]":
118
+ from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
119
+
120
+ return CoalesceExpression(lhs=self, rhs=other)
121
+
122
+ def __getitem__(self, field: str) -> "AccessorExpression":
123
+ from vellum.workflows.expressions.accessor import AccessorExpression
124
+
125
+ return AccessorExpression(base=self, field=field)
126
+
127
+ @overload
128
+ def equals(self, other: "BaseDescriptor[_O]") -> "EqualsExpression[_T, _O]": ...
129
+
130
+ @overload
131
+ def equals(self, other: _O) -> "EqualsExpression[_T, _O]": ...
132
+
133
+ def equals(self, other: "Union[BaseDescriptor[_O], _O]") -> "EqualsExpression[_T, _O]":
134
+ from vellum.workflows.expressions.equals import EqualsExpression
135
+
136
+ return EqualsExpression(lhs=self, rhs=other)
137
+
138
+ @overload
139
+ def does_not_equal(self, other: "BaseDescriptor[_O]") -> "DoesNotEqualExpression[_T, _O]": ...
140
+
141
+ @overload
142
+ def does_not_equal(self, other: _O) -> "DoesNotEqualExpression[_T, _O]": ...
143
+
144
+ def does_not_equal(self, other: "Union[BaseDescriptor[_O], _O]") -> "DoesNotEqualExpression[_T, _O]":
145
+ from vellum.workflows.expressions.does_not_equal import DoesNotEqualExpression
146
+
147
+ return DoesNotEqualExpression(lhs=self, rhs=other)
148
+
149
+ @overload
150
+ def less_than(self, other: "BaseDescriptor[_O]") -> "LessThanExpression[_T, _O]": ...
151
+
152
+ @overload
153
+ def less_than(self, other: _O) -> "LessThanExpression[_T, _O]": ...
154
+
155
+ def less_than(self, other: "Union[BaseDescriptor[_O], _O]") -> "LessThanExpression[_T, _O]":
156
+ from vellum.workflows.expressions.less_than import LessThanExpression
157
+
158
+ return LessThanExpression(lhs=self, rhs=other)
159
+
160
+ @overload
161
+ def greater_than(self, other: "BaseDescriptor[_O]") -> "GreaterThanExpression[_T, _O]": ...
162
+
163
+ @overload
164
+ def greater_than(self, other: _O) -> "GreaterThanExpression[_T, _O]": ...
165
+
166
+ def greater_than(self, other: "Union[BaseDescriptor[_O], _O]") -> "GreaterThanExpression[_T, _O]":
167
+ from vellum.workflows.expressions.greater_than import GreaterThanExpression
168
+
169
+ return GreaterThanExpression(lhs=self, rhs=other)
170
+
171
+ @overload
172
+ def less_than_or_equal_to(self, other: "BaseDescriptor[_O]") -> "LessThanOrEqualToExpression[_T, _O]": ...
173
+
174
+ @overload
175
+ def less_than_or_equal_to(self, other: _O) -> "LessThanOrEqualToExpression[_T, _O]": ...
176
+
177
+ def less_than_or_equal_to(self, other: "Union[BaseDescriptor[_O], _O]") -> "LessThanOrEqualToExpression[_T, _O]":
178
+ from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
179
+
180
+ return LessThanOrEqualToExpression(lhs=self, rhs=other)
181
+
182
+ @overload
183
+ def greater_than_or_equal_to(self, other: "BaseDescriptor[_O]") -> "GreaterThanOrEqualToExpression[_T, _O]": ...
184
+
185
+ @overload
186
+ def greater_than_or_equal_to(self, other: _O) -> "GreaterThanOrEqualToExpression[_T, _O]": ...
187
+
188
+ def greater_than_or_equal_to(
189
+ self, other: "Union[BaseDescriptor[_O], _O]"
190
+ ) -> "GreaterThanOrEqualToExpression[_T, _O]":
191
+ from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
192
+
193
+ return GreaterThanOrEqualToExpression(lhs=self, rhs=other)
194
+
195
+ @overload
196
+ def contains(self, other: "BaseDescriptor[_O]") -> "ContainsExpression[_T, _O]": ...
197
+
198
+ @overload
199
+ def contains(self, other: _O) -> "ContainsExpression[_T, _O]": ...
200
+
201
+ def contains(self, other: "Union[BaseDescriptor[_O], _O]") -> "ContainsExpression[_T, _O]":
202
+ from vellum.workflows.expressions.contains import ContainsExpression
203
+
204
+ return ContainsExpression(lhs=self, rhs=other)
205
+
206
+ @overload
207
+ def begins_with(self, other: "BaseDescriptor[_O]") -> "BeginsWithExpression[_T, _O]": ...
208
+
209
+ @overload
210
+ def begins_with(self, other: _O) -> "BeginsWithExpression[_T, _O]": ...
211
+
212
+ def begins_with(self, other: "Union[BaseDescriptor[_O], _O]") -> "BeginsWithExpression[_T, _O]":
213
+ from vellum.workflows.expressions.begins_with import BeginsWithExpression
214
+
215
+ return BeginsWithExpression(lhs=self, rhs=other)
216
+
217
+ @overload
218
+ def ends_with(self, other: "BaseDescriptor[_O]") -> "EndsWithExpression[_T, _O]": ...
219
+
220
+ @overload
221
+ def ends_with(self, other: _O) -> "EndsWithExpression[_T, _O]": ...
222
+
223
+ def ends_with(self, other: "Union[BaseDescriptor[_O], _O]") -> "EndsWithExpression[_T, _O]":
224
+ from vellum.workflows.expressions.ends_with import EndsWithExpression
225
+
226
+ return EndsWithExpression(lhs=self, rhs=other)
227
+
228
+ @overload
229
+ def does_not_contain(self, other: "BaseDescriptor[_O]") -> "DoesNotContainExpression[_T, _O]": ...
230
+
231
+ @overload
232
+ def does_not_contain(self, other: _O) -> "DoesNotContainExpression[_T, _O]": ...
233
+
234
+ def does_not_contain(self, other: "Union[BaseDescriptor[_O], _O]") -> "DoesNotContainExpression[_T, _O]":
235
+ from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
236
+
237
+ return DoesNotContainExpression(lhs=self, rhs=other)
238
+
239
+ @overload
240
+ def does_not_begin_with(self, other: "BaseDescriptor[_O]") -> "DoesNotBeginWithExpression[_T, _O]": ...
241
+
242
+ @overload
243
+ def does_not_begin_with(self, other: _O) -> "DoesNotBeginWithExpression[_T, _O]": ...
244
+
245
+ def does_not_begin_with(self, other: "Union[BaseDescriptor[_O], _O]") -> "DoesNotBeginWithExpression[_T, _O]":
246
+ from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
247
+
248
+ return DoesNotBeginWithExpression(lhs=self, rhs=other)
249
+
250
+ @overload
251
+ def does_not_end_with(self, other: "BaseDescriptor[_O]") -> "DoesNotEndWithExpression[_T, _O]": ...
252
+
253
+ @overload
254
+ def does_not_end_with(self, other: _O) -> "DoesNotEndWithExpression[_T, _O]": ...
255
+
256
+ def does_not_end_with(self, other: "Union[BaseDescriptor[_O], _O]") -> "DoesNotEndWithExpression[_T, _O]":
257
+ from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
258
+
259
+ return DoesNotEndWithExpression(lhs=self, rhs=other)
260
+
261
+ def is_none(self) -> "IsNullExpression":
262
+ from vellum.workflows.expressions.is_null import IsNullExpression
263
+
264
+ return IsNullExpression(expression=self)
265
+
266
+ def is_not_none(self) -> "IsNotNullExpression":
267
+ from vellum.workflows.expressions.is_not_null import IsNotNullExpression
268
+
269
+ return IsNotNullExpression(expression=self)
270
+
271
+ def is_undefined(self) -> "IsUndefinedExpression":
272
+ from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
273
+
274
+ return IsUndefinedExpression(expression=self)
275
+
276
+ def is_not_undefined(self) -> "IsNotUndefinedExpression":
277
+ from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpression
278
+
279
+ return IsNotUndefinedExpression(expression=self)
280
+
281
+ @overload
282
+ def in_(self, other: "BaseDescriptor[_O]") -> "InExpression[_T, _O]": ...
283
+
284
+ @overload
285
+ def in_(self, other: _O) -> "InExpression[_T, _O]": ...
286
+
287
+ def in_(self, other: "Union[BaseDescriptor[_O], _O]") -> "InExpression[_T, _O]":
288
+ from vellum.workflows.expressions.in_ import InExpression
289
+
290
+ return InExpression(lhs=self, rhs=other)
291
+
292
+ @overload
293
+ def not_in(self, other: "BaseDescriptor[_O]") -> "NotInExpression[_T, _O]": ...
294
+
295
+ @overload
296
+ def not_in(self, other: _O) -> "NotInExpression[_T, _O]": ...
297
+
298
+ def not_in(self, other: "Union[BaseDescriptor[_O], _O]") -> "NotInExpression[_T, _O]":
299
+ from vellum.workflows.expressions.not_in import NotInExpression
300
+
301
+ return NotInExpression(lhs=self, rhs=other)
302
+
303
+ @overload
304
+ def between(self, start: "BaseDescriptor[_O]", end: "BaseDescriptor[_O2]") -> "BetweenExpression[_T, _O, _O2]": ...
305
+
306
+ @overload
307
+ def between(self, start: _O, end: _O2) -> "BetweenExpression[_T, _O, _O2]": ...
308
+
309
+ def between(
310
+ self, start: "Union[BaseDescriptor[_O], _O]", end: "Union[BaseDescriptor[_O2], _O2]"
311
+ ) -> "BetweenExpression[_T, _O, _O2]":
312
+ from vellum.workflows.expressions.between import BetweenExpression
313
+
314
+ return BetweenExpression(value=self, start=start, end=end)
315
+
316
+ @overload
317
+ def not_between(
318
+ self, start: "BaseDescriptor[_O]", end: "BaseDescriptor[_O2]"
319
+ ) -> "NotBetweenExpression[_T, _O, _O2]": ...
320
+
321
+ @overload
322
+ def not_between(self, start: _O, end: _O2) -> "NotBetweenExpression[_T, _O, _O2]": ...
323
+
324
+ def not_between(
325
+ self, start: "Union[BaseDescriptor[_O], _O]", end: "Union[BaseDescriptor[_O2], _O2]"
326
+ ) -> "NotBetweenExpression[_T, _O, _O2]":
327
+ from vellum.workflows.expressions.not_between import NotBetweenExpression
328
+
329
+ return NotBetweenExpression(value=self, start=start, end=end)
330
+
331
+ def is_blank(self) -> "IsBlankExpression":
332
+ from vellum.workflows.expressions.is_blank import IsBlankExpression
333
+
334
+ return IsBlankExpression(expression=self)
335
+
336
+ def is_not_blank(self) -> "IsNotBlankExpression":
337
+ from vellum.workflows.expressions.is_not_blank import IsNotBlankExpression
338
+
339
+ return IsNotBlankExpression(expression=self)
@@ -0,0 +1,83 @@
1
+ import pytest
2
+
3
+ from vellum.workflows.descriptors.utils import resolve_value
4
+ from vellum.workflows.state.base import BaseState
5
+
6
+
7
+ class FixtureState(BaseState):
8
+ alpha = 1
9
+ beta = 2
10
+
11
+ gamma = "hello"
12
+ delta = "el"
13
+
14
+ epsilon = 3
15
+ zeta = {
16
+ "foo": "bar",
17
+ }
18
+
19
+
20
+ @pytest.mark.parametrize(
21
+ ["descriptor", "expected_value"],
22
+ [
23
+ (FixtureState.alpha | FixtureState.beta, 1),
24
+ (FixtureState.alpha & FixtureState.beta, 2),
25
+ (FixtureState.beta.coalesce(FixtureState.alpha), 2),
26
+ (FixtureState.alpha.equals(FixtureState.beta), False),
27
+ (FixtureState.alpha.does_not_equal(FixtureState.beta), True),
28
+ (FixtureState.alpha.less_than(FixtureState.beta), True),
29
+ (FixtureState.alpha.greater_than(FixtureState.beta), False),
30
+ (FixtureState.alpha.less_than_or_equal_to(FixtureState.beta), True),
31
+ (FixtureState.alpha.greater_than_or_equal_to(FixtureState.beta), False),
32
+ (FixtureState.gamma.contains(FixtureState.delta), True),
33
+ (FixtureState.gamma.begins_with(FixtureState.delta), False),
34
+ (FixtureState.gamma.ends_with(FixtureState.delta), False),
35
+ (FixtureState.gamma.does_not_contain(FixtureState.delta), False),
36
+ (FixtureState.gamma.does_not_begin_with(FixtureState.delta), True),
37
+ (FixtureState.gamma.does_not_end_with(FixtureState.delta), True),
38
+ (FixtureState.alpha.is_none(), False),
39
+ (FixtureState.alpha.is_not_none(), True),
40
+ (FixtureState.delta.in_(FixtureState.gamma), True),
41
+ (FixtureState.delta.not_in(FixtureState.gamma), False),
42
+ (FixtureState.alpha.between(FixtureState.beta, FixtureState.epsilon), False),
43
+ (FixtureState.alpha.not_between(FixtureState.beta, FixtureState.epsilon), True),
44
+ (FixtureState.delta.is_blank(), False),
45
+ (FixtureState.delta.is_not_blank(), True),
46
+ (
47
+ FixtureState.alpha.equals(FixtureState.alpha)
48
+ | FixtureState.alpha.equals(FixtureState.beta) & FixtureState.alpha.equals(FixtureState.beta),
49
+ True,
50
+ ),
51
+ (FixtureState.zeta["foo"], "bar"),
52
+ ],
53
+ ids=[
54
+ "or",
55
+ "and",
56
+ "coalesce",
57
+ "eq",
58
+ "dne",
59
+ "less_than",
60
+ "greater_than",
61
+ "less_than_or_equal_to",
62
+ "greater_than_or_equal_to",
63
+ "contains",
64
+ "begins_with",
65
+ "ends_with",
66
+ "does_not_contain",
67
+ "does_not_begin_with",
68
+ "does_not_end_with",
69
+ "is_none",
70
+ "is_not_none",
71
+ "in_",
72
+ "not_in",
73
+ "between",
74
+ "not_between",
75
+ "is_blank",
76
+ "is_not_blank",
77
+ "or_and",
78
+ "accessor",
79
+ ],
80
+ )
81
+ def test_resolve_value__happy_path(descriptor, expected_value):
82
+ actual_value = resolve_value(descriptor, FixtureState())
83
+ assert actual_value == expected_value
@@ -0,0 +1,90 @@
1
+ from collections.abc import Mapping
2
+ import dataclasses
3
+ import inspect
4
+ from typing import Any, Dict, Optional, Sequence, Set, TypeVar, Union, cast, overload
5
+
6
+ from pydantic import BaseModel
7
+
8
+ from vellum.workflows.descriptors.base import BaseDescriptor
9
+ from vellum.workflows.state.base import BaseState
10
+
11
+ _T = TypeVar("_T")
12
+
13
+
14
+ @overload
15
+ def resolve_value(
16
+ value: BaseDescriptor[_T], state: BaseState, path: str = "", memo: Optional[Dict[str, Any]] = None
17
+ ) -> _T: ...
18
+
19
+
20
+ @overload
21
+ def resolve_value(value: _T, state: BaseState, path: str = "", memo: Optional[Dict[str, Any]] = None) -> _T: ...
22
+
23
+
24
+ def resolve_value(
25
+ value: Union[BaseDescriptor[_T], _T], state: BaseState, path: str = "", memo: Optional[Dict[str, Any]] = None
26
+ ) -> _T:
27
+ """
28
+ Recursively resolves Descriptor's until we have a constant value, using BaseState.
29
+
30
+ The nonideal casts in this method are due to the `isinstance` calls detaching types
31
+ from the `_T` generic.
32
+ """
33
+
34
+ if inspect.isclass(value):
35
+ return cast(_T, value)
36
+
37
+ if isinstance(value, BaseDescriptor):
38
+ resolved_value = value.resolve(state)
39
+ if memo is not None:
40
+ memo[path] = resolved_value
41
+ return resolved_value
42
+
43
+ if isinstance(value, property) or callable(value):
44
+ return cast(_T, value)
45
+
46
+ if isinstance(value, (str, bytes)):
47
+ return cast(_T, value)
48
+
49
+ if dataclasses.is_dataclass(value):
50
+ # The `inspect.isclass` check above prevents `value` from being a class.
51
+ dataclass_value = dataclasses.replace( # type: ignore[type-var]
52
+ value,
53
+ **{
54
+ field.name: resolve_value(getattr(value, field.name), state, path=f"{path}.{field.name}", memo=memo)
55
+ for field in dataclasses.fields(value)
56
+ },
57
+ )
58
+ return cast(_T, dataclass_value)
59
+
60
+ if isinstance(value, BaseModel):
61
+ pydantic_value = value.model_copy(
62
+ update={
63
+ key: resolve_value(getattr(value, key), state, path=f"{path}.{key}", memo=memo)
64
+ for key in value.dict().keys()
65
+ }
66
+ )
67
+ return cast(_T, pydantic_value)
68
+
69
+ if isinstance(value, Mapping):
70
+ mapped_value = type(value)( # type: ignore[call-arg]
71
+ {
72
+ dict_key: resolve_value(dict_value, state, path=f"{path}.{dict_key}", memo=memo)
73
+ for dict_key, dict_value in value.items()
74
+ }
75
+ )
76
+ return cast(_T, mapped_value)
77
+
78
+ if isinstance(value, Sequence):
79
+ sequence_value = type(value)(
80
+ resolve_value(seq_value, state, path=f"{path}.{index}", memo=memo) for index, seq_value in enumerate(value)
81
+ ) # type: ignore[call-arg]
82
+ return cast(_T, sequence_value)
83
+
84
+ if isinstance(value, Set):
85
+ set_value = type(value)(
86
+ resolve_value(set_value, state, path=f"{path}.{index}", memo=memo) for index, set_value in enumerate(value)
87
+ )
88
+ return cast(_T, set_value)
89
+
90
+ return value
@@ -0,0 +1,5 @@
1
+ from .edge import Edge
2
+
3
+ __all__ = [
4
+ "Edge",
5
+ ]
@@ -0,0 +1,23 @@
1
+ from typing import TYPE_CHECKING, Any, Type
2
+
3
+ if TYPE_CHECKING:
4
+ from vellum.workflows.nodes.bases import BaseNode
5
+ from vellum.workflows.ports.port import Port
6
+
7
+
8
+ class Edge:
9
+ def __init__(self, from_port: "Port", to_node: Type["BaseNode"]):
10
+ self.from_port = from_port
11
+ self.to_node = to_node
12
+
13
+ def __eq__(self, other: Any) -> bool:
14
+ if not isinstance(other, Edge):
15
+ return False
16
+
17
+ return self.from_port == other.from_port and self.to_node == other.to_node
18
+
19
+ def __hash__(self) -> int:
20
+ return hash((self.from_port, self.to_node))
21
+
22
+ def __repr__(self) -> str:
23
+ return f"{self.from_port} >> {self.to_node}"