lionagi 0.14.4__tar.gz → 0.14.5__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.
- {lionagi-0.14.4 → lionagi-0.14.5}/PKG-INFO +6 -2
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/branch_operations.rst +1 -3
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/concurrency/__init__.py +25 -1
- lionagi-0.14.5/lionagi/libs/concurrency/patterns.py +259 -0
- lionagi-0.14.5/lionagi/libs/concurrency/primitives.py +290 -0
- lionagi-0.14.5/lionagi/libs/concurrency/resource_tracker.py +182 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/concurrency/task.py +4 -2
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/pile.py +16 -38
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/processor.py +53 -26
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/rate_limited_processor.py +53 -35
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/session/branch.py +0 -5
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/utils.py +56 -174
- lionagi-0.14.5/lionagi/version.py +1 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/pyproject.toml +7 -2
- {lionagi-0.14.4 → lionagi-0.14.5}/uv.lock +11 -4
- lionagi-0.14.4/lionagi/libs/concurrency/patterns.py +0 -252
- lionagi-0.14.4/lionagi/libs/concurrency/primitives.py +0 -242
- lionagi-0.14.4/lionagi/version.py +0 -1
- {lionagi-0.14.4 → lionagi-0.14.5}/.coveragerc +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.env.example +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/FUNDING.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/dependabot.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/workflows/ci.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/workflows/codeql.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/workflows/docs.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.github/workflows/release.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.gitignore +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.pre-commit-config.yaml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/.python-version +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/CODE_OF_CONDUCT.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/CONTRIBUTING.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/LICENSE +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/assets/operation_builder.gif +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/001_branch_converse.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/002_branch_interact.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/003_branch_info.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/004_conversation_patterns.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/005_react_basics.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/006_operation_graphs_claim_extraction.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/data/002_comedian.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/data/002_critic.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/data/006_lion_proof_ch2.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/cookbooks/using_claude_code.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/Makefile +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/_static/custom.css +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/_templates/layout.html +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/conf.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/action.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/adapter.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/branch.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/concepts.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/element_id.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/event.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/form.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/graph.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/instruct.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/lib_file.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/lib_nested.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/lib_package.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/lib_schema.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/lib_validate.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/log.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/mail.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/message.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/models.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/operative_step.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/pile.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/processor.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/progression.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/service.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/session.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/modules/utils.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/tutorials/get_started.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/tutorials/get_started_pt2.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/tutorials/get_started_pt3.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/docs/tutorials/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/_class_registry.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/_errors.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/_types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/adapters/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/adapters/async_postgres_adapter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/adapters/postgres_model_adapter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/config.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/action.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/code.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/file.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/instruct.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/reason.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/fields/research.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/concurrency/cancel.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/concurrency/errors.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/chunk.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/concat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/concat_files.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/file_ops.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/process.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/file/save.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/flatten.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/nfilter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/nget.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/ninsert.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/nmerge.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/npop.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/nset.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/unflatten.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/nested/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/package/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/package/imports.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/package/management.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/package/params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/package/system.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/parse.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/as_readable.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/extract_code_block.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/extract_docstring.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/function_to_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/json_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/schema/load_pydantic_model_from_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/llmlingua.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/perplexity.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/symbolic_compress_context.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/abstract_algebra.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/category_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/complex_analysis.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/framework_options.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/group_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/math_logic.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/reflective_patterns.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/set_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/frameworks/topology_fundamentals.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/mapping/lion_emoji_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/mapping/python_math_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/mapping/rust_chinese_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/resources/utility/base_synthlang_system_prompt.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/synthlang_/translate_to_synthlang.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/token_transform/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/common_field_validators.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/fuzzy_match_keys.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/fuzzy_validate_mapping.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/string_similarity.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/libs/validate/validate_boolean.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/field_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/hashable_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/model_params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/note.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/operable_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/models/schema_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/ReAct/ReAct.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/ReAct/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/ReAct/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/_act/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/_act/act.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/brainstorm/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/brainstorm/brainstorm.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/brainstorm/prompt.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/builder.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/chat/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/chat/chat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/communicate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/communicate/communicate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/flow.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/instruct/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/instruct/instruct.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/interpret/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/interpret/interpret.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/node.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/operate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/operate/operate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/parse/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/parse/parse.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/plan/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/plan/plan.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/plan/prompt.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/select/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/select/select.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/select/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/translate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/translate/translate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/operations/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/_concepts.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/action/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/action/function_calling.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/action/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/action/tool.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/forms/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/forms/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/forms/flow.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/forms/form.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/forms/report.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/element.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/event.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/log.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/generic/progression.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/graph/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/graph/edge.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/graph/graph.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/graph/node.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/exchange.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/mail.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/mailbox.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/mail/package.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/action_request.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/action_response.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/assistant_response.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/instruction.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/message.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/system.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/action_request.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/action_response.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/system_message.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/operatives/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/operatives/operative.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/operatives/step.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/protocols/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/py.typed +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/api_calling.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/endpoint.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/endpoint_config.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/header_factory.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/match_endpoint.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/anthropic_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/claude_code_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/claude_code_cli.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/exa_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/oai_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/ollama_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/connections/providers/perplexity_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/imodel.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/resilience.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/anthropic_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/exa_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/openai_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/third_party/pplx_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/token_calculator.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/service/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/session/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/session/prompts.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/session/session.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/settings.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/file/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/file/reader.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/memory/tools.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/lionagi/tools/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/main.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/scripts/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/scripts/concat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.5}/scripts/config.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.14.
|
3
|
+
Version: 0.14.5
|
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'
|
@@ -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,
|
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
|
@@ -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
|
]
|
@@ -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()
|
@@ -0,0 +1,290 @@
|
|
1
|
+
"""Resource management primitives for structured concurrency.
|
2
|
+
|
3
|
+
Pure async primitives focused on correctness and simplicity.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import math
|
7
|
+
from types import TracebackType
|
8
|
+
|
9
|
+
import anyio
|
10
|
+
|
11
|
+
from .resource_tracker import track_resource, untrack_resource
|
12
|
+
|
13
|
+
|
14
|
+
class Lock:
|
15
|
+
"""A mutex lock for controlling access to a shared resource."""
|
16
|
+
|
17
|
+
def __init__(self):
|
18
|
+
"""Initialize a new lock."""
|
19
|
+
self._lock = anyio.Lock()
|
20
|
+
self._acquired = False
|
21
|
+
track_resource(self, f"Lock-{id(self)}", "Lock")
|
22
|
+
|
23
|
+
def __del__(self):
|
24
|
+
"""Clean up resource tracking when lock is destroyed."""
|
25
|
+
try:
|
26
|
+
untrack_resource(self)
|
27
|
+
except Exception:
|
28
|
+
pass
|
29
|
+
|
30
|
+
async def __aenter__(self) -> None:
|
31
|
+
"""Acquire the lock."""
|
32
|
+
await self._lock.acquire()
|
33
|
+
self._acquired = True
|
34
|
+
|
35
|
+
async def __aexit__(
|
36
|
+
self,
|
37
|
+
exc_type: type[BaseException] | None,
|
38
|
+
exc_val: BaseException | None,
|
39
|
+
exc_tb: TracebackType | None,
|
40
|
+
) -> None:
|
41
|
+
"""Release the lock."""
|
42
|
+
self._lock.release()
|
43
|
+
self._acquired = False
|
44
|
+
|
45
|
+
async def acquire(self) -> None:
|
46
|
+
"""Acquire the lock directly."""
|
47
|
+
await self._lock.acquire()
|
48
|
+
self._acquired = True
|
49
|
+
|
50
|
+
def release(self) -> None:
|
51
|
+
"""Release the lock directly."""
|
52
|
+
if not self._acquired:
|
53
|
+
raise RuntimeError(
|
54
|
+
"Attempted to release lock that was not acquired by this task"
|
55
|
+
)
|
56
|
+
self._lock.release()
|
57
|
+
self._acquired = False
|
58
|
+
|
59
|
+
|
60
|
+
class Semaphore:
|
61
|
+
"""A semaphore preventing excessive releases."""
|
62
|
+
|
63
|
+
def __init__(self, initial_value: int):
|
64
|
+
"""Initialize a new semaphore."""
|
65
|
+
if initial_value < 0:
|
66
|
+
raise ValueError("The initial value must be >= 0")
|
67
|
+
self._initial_value = initial_value
|
68
|
+
self._current_acquisitions = 0
|
69
|
+
self._semaphore = anyio.Semaphore(initial_value)
|
70
|
+
track_resource(self, f"Semaphore-{id(self)}", "Semaphore")
|
71
|
+
|
72
|
+
def __del__(self):
|
73
|
+
"""Clean up resource tracking when semaphore is destroyed."""
|
74
|
+
try:
|
75
|
+
untrack_resource(self)
|
76
|
+
except Exception:
|
77
|
+
pass
|
78
|
+
|
79
|
+
async def __aenter__(self) -> None:
|
80
|
+
"""Acquire the semaphore."""
|
81
|
+
await self.acquire()
|
82
|
+
|
83
|
+
async def __aexit__(
|
84
|
+
self,
|
85
|
+
exc_type: type[BaseException] | None,
|
86
|
+
exc_val: BaseException | None,
|
87
|
+
exc_tb: TracebackType | None,
|
88
|
+
) -> None:
|
89
|
+
"""Release the semaphore."""
|
90
|
+
self.release()
|
91
|
+
|
92
|
+
async def acquire(self) -> None:
|
93
|
+
"""Acquire the semaphore."""
|
94
|
+
await self._semaphore.acquire()
|
95
|
+
self._current_acquisitions += 1
|
96
|
+
|
97
|
+
def release(self) -> None:
|
98
|
+
"""Release the semaphore."""
|
99
|
+
if self._current_acquisitions <= 0:
|
100
|
+
raise RuntimeError(
|
101
|
+
"Cannot release semaphore: no outstanding acquisitions"
|
102
|
+
)
|
103
|
+
self._semaphore.release()
|
104
|
+
self._current_acquisitions -= 1
|
105
|
+
|
106
|
+
@property
|
107
|
+
def current_acquisitions(self) -> int:
|
108
|
+
"""Get the current number of outstanding acquisitions."""
|
109
|
+
return self._current_acquisitions
|
110
|
+
|
111
|
+
@property
|
112
|
+
def initial_value(self) -> int:
|
113
|
+
"""Get the initial semaphore value."""
|
114
|
+
return self._initial_value
|
115
|
+
|
116
|
+
|
117
|
+
class CapacityLimiter:
|
118
|
+
"""A context manager for limiting the number of concurrent operations."""
|
119
|
+
|
120
|
+
def __init__(self, total_tokens: int | float):
|
121
|
+
"""Initialize a new capacity limiter."""
|
122
|
+
if total_tokens == math.inf:
|
123
|
+
processed_tokens = math.inf
|
124
|
+
elif isinstance(total_tokens, (int, float)) and total_tokens >= 1:
|
125
|
+
processed_tokens = (
|
126
|
+
int(total_tokens) if total_tokens != math.inf else math.inf
|
127
|
+
)
|
128
|
+
else:
|
129
|
+
raise ValueError(
|
130
|
+
"The total number of tokens must be >= 1 (int or math.inf)"
|
131
|
+
)
|
132
|
+
|
133
|
+
self._limiter = anyio.CapacityLimiter(processed_tokens)
|
134
|
+
self._borrower_counter = 0
|
135
|
+
self._active_borrowers = {}
|
136
|
+
track_resource(self, f"CapacityLimiter-{id(self)}", "CapacityLimiter")
|
137
|
+
|
138
|
+
def __del__(self):
|
139
|
+
"""Clean up resource tracking when limiter is destroyed."""
|
140
|
+
try:
|
141
|
+
untrack_resource(self)
|
142
|
+
except Exception:
|
143
|
+
pass
|
144
|
+
|
145
|
+
async def __aenter__(self) -> None:
|
146
|
+
"""Acquire a token."""
|
147
|
+
await self.acquire()
|
148
|
+
|
149
|
+
async def __aexit__(
|
150
|
+
self,
|
151
|
+
exc_type: type[BaseException] | None,
|
152
|
+
exc_val: BaseException | None,
|
153
|
+
exc_tb: TracebackType | None,
|
154
|
+
) -> None:
|
155
|
+
"""Release the token."""
|
156
|
+
self.release()
|
157
|
+
|
158
|
+
async def acquire(self) -> None:
|
159
|
+
"""Acquire a token."""
|
160
|
+
# Create a unique borrower identity for each acquisition
|
161
|
+
self._borrower_counter += 1
|
162
|
+
borrower = f"borrower-{self._borrower_counter}"
|
163
|
+
await self._limiter.acquire_on_behalf_of(borrower)
|
164
|
+
self._active_borrowers[borrower] = True
|
165
|
+
|
166
|
+
def release(self) -> None:
|
167
|
+
"""Release a token."""
|
168
|
+
# Find and release the first active borrower
|
169
|
+
if not self._active_borrowers:
|
170
|
+
raise RuntimeError("No tokens to release")
|
171
|
+
|
172
|
+
borrower = next(iter(self._active_borrowers))
|
173
|
+
self._limiter.release_on_behalf_of(borrower)
|
174
|
+
del self._active_borrowers[borrower]
|
175
|
+
|
176
|
+
@property
|
177
|
+
def total_tokens(self) -> int | float:
|
178
|
+
"""The total number of tokens."""
|
179
|
+
return float(self._limiter.total_tokens)
|
180
|
+
|
181
|
+
@total_tokens.setter
|
182
|
+
def total_tokens(self, value: int | float) -> None:
|
183
|
+
"""Set the total number of tokens."""
|
184
|
+
if value == math.inf:
|
185
|
+
processed_value = math.inf
|
186
|
+
elif isinstance(value, (int, float)) and value >= 1:
|
187
|
+
processed_value = int(value) if value != math.inf else math.inf
|
188
|
+
else:
|
189
|
+
raise ValueError(
|
190
|
+
"The total number of tokens must be >= 1 (int or math.inf)"
|
191
|
+
)
|
192
|
+
|
193
|
+
current_borrowed = self._limiter.borrowed_tokens
|
194
|
+
if processed_value != math.inf and processed_value < current_borrowed:
|
195
|
+
raise ValueError(
|
196
|
+
f"Cannot set total_tokens to {processed_value}: {current_borrowed} tokens "
|
197
|
+
f"are currently borrowed. Wait for tokens to be released or "
|
198
|
+
f"set total_tokens to at least {current_borrowed}."
|
199
|
+
)
|
200
|
+
|
201
|
+
self._limiter.total_tokens = processed_value
|
202
|
+
|
203
|
+
@property
|
204
|
+
def borrowed_tokens(self) -> int:
|
205
|
+
"""The number of tokens currently borrowed."""
|
206
|
+
return self._limiter.borrowed_tokens
|
207
|
+
|
208
|
+
@property
|
209
|
+
def available_tokens(self) -> int | float:
|
210
|
+
"""The number of tokens currently available."""
|
211
|
+
return self._limiter.available_tokens
|
212
|
+
|
213
|
+
|
214
|
+
class Event:
|
215
|
+
"""An event object for task synchronization."""
|
216
|
+
|
217
|
+
def __init__(self):
|
218
|
+
"""Initialize a new event in the unset state."""
|
219
|
+
self._event = anyio.Event()
|
220
|
+
track_resource(self, f"Event-{id(self)}", "Event")
|
221
|
+
|
222
|
+
def __del__(self):
|
223
|
+
"""Clean up resource tracking when event is destroyed."""
|
224
|
+
try:
|
225
|
+
untrack_resource(self)
|
226
|
+
except Exception:
|
227
|
+
pass
|
228
|
+
|
229
|
+
def is_set(self) -> bool:
|
230
|
+
"""Check if the event is set."""
|
231
|
+
return self._event.is_set()
|
232
|
+
|
233
|
+
def set(self) -> None:
|
234
|
+
"""Set the event, allowing all waiting tasks to proceed."""
|
235
|
+
self._event.set()
|
236
|
+
|
237
|
+
async def wait(self) -> None:
|
238
|
+
"""Wait until the event is set."""
|
239
|
+
await self._event.wait()
|
240
|
+
|
241
|
+
|
242
|
+
class Condition:
|
243
|
+
"""A condition variable for task synchronization."""
|
244
|
+
|
245
|
+
def __init__(self, lock: Lock | None = None):
|
246
|
+
"""Initialize a new condition."""
|
247
|
+
self._lock = lock or Lock()
|
248
|
+
self._condition = anyio.Condition(self._lock._lock)
|
249
|
+
track_resource(self, f"Condition-{id(self)}", "Condition")
|
250
|
+
|
251
|
+
def __del__(self):
|
252
|
+
"""Clean up resource tracking when condition is destroyed."""
|
253
|
+
try:
|
254
|
+
untrack_resource(self)
|
255
|
+
except Exception:
|
256
|
+
pass
|
257
|
+
|
258
|
+
async def __aenter__(self) -> "Condition":
|
259
|
+
"""Acquire the underlying lock."""
|
260
|
+
await self._lock.__aenter__()
|
261
|
+
return self
|
262
|
+
|
263
|
+
async def __aexit__(
|
264
|
+
self,
|
265
|
+
exc_type: type[BaseException] | None,
|
266
|
+
exc_val: BaseException | None,
|
267
|
+
exc_tb: TracebackType | None,
|
268
|
+
) -> None:
|
269
|
+
"""Release the underlying lock."""
|
270
|
+
await self._lock.__aexit__(exc_type, exc_val, exc_tb)
|
271
|
+
|
272
|
+
async def wait(self) -> None:
|
273
|
+
"""Wait for a notification.
|
274
|
+
|
275
|
+
This releases the underlying lock, waits for a notification, and then
|
276
|
+
reacquires the lock.
|
277
|
+
"""
|
278
|
+
await self._condition.wait()
|
279
|
+
|
280
|
+
async def notify(self, n: int = 1) -> None:
|
281
|
+
"""Notify waiting tasks.
|
282
|
+
|
283
|
+
Args:
|
284
|
+
n: The number of tasks to notify
|
285
|
+
"""
|
286
|
+
await self._condition.notify(n)
|
287
|
+
|
288
|
+
async def notify_all(self) -> None:
|
289
|
+
"""Notify all waiting tasks."""
|
290
|
+
await self._condition.notify_all()
|