lionagi 0.14.4__tar.gz → 0.14.6__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 (290) hide show
  1. {lionagi-0.14.4 → lionagi-0.14.6}/PKG-INFO +6 -2
  2. lionagi-0.14.6/docs/discussions/anyio-migration.md +178 -0
  3. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/branch_operations.rst +1 -3
  4. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/instruct.py +3 -17
  5. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/__init__.py +25 -1
  6. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/cancel.py +1 -1
  7. lionagi-0.14.6/lionagi/libs/concurrency/patterns.py +259 -0
  8. lionagi-0.14.6/lionagi/libs/concurrency/primitives.py +290 -0
  9. lionagi-0.14.6/lionagi/libs/concurrency/resource_tracker.py +182 -0
  10. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/task.py +4 -2
  11. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/builder.py +9 -0
  12. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/flow.py +163 -60
  13. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/pile.py +7 -10
  14. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/processor.py +53 -26
  15. lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/__init__.py +3 -0
  16. lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/models.py +235 -0
  17. lionagi-0.14.4/lionagi/service/connections/providers/claude_code_cli.py → lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/stream_cli.py +20 -80
  18. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/claude_code_.py +13 -223
  19. lionagi-0.14.6/lionagi/service/connections/providers/claude_code_cli.py +105 -0
  20. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/rate_limited_processor.py +53 -35
  21. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/branch.py +6 -51
  22. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/session.py +26 -8
  23. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/utils.py +56 -174
  24. lionagi-0.14.6/lionagi/version.py +1 -0
  25. {lionagi-0.14.4 → lionagi-0.14.6}/pyproject.toml +7 -2
  26. {lionagi-0.14.4 → lionagi-0.14.6}/uv.lock +11 -4
  27. lionagi-0.14.4/lionagi/libs/concurrency/patterns.py +0 -252
  28. lionagi-0.14.4/lionagi/libs/concurrency/primitives.py +0 -242
  29. lionagi-0.14.4/lionagi/version.py +0 -1
  30. {lionagi-0.14.4 → lionagi-0.14.6}/.coveragerc +0 -0
  31. {lionagi-0.14.4 → lionagi-0.14.6}/.env.example +0 -0
  32. {lionagi-0.14.4 → lionagi-0.14.6}/.github/FUNDING.yml +0 -0
  33. {lionagi-0.14.4 → lionagi-0.14.6}/.github/dependabot.yml +0 -0
  34. {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/ci.yml +0 -0
  35. {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/codeql.yml +0 -0
  36. {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/docs.yml +0 -0
  37. {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/release.yml +0 -0
  38. {lionagi-0.14.4 → lionagi-0.14.6}/.gitignore +0 -0
  39. {lionagi-0.14.4 → lionagi-0.14.6}/.pre-commit-config.yaml +0 -0
  40. {lionagi-0.14.4 → lionagi-0.14.6}/.python-version +0 -0
  41. {lionagi-0.14.4 → lionagi-0.14.6}/CODE_OF_CONDUCT.md +0 -0
  42. {lionagi-0.14.4 → lionagi-0.14.6}/CONTRIBUTING.md +0 -0
  43. {lionagi-0.14.4 → lionagi-0.14.6}/LICENSE +0 -0
  44. {lionagi-0.14.4 → lionagi-0.14.6}/README.md +0 -0
  45. {lionagi-0.14.4 → lionagi-0.14.6}/assets/operation_builder.gif +0 -0
  46. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/001_branch_converse.ipynb +0 -0
  47. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/002_branch_interact.ipynb +0 -0
  48. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/003_branch_info.ipynb +0 -0
  49. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/004_conversation_patterns.ipynb +0 -0
  50. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/005_react_basics.ipynb +0 -0
  51. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/006_operation_graphs_claim_extraction.ipynb +0 -0
  52. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/002_comedian.json +0 -0
  53. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/002_critic.json +0 -0
  54. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/006_lion_proof_ch2.md +0 -0
  55. {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/using_claude_code.py +0 -0
  56. {lionagi-0.14.4 → lionagi-0.14.6}/docs/Makefile +0 -0
  57. {lionagi-0.14.4 → lionagi-0.14.6}/docs/_static/custom.css +0 -0
  58. {lionagi-0.14.4 → lionagi-0.14.6}/docs/_templates/layout.html +0 -0
  59. {lionagi-0.14.4 → lionagi-0.14.6}/docs/conf.py +0 -0
  60. {lionagi-0.14.4 → lionagi-0.14.6}/docs/index.rst +0 -0
  61. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/action.rst +0 -0
  62. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/adapter.rst +0 -0
  63. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/branch.rst +0 -0
  64. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/concepts.rst +0 -0
  65. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/element_id.rst +0 -0
  66. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/event.rst +0 -0
  67. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/form.rst +0 -0
  68. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/graph.rst +0 -0
  69. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/index.rst +0 -0
  70. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/instruct.rst +0 -0
  71. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_file.rst +0 -0
  72. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_nested.rst +0 -0
  73. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_package.rst +0 -0
  74. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_schema.rst +0 -0
  75. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_validate.rst +0 -0
  76. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/log.rst +0 -0
  77. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/mail.rst +0 -0
  78. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/message.rst +0 -0
  79. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/models.rst +0 -0
  80. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/operative_step.rst +0 -0
  81. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/pile.rst +0 -0
  82. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/processor.rst +0 -0
  83. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/progression.rst +0 -0
  84. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/service.rst +0 -0
  85. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/session.rst +0 -0
  86. {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/utils.rst +0 -0
  87. {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started.rst +0 -0
  88. {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started_pt2.rst +0 -0
  89. {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started_pt3.rst +0 -0
  90. {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/index.rst +0 -0
  91. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/__init__.py +0 -0
  92. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_class_registry.py +0 -0
  93. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_errors.py +0 -0
  94. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_types.py +0 -0
  95. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/__init__.py +0 -0
  96. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/async_postgres_adapter.py +0 -0
  97. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/postgres_model_adapter.py +0 -0
  98. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/config.py +0 -0
  99. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/__init__.py +0 -0
  100. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/action.py +0 -0
  101. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/base.py +0 -0
  102. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/code.py +0 -0
  103. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/file.py +0 -0
  104. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/reason.py +0 -0
  105. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/research.py +0 -0
  106. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/__init__.py +0 -0
  107. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/errors.py +0 -0
  108. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/__init__.py +0 -0
  109. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/chunk.py +0 -0
  110. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/concat.py +0 -0
  111. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/concat_files.py +0 -0
  112. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/file_ops.py +0 -0
  113. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/params.py +0 -0
  114. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/process.py +0 -0
  115. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/save.py +0 -0
  116. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/__init__.py +0 -0
  117. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/flatten.py +0 -0
  118. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nfilter.py +0 -0
  119. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nget.py +0 -0
  120. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/ninsert.py +0 -0
  121. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nmerge.py +0 -0
  122. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/npop.py +0 -0
  123. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nset.py +0 -0
  124. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/unflatten.py +0 -0
  125. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/utils.py +0 -0
  126. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/__init__.py +0 -0
  127. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/imports.py +0 -0
  128. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/management.py +0 -0
  129. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/params.py +0 -0
  130. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/system.py +0 -0
  131. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/parse.py +0 -0
  132. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/__init__.py +0 -0
  133. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/as_readable.py +0 -0
  134. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/extract_code_block.py +0 -0
  135. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/extract_docstring.py +0 -0
  136. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/function_to_schema.py +0 -0
  137. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/json_schema.py +0 -0
  138. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/load_pydantic_model_from_schema.py +0 -0
  139. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/__init__.py +0 -0
  140. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/base.py +0 -0
  141. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/llmlingua.py +0 -0
  142. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/perplexity.py +0 -0
  143. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/symbolic_compress_context.py +0 -0
  144. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang.py +0 -0
  145. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/base.py +0 -0
  146. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/abstract_algebra.toml +0 -0
  147. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/category_theory.toml +0 -0
  148. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/complex_analysis.toml +0 -0
  149. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/framework_options.json +0 -0
  150. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/group_theory.toml +0 -0
  151. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/math_logic.toml +0 -0
  152. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/reflective_patterns.toml +0 -0
  153. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/set_theory.toml +0 -0
  154. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/topology_fundamentals.toml +0 -0
  155. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/lion_emoji_mapping.toml +0 -0
  156. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/python_math_mapping.toml +0 -0
  157. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/rust_chinese_mapping.toml +0 -0
  158. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/utility/base_synthlang_system_prompt.toml +0 -0
  159. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/translate_to_synthlang.py +0 -0
  160. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/types.py +0 -0
  161. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/__init__.py +0 -0
  162. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/common_field_validators.py +0 -0
  163. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/fuzzy_match_keys.py +0 -0
  164. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/fuzzy_validate_mapping.py +0 -0
  165. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/string_similarity.py +0 -0
  166. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/validate_boolean.py +0 -0
  167. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/__init__.py +0 -0
  168. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/field_model.py +0 -0
  169. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/hashable_model.py +0 -0
  170. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/model_params.py +0 -0
  171. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/note.py +0 -0
  172. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/operable_model.py +0 -0
  173. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/schema_model.py +0 -0
  174. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/ReAct.py +0 -0
  175. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/__init__.py +0 -0
  176. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/utils.py +0 -0
  177. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/__init__.py +0 -0
  178. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/_act/__init__.py +0 -0
  179. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/_act/act.py +0 -0
  180. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/__init__.py +0 -0
  181. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/brainstorm.py +0 -0
  182. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/prompt.py +0 -0
  183. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/chat/__init__.py +0 -0
  184. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/chat/chat.py +0 -0
  185. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/communicate/__init__.py +0 -0
  186. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/communicate/communicate.py +0 -0
  187. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/instruct/__init__.py +0 -0
  188. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/instruct/instruct.py +0 -0
  189. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/interpret/__init__.py +0 -0
  190. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/interpret/interpret.py +0 -0
  191. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/manager.py +0 -0
  192. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/node.py +0 -0
  193. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/operate/__init__.py +0 -0
  194. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/operate/operate.py +0 -0
  195. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/parse/__init__.py +0 -0
  196. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/parse/parse.py +0 -0
  197. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/__init__.py +0 -0
  198. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/plan.py +0 -0
  199. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/prompt.py +0 -0
  200. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/__init__.py +0 -0
  201. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/select.py +0 -0
  202. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/utils.py +0 -0
  203. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/translate/__init__.py +0 -0
  204. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/translate/translate.py +0 -0
  205. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/types.py +0 -0
  206. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/utils.py +0 -0
  207. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/__init__.py +0 -0
  208. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/_concepts.py +0 -0
  209. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/__init__.py +0 -0
  210. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/function_calling.py +0 -0
  211. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/manager.py +0 -0
  212. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/tool.py +0 -0
  213. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/__init__.py +0 -0
  214. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/base.py +0 -0
  215. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/flow.py +0 -0
  216. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/form.py +0 -0
  217. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/report.py +0 -0
  218. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/__init__.py +0 -0
  219. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/element.py +0 -0
  220. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/event.py +0 -0
  221. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/log.py +0 -0
  222. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/progression.py +0 -0
  223. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/__init__.py +0 -0
  224. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/edge.py +0 -0
  225. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/graph.py +0 -0
  226. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/node.py +0 -0
  227. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/__init__.py +0 -0
  228. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/exchange.py +0 -0
  229. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/mail.py +0 -0
  230. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/mailbox.py +0 -0
  231. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/manager.py +0 -0
  232. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/package.py +0 -0
  233. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/__init__.py +0 -0
  234. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/action_request.py +0 -0
  235. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/action_response.py +0 -0
  236. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/assistant_response.py +0 -0
  237. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/base.py +0 -0
  238. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/instruction.py +0 -0
  239. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/manager.py +0 -0
  240. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/message.py +0 -0
  241. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/system.py +0 -0
  242. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/README.md +0 -0
  243. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/action_request.jinja2 +0 -0
  244. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/action_response.jinja2 +0 -0
  245. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -0
  246. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -0
  247. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/system_message.jinja2 +0 -0
  248. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -0
  249. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/__init__.py +0 -0
  250. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/operative.py +0 -0
  251. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/step.py +0 -0
  252. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/types.py +0 -0
  253. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/py.typed +0 -0
  254. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/__init__.py +0 -0
  255. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/__init__.py +0 -0
  256. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/api_calling.py +0 -0
  257. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/endpoint.py +0 -0
  258. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/endpoint_config.py +0 -0
  259. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/header_factory.py +0 -0
  260. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/match_endpoint.py +0 -0
  261. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/__init__.py +0 -0
  262. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/anthropic_.py +0 -0
  263. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/exa_.py +0 -0
  264. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/oai_.py +0 -0
  265. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/ollama_.py +0 -0
  266. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/perplexity_.py +0 -0
  267. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/imodel.py +0 -0
  268. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/manager.py +0 -0
  269. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/resilience.py +0 -0
  270. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/README.md +0 -0
  271. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/__init__.py +0 -0
  272. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/anthropic_models.py +0 -0
  273. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/exa_models.py +0 -0
  274. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/openai_models.py +0 -0
  275. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/pplx_models.py +0 -0
  276. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/token_calculator.py +0 -0
  277. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/types.py +0 -0
  278. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/__init__.py +0 -0
  279. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/prompts.py +0 -0
  280. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/settings.py +0 -0
  281. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/__init__.py +0 -0
  282. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/base.py +0 -0
  283. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/file/__init__.py +0 -0
  284. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/file/reader.py +0 -0
  285. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/memory/tools.py +0 -0
  286. {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/types.py +0 -0
  287. {lionagi-0.14.4 → lionagi-0.14.6}/main.py +0 -0
  288. {lionagi-0.14.4 → lionagi-0.14.6}/scripts/README.md +0 -0
  289. {lionagi-0.14.4 → lionagi-0.14.6}/scripts/concat.py +0 -0
  290. {lionagi-0.14.4 → lionagi-0.14.6}/scripts/config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lionagi
3
- Version: 0.14.4
3
+ Version: 0.14.6
4
4
  Summary: An Intelligence Operating System.
5
5
  Author-email: HaiyangLi <quantocean.li@gmail.com>, Liangbingyan Luo <llby_luo@outlook.com>
6
6
  License: Apache License
@@ -225,7 +225,6 @@ Requires-Dist: anyio>=4.8.0
225
225
  Requires-Dist: backoff>=2.2.1
226
226
  Requires-Dist: jinja2>=3.1.0
227
227
  Requires-Dist: json-repair>=0.40.0
228
- Requires-Dist: matplotlib>=3.9.0
229
228
  Requires-Dist: pillow>=11.0.0
230
229
  Requires-Dist: psutil>=7.0.0
231
230
  Requires-Dist: pydantic-settings>=2.8.0
@@ -239,6 +238,8 @@ Requires-Dist: claude-code-sdk>=0.0.14; extra == 'all'
239
238
  Requires-Dist: datamodel-code-generator>=0.31.2; extra == 'all'
240
239
  Requires-Dist: docling>=2.15.1; extra == 'all'
241
240
  Requires-Dist: fastmcp>=2.10.5; extra == 'all'
241
+ Requires-Dist: matplotlib>=3.7.0; extra == 'all'
242
+ Requires-Dist: networkx>=3.0.0; extra == 'all'
242
243
  Requires-Dist: ollama>=0.4.0; extra == 'all'
243
244
  Requires-Dist: pydapter[postgres]; extra == 'all'
244
245
  Requires-Dist: rich>=13.0.0; extra == 'all'
@@ -248,6 +249,9 @@ Provides-Extra: docs
248
249
  Requires-Dist: furo>=2024.8.6; extra == 'docs'
249
250
  Requires-Dist: sphinx-autobuild>=2024.10.3; extra == 'docs'
250
251
  Requires-Dist: sphinx>=8.1.3; extra == 'docs'
252
+ Provides-Extra: graph
253
+ Requires-Dist: matplotlib>=3.7.0; extra == 'graph'
254
+ Requires-Dist: networkx>=3.0.0; extra == 'graph'
251
255
  Provides-Extra: lint
252
256
  Requires-Dist: black[jupyter]>=24.10.0; extra == 'lint'
253
257
  Requires-Dist: isort>=5.13.2; extra == 'lint'
@@ -0,0 +1,178 @@
1
+ # Migration to AnyIO: A Technical Discussion
2
+
3
+ ## Overview
4
+
5
+ In our latest release (v0.14.5), we've made a strategic decision to migrate our asynchronous primitives from raw `asyncio` to `anyio`. This document discusses the rationale, implications, and future benefits of this architectural change.
6
+
7
+ ## Why AnyIO?
8
+
9
+ ### 1. Backend Agnosticism
10
+
11
+ AnyIO provides a unified interface that works with multiple async backends:
12
+ - **asyncio** (current Python standard)
13
+ - **trio** (structured concurrency pioneer)
14
+ - **curio** (high-performance alternative)
15
+
16
+ This abstraction layer means our codebase is no longer tightly coupled to asyncio's implementation details. As the Python async ecosystem evolves, we can adapt without major rewrites.
17
+
18
+ ### 2. Structured Concurrency by Default
19
+
20
+ ```python
21
+ # Before (asyncio)
22
+ tasks = []
23
+ for item in items:
24
+ task = asyncio.create_task(process(item))
25
+ tasks.append(task)
26
+ results = await asyncio.gather(*tasks) # What if a task fails?
27
+
28
+ # After (anyio)
29
+ async with create_task_group() as tg:
30
+ for item in items:
31
+ await tg.start_soon(process, item)
32
+ # All tasks are guaranteed to complete or cancel properly
33
+ ```
34
+
35
+ Task groups enforce a fundamental principle: **no task outlives its parent scope**. This eliminates entire classes of bugs related to orphaned tasks and resource leaks.
36
+
37
+ ### 3. Better Cancellation Semantics
38
+
39
+ AnyIO's cancellation model is more predictable:
40
+ - Cancellation is always delivered at checkpoint
41
+ - Cancel scopes provide fine-grained control
42
+ - No more mysterious "Task was destroyed but it is pending!" errors
43
+
44
+ ```python
45
+ # Timeout with proper cancellation
46
+ with anyio.move_on_after(5.0) as cancel_scope:
47
+ result = await long_running_operation()
48
+ if cancel_scope.cancelled_caught:
49
+ print("Operation timed out cleanly")
50
+ ```
51
+
52
+ ## Implementation Highlights
53
+
54
+ ### Thread-to-Async Bridge
55
+
56
+ One subtle but important change is how we handle sync functions in async contexts:
57
+
58
+ ```python
59
+ # Before
60
+ result = await asyncio.to_thread(sync_func, arg)
61
+
62
+ # After
63
+ result = await anyio.to_thread.run_sync(sync_func, arg)
64
+ ```
65
+
66
+ AnyIO's approach provides:
67
+ - Better thread pool management
68
+ - Proper cancellation propagation to threads
69
+ - Consistent behavior across different async backends
70
+
71
+ ### Unified Lock Semantics
72
+
73
+ Our `ConcurrencyLock` now wraps AnyIO's primitives, providing:
74
+ - Same lock works in both sync and async contexts
75
+ - No more deadlocks from mixing lock types
76
+ - Better debugging with lock ownership tracking
77
+
78
+ ### Sleep and Timing
79
+
80
+ ```python
81
+ # Before
82
+ await asyncio.sleep(1.0)
83
+
84
+ # After
85
+ await anyio.sleep(1.0)
86
+ ```
87
+
88
+ While seemingly trivial, AnyIO's sleep:
89
+ - Respects cancellation properly
90
+ - Works consistently across backends
91
+ - Integrates with structured concurrency
92
+
93
+ ## Performance Implications
94
+
95
+ ### The Good
96
+ 1. **Better CPU utilization**: Task groups reduce overhead compared to gather/wait
97
+ 2. **Memory efficiency**: Structured concurrency prevents task accumulation
98
+ 3. **Predictable performance**: Consistent behavior reduces edge-case slowdowns
99
+
100
+ ### The Trade-offs
101
+ 1. **Slight overhead**: Abstraction layer adds minimal overhead (~5-10%)
102
+ 2. **Learning curve**: Developers need to understand structured concurrency
103
+ 3. **Ecosystem compatibility**: Some asyncio-specific libraries need adapters
104
+
105
+ ## Future Benefits
106
+
107
+ ### 1. Trio Compatibility
108
+ We can now experiment with trio as a backend for specific use cases:
109
+ ```python
110
+ # Run lionagi with trio backend
111
+ import trio
112
+ import anyio
113
+
114
+ async def main():
115
+ async with anyio.from_thread.start_blocking_portal(
116
+ backend="trio", backend_options={"trio_token": trio.lowlevel.current_trio_token()}
117
+ ) as portal:
118
+ await portal.call(run_lionagi_operation)
119
+ ```
120
+
121
+ ### 2. Better Testing
122
+ AnyIO provides excellent testing utilities:
123
+ ```python
124
+ async def test_timeout_handling():
125
+ with anyio.move_on_after(0.1):
126
+ await anyio.sleep(1.0) # Will be cancelled
127
+ # Test continues normally
128
+ ```
129
+
130
+ ### 3. WebAssembly Ready
131
+ As Python moves toward WASM support, AnyIO's abstraction will help us adapt to environments where traditional threading doesn't exist.
132
+
133
+ ## Migration Patterns
134
+
135
+ ### For Library Users
136
+ Most changes are transparent, but be aware of:
137
+ - Different exception types (use `anyio.get_cancelled_exc_class()`)
138
+ - Import changes if you were using our internal async utilities
139
+ - Slightly different timeout behavior (more predictable)
140
+
141
+ ### for Library Developers
142
+ Key patterns to adopt:
143
+ ```python
144
+ # Always use task groups for concurrent operations
145
+ async with create_task_group() as tg:
146
+ await tg.start_soon(task1)
147
+ await tg.start_soon(task2)
148
+
149
+ # Use cancel scopes for timeouts
150
+ with anyio.move_on_after(timeout):
151
+ result = await operation()
152
+
153
+ # Handle backend-specific exceptions
154
+ try:
155
+ await operation()
156
+ except anyio.get_cancelled_exc_class():
157
+ # Properly handle cancellation
158
+ raise
159
+ ```
160
+
161
+ ## Philosophical Alignment
162
+
163
+ This migration aligns with our core principles:
164
+
165
+ 1. **Reliability First**: Structured concurrency eliminates entire bug categories
166
+ 2. **Future-Proof Design**: Backend agnosticism protects against ecosystem changes
167
+ 3. **Developer Experience**: Clearer mental models reduce cognitive load
168
+ 4. **Performance with Correctness**: We optimize without sacrificing safety
169
+
170
+ ## Conclusion
171
+
172
+ The migration to AnyIO represents a maturation of our async architecture. While the immediate benefits include better resource management and fewer concurrency bugs, the long-term value lies in our ability to evolve with the Python async ecosystem.
173
+
174
+ This change positions lionagi to take advantage of future innovations in async Python while providing our users with a more reliable and predictable experience today.
175
+
176
+ ---
177
+
178
+ *For specific migration examples and API changes, see our [v0.14.6 Release Notes](./RELEASE_NOTES_v0.14.5.md).*
@@ -64,7 +64,7 @@ of action requests. Optionally retries, logs errors, or merges results.
64
64
  This is best used for **explicit function calls** triggered from user
65
65
  instructions or system logic.
66
66
 
67
- .. method:: Branch.act(action_request, *, suppress_errors=True, sanitize_input=False, unique_input=False, num_retries=0, initial_delay=0, retry_delay=0, backoff_factor=1, retry_default=UNDEFINED, retry_timeout=None, retry_timing=False, max_concurrent=None, throttle_period=None, flatten=True, dropna=True, unique_output=False, flatten_tuple_set=False)
67
+ .. method:: Branch.act(action_request, *, suppress_errors=True, sanitize_input=False, unique_input=False, num_retries=0, initial_delay=0, retry_delay=0, backoff_factor=1, retry_default=UNDEFINED, retry_timeout=None, max_concurrent=None, throttle_period=None, flatten=True, dropna=True, unique_output=False, flatten_tuple_set=False)
68
68
 
69
69
  Public, potentially batched, asynchronous interface to run one or multiple action requests.
70
70
 
@@ -90,8 +90,6 @@ instructions or system logic.
90
90
  Fallback value if all retries fail (if suppressing errors)
91
91
  retry_timeout : float | None, default=None
92
92
  Overall timeout for all attempts (None = no limit)
93
- retry_timing : bool, default=False
94
- If True, track time used for retries
95
93
  max_concurrent : int | None, default=None
96
94
  Maximum concurrent tasks (if batching)
97
95
  throttle_period : float | None, default=None
@@ -35,7 +35,6 @@ class Instruct(HashableModel):
35
35
  "reason",
36
36
  "actions",
37
37
  "action_strategy",
38
- "batch_size",
39
38
  "request_params",
40
39
  "response_params",
41
40
  ]
@@ -97,16 +96,10 @@ class Instruct(HashableModel):
97
96
  "None: Contextual execution."
98
97
  ),
99
98
  )
100
- action_strategy: Literal["batch", "sequential", "concurrent"] | None = (
101
- Field(
102
- None,
103
- description="Action strategy to use for executing actions. Default "
104
- "is 'concurrent'. Only provide for if actions are enabled.",
105
- )
106
- )
107
- batch_size: int | None = Field(
99
+ action_strategy: Literal["sequential", "concurrent"] | None = Field(
108
100
  None,
109
- description="Batch size for executing actions. Only provide for 'batch' strategy.",
101
+ description="Action strategy to use for executing actions. Default "
102
+ "is 'concurrent'. Only provide for if actions are enabled.",
110
103
  )
111
104
 
112
105
  @field_validator("instruction", "guidance", "context", mode="before")
@@ -123,13 +116,6 @@ class Instruct(HashableModel):
123
116
  return "concurrent"
124
117
  return v
125
118
 
126
- @field_validator("batch_size", mode="before")
127
- def _validate_batch_size(cls, v):
128
- try:
129
- return to_num(v, num_type=int)
130
- except Exception:
131
- return None
132
-
133
119
 
134
120
  class InstructResponse(HashableModel):
135
121
  instruct: Instruct
@@ -1,4 +1,4 @@
1
- """Structured concurrency primitives.
1
+ """Structured concurrency primitives for pynector.
2
2
 
3
3
  This module provides structured concurrency primitives using AnyIO,
4
4
  which allows for consistent behavior across asyncio and trio backends.
@@ -6,7 +6,21 @@ which allows for consistent behavior across asyncio and trio backends.
6
6
 
7
7
  from .cancel import CancelScope, fail_after, move_on_after
8
8
  from .errors import get_cancelled_exc_class, shield
9
+ from .patterns import (
10
+ ConnectionPool,
11
+ WorkerPool,
12
+ parallel_requests,
13
+ retry_with_timeout,
14
+ )
9
15
  from .primitives import CapacityLimiter, Condition, Event, Lock, Semaphore
16
+ from .resource_tracker import (
17
+ ResourceTracker,
18
+ cleanup_check,
19
+ get_global_tracker,
20
+ resource_leak_detector,
21
+ track_resource,
22
+ untrack_resource,
23
+ )
10
24
  from .task import TaskGroup, create_task_group
11
25
 
12
26
  __all__ = [
@@ -15,6 +29,10 @@ __all__ = [
15
29
  "CancelScope",
16
30
  "move_on_after",
17
31
  "fail_after",
32
+ "ConnectionPool",
33
+ "WorkerPool",
34
+ "parallel_requests",
35
+ "retry_with_timeout",
18
36
  "Lock",
19
37
  "Semaphore",
20
38
  "CapacityLimiter",
@@ -22,4 +40,10 @@ __all__ = [
22
40
  "Condition",
23
41
  "get_cancelled_exc_class",
24
42
  "shield",
43
+ "ResourceTracker",
44
+ "resource_leak_detector",
45
+ "track_resource",
46
+ "untrack_resource",
47
+ "cleanup_check",
48
+ "get_global_tracker",
25
49
  ]
@@ -4,7 +4,7 @@ import time
4
4
  from collections.abc import Iterator
5
5
  from contextlib import contextmanager
6
6
  from types import TracebackType
7
- from typing import Optional, TypeVar
7
+ from typing import TypeVar
8
8
 
9
9
  import anyio
10
10
 
@@ -0,0 +1,259 @@
1
+ """Common concurrency patterns for structured concurrency."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Awaitable, Callable
7
+ from types import TracebackType
8
+ from typing import Any, TypeVar
9
+
10
+ import anyio
11
+
12
+ from .cancel import move_on_after
13
+ from .primitives import CapacityLimiter, Lock
14
+ from .resource_tracker import track_resource, untrack_resource
15
+ from .task import create_task_group
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ T = TypeVar("T")
20
+ R = TypeVar("R")
21
+ Response = TypeVar("Response")
22
+
23
+
24
+ class ConnectionPool:
25
+ """A pool of reusable connections."""
26
+
27
+ def __init__(
28
+ self,
29
+ max_connections: int,
30
+ connection_factory: Callable[[], Awaitable[T]],
31
+ ):
32
+ """Initialize a new connection pool."""
33
+ if max_connections < 1:
34
+ raise ValueError("max_connections must be >= 1")
35
+ if not callable(connection_factory):
36
+ raise ValueError("connection_factory must be callable")
37
+
38
+ self._connection_factory = connection_factory
39
+ self._limiter = CapacityLimiter(max_connections)
40
+ self._connections: list[T] = []
41
+ self._lock = Lock()
42
+
43
+ track_resource(self, f"ConnectionPool-{id(self)}", "ConnectionPool")
44
+
45
+ def __del__(self):
46
+ """Clean up resource tracking."""
47
+ try:
48
+ untrack_resource(self)
49
+ except Exception:
50
+ pass
51
+
52
+ async def acquire(self) -> T:
53
+ """Acquire a connection from the pool."""
54
+ await self._limiter.acquire()
55
+
56
+ try:
57
+ async with self._lock:
58
+ if self._connections:
59
+ return self._connections.pop()
60
+
61
+ # No pooled connection available, create new one
62
+ return await self._connection_factory()
63
+ except Exception:
64
+ self._limiter.release()
65
+ raise
66
+
67
+ async def release(self, connection: T) -> None:
68
+ """Release a connection back to the pool."""
69
+ try:
70
+ async with self._lock:
71
+ self._connections.append(connection)
72
+ finally:
73
+ self._limiter.release()
74
+
75
+ async def __aenter__(self) -> ConnectionPool[T]:
76
+ """Enter the connection pool context."""
77
+ return self
78
+
79
+ async def __aexit__(
80
+ self,
81
+ exc_type: type[BaseException] | None,
82
+ exc_val: BaseException | None,
83
+ exc_tb: TracebackType | None,
84
+ ) -> None:
85
+ """Exit the connection pool context."""
86
+ # Clean up any remaining connections
87
+ async with self._lock:
88
+ self._connections.clear()
89
+
90
+
91
+ async def parallel_requests(
92
+ inputs: list[str],
93
+ func: Callable[[str], Awaitable[Response]],
94
+ max_concurrency: int = 10,
95
+ ) -> list[Response]:
96
+ """Execute requests in parallel with controlled concurrency.
97
+
98
+ Args:
99
+ inputs: List of inputs
100
+ fetch_func: Async function
101
+ max_concurrency: Maximum number of concurrent requests
102
+
103
+ Returns:
104
+ List of responses in the same order as inputs
105
+ """
106
+ if not inputs:
107
+ return []
108
+
109
+ results: list[Response | None] = [None] * len(inputs)
110
+
111
+ async def bounded_fetch(
112
+ semaphore: anyio.Semaphore, idx: int, url: str
113
+ ) -> None:
114
+ async with semaphore:
115
+ results[idx] = await func(url)
116
+
117
+ try:
118
+ async with create_task_group() as tg:
119
+ semaphore = anyio.Semaphore(max_concurrency)
120
+
121
+ for i, inp in enumerate(inputs):
122
+ await tg.start_soon(bounded_fetch, semaphore, i, inp)
123
+ except BaseException as e:
124
+ # Re-raise the first exception directly instead of ExceptionGroup
125
+ if hasattr(e, "exceptions") and e.exceptions:
126
+ raise e.exceptions[0]
127
+ else:
128
+ raise
129
+
130
+ return results # type: ignore
131
+
132
+
133
+ async def retry_with_timeout(
134
+ func: Callable[[], Awaitable[T]],
135
+ max_retries: int = 3,
136
+ timeout: float = 30.0,
137
+ backoff_factor: float = 1.0,
138
+ ) -> T:
139
+ """Retry an async function with exponential backoff and timeout.
140
+
141
+ Args:
142
+ func: The async function to retry
143
+ max_retries: Maximum number of retries
144
+ timeout: Timeout for each attempt
145
+ backoff_factor: Multiplier for exponential backoff
146
+
147
+ Returns:
148
+ The result of the successful function call
149
+
150
+ Raises:
151
+ Exception: The last exception raised by the function
152
+ """
153
+ last_exception = None
154
+
155
+ for attempt in range(max_retries):
156
+ try:
157
+ with move_on_after(timeout) as cancel_scope:
158
+ result = await func()
159
+ if not cancel_scope.cancelled_caught:
160
+ return result
161
+ else:
162
+ raise TimeoutError(f"Function timed out after {timeout}s")
163
+ except Exception as e:
164
+ last_exception = e
165
+ if attempt < max_retries - 1:
166
+ delay = backoff_factor * (2**attempt)
167
+ await anyio.sleep(delay)
168
+ continue
169
+
170
+ if last_exception:
171
+ raise last_exception
172
+ else:
173
+ raise RuntimeError("Retry failed without capturing exception")
174
+
175
+
176
+ class WorkerPool:
177
+ """A pool of worker tasks that process items from a queue."""
178
+
179
+ def __init__(
180
+ self, num_workers: int, worker_func: Callable[[Any], Awaitable[None]]
181
+ ):
182
+ """Initialize a new worker pool."""
183
+ if num_workers < 1:
184
+ raise ValueError("num_workers must be >= 1")
185
+ if not callable(worker_func):
186
+ raise ValueError("worker_func must be callable")
187
+
188
+ self._num_workers = num_workers
189
+ self._worker_func = worker_func
190
+ self._queue = anyio.create_memory_object_stream(1000)
191
+ self._task_group = None
192
+
193
+ track_resource(self, f"WorkerPool-{id(self)}", "WorkerPool")
194
+
195
+ def __del__(self):
196
+ """Clean up resource tracking."""
197
+ try:
198
+ untrack_resource(self)
199
+ except Exception:
200
+ pass
201
+
202
+ async def start(self) -> None:
203
+ """Start the worker pool."""
204
+ if self._task_group is not None:
205
+ raise RuntimeError("Worker pool is already started")
206
+
207
+ self._task_group = create_task_group()
208
+ await self._task_group.__aenter__()
209
+
210
+ # Start worker tasks
211
+ for i in range(self._num_workers):
212
+ await self._task_group.start_soon(self._worker_loop)
213
+
214
+ async def stop(self) -> None:
215
+ """Stop the worker pool."""
216
+ if self._task_group is None:
217
+ return
218
+
219
+ # Close the queue to signal workers to stop
220
+ await self._queue[0].aclose()
221
+
222
+ # Wait for all workers to finish
223
+ try:
224
+ await self._task_group.__aexit__(None, None, None)
225
+ finally:
226
+ self._task_group = None
227
+
228
+ async def submit(self, item: Any) -> None:
229
+ """Submit an item for processing."""
230
+ if self._task_group is None:
231
+ raise RuntimeError("Worker pool is not started")
232
+ await self._queue[0].send(item)
233
+
234
+ async def _worker_loop(self) -> None:
235
+ """Main loop for worker tasks."""
236
+ try:
237
+ async with self._queue[1]:
238
+ async for item in self._queue[1]:
239
+ try:
240
+ await self._worker_func(item)
241
+ except Exception as e:
242
+ logger.error(f"Worker error processing item: {e}")
243
+ except anyio.ClosedResourceError:
244
+ # Queue was closed, worker should exit gracefully
245
+ pass
246
+
247
+ async def __aenter__(self) -> WorkerPool:
248
+ """Enter the worker pool context."""
249
+ await self.start()
250
+ return self
251
+
252
+ async def __aexit__(
253
+ self,
254
+ exc_type: type[BaseException] | None,
255
+ exc_val: BaseException | None,
256
+ exc_tb: TracebackType | None,
257
+ ) -> None:
258
+ """Exit the worker pool context."""
259
+ await self.stop()