krons 0.2.3__tar.gz → 0.2.4__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.
- krons-0.2.4/CHANGELOG.md +38 -0
- {krons-0.2.3 → krons-0.2.4}/PKG-INFO +1 -1
- {krons-0.2.3 → krons-0.2.4}/pyproject.toml +1 -1
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/common.py +1 -1
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/prepare_msg.py +1 -2
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/node.py +27 -26
- krons-0.2.4/src/krons/core/specs/adapters/_utils.py +87 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/catalog/_content.py +6 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/types/db_types.py +103 -18
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/session.py +3 -1
- {krons-0.2.3 → krons-0.2.4}/tests/specs/test_catalog.py +10 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_db_types.py +117 -1
- {krons-0.2.3 → krons-0.2.4}/uv.lock +1 -1
- krons-0.2.3/src/krons/core/specs/adapters/_utils.py +0 -45
- {krons-0.2.3 → krons-0.2.4}/.github/workflows/release.yml +0 -0
- {krons-0.2.3 → krons-0.2.4}/.gitignore +0 -0
- {krons-0.2.3 → krons-0.2.4}/.python-version +0 -0
- {krons-0.2.3 → krons-0.2.4}/CLAUDE.md +0 -0
- {krons-0.2.3 → krons-0.2.4}/LICENSE +0 -0
- {krons-0.2.3 → krons-0.2.4}/README.md +0 -0
- {krons-0.2.3 → krons-0.2.4}/cookbooks/007_fan_out_in.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/cookbooks/test_conduct.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/code_review_panel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/codegen_pipeline.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/dynamic_response.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/event_sourcing.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/multi_agent_orchestration.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/pipeline_router.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/research_agent.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/tech_debate.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/examples/validation_loop.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/mcps/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/mcps/loader.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/mcps/wrapper.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/action.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/assistant.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/instruction.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/role.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/message/system.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/act.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/generate.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/llm_reparse.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/operate.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/parse.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/react.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/specs.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/structure.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/operations/utils.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/anthropic_messages.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/claude_code.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/gemini.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/match.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/providers/oai_chat.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/third_party/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/third_party/anthropic_models.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/third_party/claude_code.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/third_party/gemini_models.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/third_party/openai_models.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/agent/tool.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/broadcaster.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/element.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/event.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/eventbus.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/flow.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/graph.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/pile.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/processor.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/base/progression.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/adapters/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/adapters/dataclass_field.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/adapters/factory.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/adapters/pydantic_adapter.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/adapters/sql_ddl.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/catalog/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/catalog/_audit.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/catalog/_common.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/catalog/_enforcement.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/factory.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/operable.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/protocol.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/specs/spec.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/types/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/types/_sentinel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/types/base.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/core/types/identity.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/errors.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/protocols.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/py.typed +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/backend.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/endpoint.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/hook.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/imodel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/header_factory.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/rate_limited_executor.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/rate_limiter.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/resilience.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/resource/utilities/token_calculator.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/constraints.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/exchange.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/message.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/session/registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_function_arg_parser.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_hash.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_json_dump.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_lazy_init.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_pythonic_function_call.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_to_list.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_to_num.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/_utils.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_async_call.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_cancel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_errors.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_patterns.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_primitives.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_priority_queue.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_resource_tracker.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_run_async.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_task.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/concurrency/_utils.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/display.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/_extract_json.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/_fuzzy_json.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/_fuzzy_match.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/_string_similarity.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/fuzzy/_to_dict.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/schemas/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/schemas/_breakdown_pydantic_annotation.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/schemas/_formatter.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/schemas/_minimal_yaml.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/schemas/_typescript.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/sql/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/sql/_sql_validation.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/validators/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/utils/validators/_validate_image_url.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/engine.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/form.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/builder.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/context.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/flow.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/node.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/operations/registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/report.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/boolean.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/choice.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/mapping.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/model.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/number.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/common/string.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/rule.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/rules/validator.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/src/krons/work/worker.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/test_mcps_unit.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/test_message.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/test_providers_e2e.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/test_providers_unit.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/agent/test_tool_unit.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/conftest.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_broadcaster.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_element.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_error_paths.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_event.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_event_status_race.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_eventbus.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_flow.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_flow_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_graph.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_graph_event_loop.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_node.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_pile.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_pile_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_processor.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_processor_security.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_progression.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_progression_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_serialization_roundtrip.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/core/test_thread_safety_stress.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_builder.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_flow.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_node.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_op_flow.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_op_node.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/operations/test_registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/test_backend.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/test_endpoint.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/test_hook.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/test_imodel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/test_registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/utilities/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/utilities/test_header_factory.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/utilities/test_rate_limiter.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/utilities/test_resilience.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/resource/utilities/test_resilience_retry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_base.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_builtin.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_registry.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_rule_params_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_rules_comprehensive.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/rules/test_validator.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_exchange.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_message.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_message_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_session.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_session_edge_cases.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/session/test_session_logging.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/specs/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/specs/test_factory.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/test_protocols.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/conftest.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/spec_adapters/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/spec_adapters/test_adapters_py311.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/spec_adapters/test_protocol.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/spec_adapters/test_pydantic_field.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/spec_adapters/test_sql_ddl_specs.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_identity.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_model.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_operable.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_sentinel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_spec.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_types.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/types/test_types_py311.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_async_call.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_cancel.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_errors.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_patterns.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_primitives.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_priority_queue.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_run_async.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_task.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/concurrency/test_utils.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_extract_json.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_fuzzy_json.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_fuzzy_match.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_hash.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_json_dump.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_lazy_init.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_sql_validation.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_string_similarity.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_to_dict.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_to_list.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_to_num.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/utils/test_utils.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/work/__init__.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/work/test_engine.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/work/test_form.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/work/test_report.py +0 -0
- {krons-0.2.3 → krons-0.2.4}/tests/work/test_worker.py +0 -0
krons-0.2.4/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.4] - 2026-02-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `parse_forward_ref` canonical parser for ForwardRef annotations
|
|
13
|
+
- Support for `FK["Model"]` and `FK['Model']` string forward references
|
|
14
|
+
- Robust nullability detection: `Optional[X]`, `Union[X, None]`, `X | None`
|
|
15
|
+
- `meta_key` parameter in `ContentSpecs.get_specs()` for DB alias customization
|
|
16
|
+
- Duck typing (`_is_spec_like`) to avoid circular imports
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- DDL generation now uses config-driven audit columns (dict pattern)
|
|
21
|
+
- `_utils.py` uses shared `parse_forward_ref` instead of duplicate implementation
|
|
22
|
+
- Removed `include_audit_columns` parameter from `generate_ddl()` (now config-driven)
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- ForwardRef parsing with `from __future__ import annotations` (PEP 563)
|
|
27
|
+
- Nested Union types in nullability detection (e.g., `Union[FK[User], None]`)
|
|
28
|
+
|
|
29
|
+
## [0.2.3] - 2026-02-04
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Multi-agent example patterns (code_review_panel, tech_debate)
|
|
34
|
+
- 10 comprehensive examples demonstrating framework capabilities
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Pile UUID access bug for Element retrieval
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
4
|
+
from typing import Any, cast
|
|
6
5
|
|
|
7
6
|
from pydantic import JsonValue
|
|
8
7
|
|
|
@@ -745,9 +745,12 @@ def create_node(
|
|
|
745
745
|
has_embedding = True
|
|
746
746
|
|
|
747
747
|
# 1. Build all possible specs
|
|
748
|
+
# Extract meta_key from config_kwargs if provided, else use default
|
|
749
|
+
meta_key = config_kwargs.get("meta_key", "node_metadata")
|
|
748
750
|
all_specs = ContentSpecs.get_specs(
|
|
749
751
|
content_type=content if content else Unset,
|
|
750
752
|
dim=resolved_embedding_dim,
|
|
753
|
+
meta_key=meta_key,
|
|
751
754
|
) + AuditSpecs.get_specs(use_uuid=True)
|
|
752
755
|
|
|
753
756
|
# 2. Track which fields to include
|
|
@@ -837,19 +840,17 @@ def _extract_base_type(annotation: Any) -> Any:
|
|
|
837
840
|
return annotation
|
|
838
841
|
|
|
839
842
|
|
|
840
|
-
def generate_ddl(
|
|
841
|
-
node_cls: type[Node],
|
|
842
|
-
*,
|
|
843
|
-
include_audit_columns: bool = True,
|
|
844
|
-
) -> str:
|
|
843
|
+
def generate_ddl(node_cls: type[Node]) -> str:
|
|
845
844
|
"""Generate CREATE TABLE DDL from Node subclass.
|
|
846
845
|
|
|
847
846
|
Flattens content fields (if configured), adds audit columns, and
|
|
848
847
|
generates PostgreSQL DDL with pgvector support for embeddings.
|
|
849
848
|
|
|
849
|
+
Audit column inclusion is driven by NodeConfig settings (track_updated_at,
|
|
850
|
+
soft_delete, versioning, etc.).
|
|
851
|
+
|
|
850
852
|
Args:
|
|
851
853
|
node_cls: Persistable Node subclass (must have table_name)
|
|
852
|
-
include_audit_columns: Include audit columns from NodeConfig
|
|
853
854
|
|
|
854
855
|
Returns:
|
|
855
856
|
CREATE TABLE IF NOT EXISTS statement
|
|
@@ -875,7 +876,8 @@ def generate_ddl(
|
|
|
875
876
|
)
|
|
876
877
|
|
|
877
878
|
all_specs = ContentSpecs.get_specs(
|
|
878
|
-
dim=config.embedding_dim if config.embedding_enabled else Unset
|
|
879
|
+
dim=config.embedding_dim if config.embedding_enabled else Unset,
|
|
880
|
+
meta_key=config.meta_key,
|
|
879
881
|
) + AuditSpecs.get_specs(use_uuid=True)
|
|
880
882
|
|
|
881
883
|
# Flatten content: extract fields from BaseModel instead of generic JSONB
|
|
@@ -900,25 +902,24 @@ def generate_ddl(
|
|
|
900
902
|
):
|
|
901
903
|
include.add("content")
|
|
902
904
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
include.add("integrity_hash")
|
|
905
|
+
if not config.is_sentinel_field("meta_key") and config.meta_key != "metadata":
|
|
906
|
+
include.add(config.meta_key)
|
|
907
|
+
|
|
908
|
+
audit_cols = {
|
|
909
|
+
"updated_at": config.track_updated_at,
|
|
910
|
+
"updated_by": config.track_updated_by,
|
|
911
|
+
"is_active": config.track_is_active,
|
|
912
|
+
"is_deleted": config.soft_delete,
|
|
913
|
+
"deleted_at": config.soft_delete,
|
|
914
|
+
"deleted_by": config.soft_delete and config.track_deleted_by,
|
|
915
|
+
"version": config.versioning,
|
|
916
|
+
"content_hash": config.content_hashing,
|
|
917
|
+
"integrity_hash": config.integrity_hashing,
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
for col, enabled in audit_cols.items():
|
|
921
|
+
if enabled:
|
|
922
|
+
include.add(col)
|
|
922
923
|
|
|
923
924
|
# If flattened, include the extracted content field names
|
|
924
925
|
if config.flatten_content and content_type is not None:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import types
|
|
4
|
+
from functools import reduce
|
|
5
|
+
from typing import Annotated, Any, ForwardRef, Union, get_args, get_origin
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _resolve_forward_ref(fwd: ForwardRef) -> dict[str, Any]:
|
|
10
|
+
"""Handle ForwardRef annotations (from 'from __future__ import annotations').
|
|
11
|
+
|
|
12
|
+
Parses the string representation to extract type info for DDL generation.
|
|
13
|
+
FK[Model] -> Annotated[UUID, FKMeta(model_name)], Vector[dim] -> list[float], etc.
|
|
14
|
+
|
|
15
|
+
Uses parse_forward_ref from db_types as canonical parser.
|
|
16
|
+
"""
|
|
17
|
+
from krons.core.types.db_types import parse_forward_ref
|
|
18
|
+
|
|
19
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
20
|
+
|
|
21
|
+
# FK[Model] -> Annotated[UUID, FKMeta]
|
|
22
|
+
if fk is not None:
|
|
23
|
+
base_type = Annotated[UUID, fk]
|
|
24
|
+
return {"base_type": base_type, "nullable": nullable, "listable": False}
|
|
25
|
+
|
|
26
|
+
# Vector[dim] -> Annotated[list[float], VectorMeta]
|
|
27
|
+
if vec is not None:
|
|
28
|
+
base_type = Annotated[list[float], vec]
|
|
29
|
+
return {"base_type": base_type, "nullable": nullable, "listable": False}
|
|
30
|
+
|
|
31
|
+
# Default: treat as generic type (will map to TEXT in SQL)
|
|
32
|
+
return {"base_type": str, "nullable": nullable, "listable": False}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def resolve_annotation_to_base_types(annotation: Any) -> dict[str, Any]:
|
|
36
|
+
"""Resolve an annotation to its base types, detecting nullable and listable.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
annotation: Type annotation to resolve (may include Optional, list, etc.)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dict with keys:
|
|
43
|
+
- base_type: The innermost type
|
|
44
|
+
- nullable: Whether None is allowed
|
|
45
|
+
- listable: Whether it's a list type
|
|
46
|
+
"""
|
|
47
|
+
# Handle ForwardRef (from 'from __future__ import annotations')
|
|
48
|
+
if isinstance(annotation, ForwardRef):
|
|
49
|
+
return _resolve_forward_ref(annotation)
|
|
50
|
+
|
|
51
|
+
def resolve_nullable_inner_type(_anno: Any) -> tuple[bool, Any]:
|
|
52
|
+
origin = get_origin(_anno)
|
|
53
|
+
|
|
54
|
+
if origin is type(None):
|
|
55
|
+
return True, type(None)
|
|
56
|
+
|
|
57
|
+
if origin in (type(int | str), types.UnionType) or origin is Union:
|
|
58
|
+
args = get_args(_anno)
|
|
59
|
+
non_none_args = [a for a in args if a is not type(None)]
|
|
60
|
+
if len(args) != len(non_none_args):
|
|
61
|
+
if len(non_none_args) == 1:
|
|
62
|
+
return True, non_none_args[0]
|
|
63
|
+
if non_none_args:
|
|
64
|
+
return True, reduce(lambda a, b: a | b, non_none_args)
|
|
65
|
+
return False, _anno
|
|
66
|
+
|
|
67
|
+
return False, _anno
|
|
68
|
+
|
|
69
|
+
def resolve_listable_element_type(_anno: Any) -> Any:
|
|
70
|
+
origin = get_origin(_anno)
|
|
71
|
+
|
|
72
|
+
if origin is list:
|
|
73
|
+
args = get_args(_anno)
|
|
74
|
+
if args:
|
|
75
|
+
return True, args[0]
|
|
76
|
+
return True, Any
|
|
77
|
+
|
|
78
|
+
return False, _anno
|
|
79
|
+
|
|
80
|
+
_null, _inner = resolve_nullable_inner_type(annotation)
|
|
81
|
+
_list, _elem = resolve_listable_element_type(_inner)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"base_type": _elem,
|
|
85
|
+
"nullable": _null,
|
|
86
|
+
"listable": _list,
|
|
87
|
+
}
|
|
@@ -33,12 +33,14 @@ class ContentSpecs(BaseModel):
|
|
|
33
33
|
*,
|
|
34
34
|
content_type: type | UnsetType = Unset,
|
|
35
35
|
dim: int | UnsetType = Unset,
|
|
36
|
+
meta_key: str | UnsetType = Unset,
|
|
36
37
|
) -> list[Spec]:
|
|
37
38
|
"""Get list of content Specs.
|
|
38
39
|
|
|
39
40
|
Args:
|
|
40
41
|
content_type: Type for content/metadata fields (default: dict).
|
|
41
42
|
dim: Embedding dimension. Unset = list[float], int = Vector[dim].
|
|
43
|
+
meta_key: DB alias for metadata field (e.g., "node_metadata").
|
|
42
44
|
"""
|
|
43
45
|
operable = Operable.from_structure(cls)
|
|
44
46
|
specs = {spec.name: spec for spec in operable.get_specs()}
|
|
@@ -48,6 +50,10 @@ class ContentSpecs(BaseModel):
|
|
|
48
50
|
specs["content"] = Spec(content_type, name="content").as_nullable()
|
|
49
51
|
specs["metadata"] = Spec(content_type, name="metadata").as_nullable()
|
|
50
52
|
|
|
53
|
+
# Add meta_key alias if specified (DB mode uses this to avoid SQL reserved word)
|
|
54
|
+
if meta_key is not Unset and isinstance(meta_key, str):
|
|
55
|
+
specs[meta_key] = Spec(dict[str, Any], name=meta_key).as_nullable()
|
|
56
|
+
|
|
51
57
|
# Override embedding with vector dimension if specified
|
|
52
58
|
if dim is not Unset and isinstance(dim, int):
|
|
53
59
|
specs["embedding"] = Spec(
|
|
@@ -14,8 +14,9 @@ Extraction:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import re
|
|
17
18
|
import types
|
|
18
|
-
from typing import Annotated, Any, Literal, Union, get_args, get_origin
|
|
19
|
+
from typing import Annotated, Any, ForwardRef, Literal, Union, get_args, get_origin
|
|
19
20
|
from uuid import UUID
|
|
20
21
|
|
|
21
22
|
from krons.core.types._sentinel import Unset, UnsetType, not_sentinel
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"Vector",
|
|
33
34
|
"VectorMeta",
|
|
34
35
|
"extract_kron_db_meta",
|
|
36
|
+
"parse_forward_ref",
|
|
35
37
|
]
|
|
36
38
|
|
|
37
39
|
|
|
@@ -200,6 +202,88 @@ def _find_in_field_info(field_info: Any, meta_type: type) -> Any | None:
|
|
|
200
202
|
return None
|
|
201
203
|
|
|
202
204
|
|
|
205
|
+
def _is_spec_like(obj: Any) -> bool:
|
|
206
|
+
"""Check if object looks like a Spec (duck typing to avoid hard import)."""
|
|
207
|
+
return (
|
|
208
|
+
hasattr(obj, "get")
|
|
209
|
+
and hasattr(obj, "__class__")
|
|
210
|
+
and "Spec" in type(obj).__name__
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _detect_nullable(arg: str) -> bool:
|
|
215
|
+
"""Detect nullability from annotation string.
|
|
216
|
+
|
|
217
|
+
Handles:
|
|
218
|
+
- X | None, None | X (PEP 604 union)
|
|
219
|
+
- Optional[X] (typing.Optional)
|
|
220
|
+
- Union[X, None], Union[None, X] (typing.Union with nested types)
|
|
221
|
+
"""
|
|
222
|
+
# PEP 604 style: X | None or None | X
|
|
223
|
+
if re.search(r"\|\s*None\b", arg) or re.search(r"\bNone\s*\|", arg):
|
|
224
|
+
return True
|
|
225
|
+
# Optional[X]
|
|
226
|
+
if re.search(r"\bOptional\s*\[", arg):
|
|
227
|
+
return True
|
|
228
|
+
# Union[..., None, ...] - if Union is present and None is anywhere in string
|
|
229
|
+
# This handles nested types like Union[FK[User], None]
|
|
230
|
+
if re.search(r"\bUnion\s*\[", arg) and re.search(r"\bNone\b", arg):
|
|
231
|
+
return True
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def parse_forward_ref(
|
|
236
|
+
fwd: ForwardRef,
|
|
237
|
+
) -> tuple[FKMeta | None, VectorMeta | None, bool]:
|
|
238
|
+
"""Parse FK/Vector metadata and nullability from a ForwardRef string.
|
|
239
|
+
|
|
240
|
+
Canonical parser for ForwardRef annotations from 'from __future__ import annotations'.
|
|
241
|
+
|
|
242
|
+
Handles:
|
|
243
|
+
- FK[Model], FK["Model"], FK['Model'] (bare and string refs)
|
|
244
|
+
- Vector[1536] (dimension as int literal)
|
|
245
|
+
- Nullability: X | None, Optional[X], Union[X, None]
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
fwd: ForwardRef to parse
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Tuple of (fk_meta, vector_meta, is_nullable)
|
|
252
|
+
- fk_meta: FKMeta if FK[...] found, else None
|
|
253
|
+
- vector_meta: VectorMeta if Vector[dim] found, else None
|
|
254
|
+
- is_nullable: True if nullable pattern detected
|
|
255
|
+
"""
|
|
256
|
+
arg = fwd.__forward_arg__
|
|
257
|
+
fk: FKMeta | None = None
|
|
258
|
+
vec: VectorMeta | None = None
|
|
259
|
+
nullable = _detect_nullable(arg)
|
|
260
|
+
|
|
261
|
+
# Match FK[ModelName] or FK["ModelName"] or FK['ModelName']
|
|
262
|
+
fk_match = re.search(r"FK\[(['\"]?)(\w+)\1\]", arg)
|
|
263
|
+
if fk_match:
|
|
264
|
+
model_name = fk_match.group(2)
|
|
265
|
+
fk = FKMeta(model_name)
|
|
266
|
+
|
|
267
|
+
# Match Vector[dim]
|
|
268
|
+
vec_match = re.search(r"Vector\[(\d+)\]", arg)
|
|
269
|
+
if vec_match:
|
|
270
|
+
dim = int(vec_match.group(1))
|
|
271
|
+
vec = VectorMeta(dim)
|
|
272
|
+
|
|
273
|
+
return fk, vec, nullable
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _extract_from_forward_ref(
|
|
277
|
+
fwd: ForwardRef,
|
|
278
|
+
) -> tuple[FKMeta | UnsetType, VectorMeta | UnsetType]:
|
|
279
|
+
"""Extract FK/Vector metadata from a ForwardRef (returns Unset for missing).
|
|
280
|
+
|
|
281
|
+
Wrapper around parse_forward_ref for extract_kron_db_meta compatibility.
|
|
282
|
+
"""
|
|
283
|
+
fk, vec, _ = parse_forward_ref(fwd)
|
|
284
|
+
return (fk if fk is not None else Unset, vec if vec is not None else Unset)
|
|
285
|
+
|
|
286
|
+
|
|
203
287
|
def extract_kron_db_meta(
|
|
204
288
|
from_: Any,
|
|
205
289
|
metas: Literal["FK", "Vector", "BOTH"] = "BOTH",
|
|
@@ -209,7 +293,7 @@ def extract_kron_db_meta(
|
|
|
209
293
|
Unified extraction dispatching on source type:
|
|
210
294
|
- FieldInfo: searches Pydantic metadata and annotation
|
|
211
295
|
- type/annotation: searches Annotated/Union structure
|
|
212
|
-
- Spec: reads spec metadata directly
|
|
296
|
+
- Spec: reads spec metadata directly (if available)
|
|
213
297
|
|
|
214
298
|
Args:
|
|
215
299
|
from_: FieldInfo, type annotation, or Spec instance
|
|
@@ -228,6 +312,10 @@ def extract_kron_db_meta(
|
|
|
228
312
|
if metas in ("Vector", "BOTH"):
|
|
229
313
|
vec = _find_in_field_info(from_, VectorMeta) or Unset
|
|
230
314
|
|
|
315
|
+
elif isinstance(from_, ForwardRef):
|
|
316
|
+
# Handle ForwardRef from 'from __future__ import annotations'
|
|
317
|
+
fk, vec = _extract_from_forward_ref(from_)
|
|
318
|
+
|
|
231
319
|
elif get_origin(from_) is not None or isinstance(from_, type):
|
|
232
320
|
# Raw type annotation
|
|
233
321
|
if metas in ("FK", "BOTH"):
|
|
@@ -235,23 +323,20 @@ def extract_kron_db_meta(
|
|
|
235
323
|
if metas in ("Vector", "BOTH"):
|
|
236
324
|
vec = _find_in_annotation(from_, VectorMeta) or Unset
|
|
237
325
|
|
|
326
|
+
elif _is_spec_like(from_):
|
|
327
|
+
# Spec-like object (duck typed to avoid circular imports)
|
|
328
|
+
if metas in ("FK", "BOTH"):
|
|
329
|
+
fk_val = from_.get("as_fk", Unset)
|
|
330
|
+
if not_sentinel(fk_val, {"none"}) and isinstance(fk_val, FKMeta):
|
|
331
|
+
fk = fk_val
|
|
332
|
+
if metas in ("Vector", "BOTH"):
|
|
333
|
+
vec_val = from_.get("embedding", Unset)
|
|
334
|
+
if not_sentinel(vec_val, {"none"}) and isinstance(vec_val, VectorMeta):
|
|
335
|
+
vec = vec_val
|
|
238
336
|
else:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if isinstance(from_, Spec):
|
|
243
|
-
if metas in ("FK", "BOTH"):
|
|
244
|
-
fk_val = from_.get("as_fk", Unset)
|
|
245
|
-
if not_sentinel(fk_val, {"none"}) and isinstance(fk_val, FKMeta):
|
|
246
|
-
fk = fk_val
|
|
247
|
-
if metas in ("Vector", "BOTH"):
|
|
248
|
-
vec_val = from_.get("embedding", Unset)
|
|
249
|
-
if not_sentinel(vec_val, {"none"}) and isinstance(vec_val, VectorMeta):
|
|
250
|
-
vec = vec_val
|
|
251
|
-
else:
|
|
252
|
-
raise TypeError(
|
|
253
|
-
f"from_ must be FieldInfo, type annotation, or Spec, got {type(from_).__name__}"
|
|
254
|
-
)
|
|
337
|
+
raise TypeError(
|
|
338
|
+
f"from_ must be FieldInfo, type annotation, or Spec, got {type(from_).__name__}"
|
|
339
|
+
)
|
|
255
340
|
|
|
256
341
|
if metas == "FK":
|
|
257
342
|
return fk
|
|
@@ -84,7 +84,9 @@ class Session(Element):
|
|
|
84
84
|
default_factory=lambda: Flow(item_type=Message)
|
|
85
85
|
)
|
|
86
86
|
resources: ResourceRegistry = Field(default_factory=ResourceRegistry, exclude=True)
|
|
87
|
-
operations: OperationRegistry = Field(
|
|
87
|
+
operations: OperationRegistry = Field(
|
|
88
|
+
default_factory=OperationRegistry, exclude=True
|
|
89
|
+
)
|
|
88
90
|
config: SessionConfig = Field(default_factory=SessionConfig)
|
|
89
91
|
default_branch_id: UUID | None = None
|
|
90
92
|
|
|
@@ -108,6 +108,16 @@ class TestContentSpecs:
|
|
|
108
108
|
assert "metadata" in names
|
|
109
109
|
assert "embedding" in names
|
|
110
110
|
|
|
111
|
+
def test_get_specs_with_meta_key(self):
|
|
112
|
+
"""ContentSpecs with meta_key should include the alias."""
|
|
113
|
+
specs = ContentSpecs.get_specs(meta_key="node_metadata")
|
|
114
|
+
|
|
115
|
+
assert isinstance(specs, list)
|
|
116
|
+
assert len(specs) == 6
|
|
117
|
+
|
|
118
|
+
names = [s.name for s in specs]
|
|
119
|
+
assert "node_metadata" in names
|
|
120
|
+
|
|
111
121
|
def test_get_specs_id_is_uuid(self):
|
|
112
122
|
"""ID spec should be UUID type."""
|
|
113
123
|
specs = ContentSpecs.get_specs()
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
"""Tests for database type annotations (FK, Vector)."""
|
|
5
5
|
|
|
6
|
-
from typing import Annotated, get_args, get_origin
|
|
6
|
+
from typing import Annotated, ForwardRef, get_args, get_origin
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
from pydantic import BaseModel, Field
|
|
11
11
|
|
|
12
12
|
from krons.core.types import FK, FKMeta, Unset, Vector, VectorMeta, extract_kron_db_meta
|
|
13
|
+
from krons.core.types.db_types import parse_forward_ref
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class MockTenant:
|
|
@@ -236,3 +237,118 @@ class TestVectorMetaExtraction:
|
|
|
236
237
|
meta = extract_kron_db_meta(field_info, metas="Vector")
|
|
237
238
|
|
|
238
239
|
assert meta is Unset
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class TestParseForwardRef:
|
|
243
|
+
"""Tests for parse_forward_ref canonical parser."""
|
|
244
|
+
|
|
245
|
+
# --- FK patterns ---
|
|
246
|
+
|
|
247
|
+
def test_fk_bare_model(self):
|
|
248
|
+
"""Should parse FK[Model]."""
|
|
249
|
+
fwd = ForwardRef("FK[User]")
|
|
250
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
251
|
+
|
|
252
|
+
assert fk is not None
|
|
253
|
+
assert fk.model == "User"
|
|
254
|
+
assert vec is None
|
|
255
|
+
assert not nullable
|
|
256
|
+
|
|
257
|
+
def test_fk_string_double_quotes(self):
|
|
258
|
+
"""Should parse FK["Model"] with double quotes."""
|
|
259
|
+
fwd = ForwardRef('FK["User"]')
|
|
260
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
261
|
+
|
|
262
|
+
assert fk is not None
|
|
263
|
+
assert fk.model == "User"
|
|
264
|
+
|
|
265
|
+
def test_fk_string_single_quotes(self):
|
|
266
|
+
"""Should parse FK['Model'] with single quotes."""
|
|
267
|
+
fwd = ForwardRef("FK['User']")
|
|
268
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
269
|
+
|
|
270
|
+
assert fk is not None
|
|
271
|
+
assert fk.model == "User"
|
|
272
|
+
|
|
273
|
+
# --- Vector patterns ---
|
|
274
|
+
|
|
275
|
+
def test_vector_dimension(self):
|
|
276
|
+
"""Should parse Vector[1536]."""
|
|
277
|
+
fwd = ForwardRef("Vector[1536]")
|
|
278
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
279
|
+
|
|
280
|
+
assert fk is None
|
|
281
|
+
assert vec is not None
|
|
282
|
+
assert vec.dim == 1536
|
|
283
|
+
assert not nullable
|
|
284
|
+
|
|
285
|
+
# --- Nullability: PEP 604 style ---
|
|
286
|
+
|
|
287
|
+
def test_nullable_pipe_none_suffix(self):
|
|
288
|
+
"""Should detect X | None."""
|
|
289
|
+
fwd = ForwardRef("FK[User] | None")
|
|
290
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
291
|
+
|
|
292
|
+
assert fk is not None
|
|
293
|
+
assert nullable
|
|
294
|
+
|
|
295
|
+
def test_nullable_none_pipe_prefix(self):
|
|
296
|
+
"""Should detect None | X."""
|
|
297
|
+
fwd = ForwardRef("None | FK[User]")
|
|
298
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
299
|
+
|
|
300
|
+
assert fk is not None
|
|
301
|
+
assert nullable
|
|
302
|
+
|
|
303
|
+
# --- Nullability: Optional style ---
|
|
304
|
+
|
|
305
|
+
def test_nullable_optional(self):
|
|
306
|
+
"""Should detect Optional[X]."""
|
|
307
|
+
fwd = ForwardRef("Optional[FK[User]]")
|
|
308
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
309
|
+
|
|
310
|
+
assert fk is not None
|
|
311
|
+
assert nullable
|
|
312
|
+
|
|
313
|
+
# --- Nullability: Union style ---
|
|
314
|
+
|
|
315
|
+
def test_nullable_union_none_last(self):
|
|
316
|
+
"""Should detect Union[X, None]."""
|
|
317
|
+
fwd = ForwardRef("Union[FK[User], None]")
|
|
318
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
319
|
+
|
|
320
|
+
assert fk is not None
|
|
321
|
+
assert nullable
|
|
322
|
+
|
|
323
|
+
def test_nullable_union_none_first(self):
|
|
324
|
+
"""Should detect Union[None, X]."""
|
|
325
|
+
fwd = ForwardRef("Union[None, FK[User]]")
|
|
326
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
327
|
+
|
|
328
|
+
assert fk is not None
|
|
329
|
+
assert nullable
|
|
330
|
+
|
|
331
|
+
# --- Non-nullable cases ---
|
|
332
|
+
|
|
333
|
+
def test_not_nullable_plain(self):
|
|
334
|
+
"""Plain type should not be nullable."""
|
|
335
|
+
fwd = ForwardRef("FK[User]")
|
|
336
|
+
_, _, nullable = parse_forward_ref(fwd)
|
|
337
|
+
assert not nullable
|
|
338
|
+
|
|
339
|
+
def test_not_nullable_union_without_none(self):
|
|
340
|
+
"""Union without None should not be nullable."""
|
|
341
|
+
fwd = ForwardRef("Union[FK[User], FK[Tenant]]")
|
|
342
|
+
_, _, nullable = parse_forward_ref(fwd)
|
|
343
|
+
assert not nullable
|
|
344
|
+
|
|
345
|
+
# --- Unknown patterns ---
|
|
346
|
+
|
|
347
|
+
def test_unknown_type_returns_none(self):
|
|
348
|
+
"""Unknown type should return None for FK and Vector."""
|
|
349
|
+
fwd = ForwardRef("SomeRandomType")
|
|
350
|
+
fk, vec, nullable = parse_forward_ref(fwd)
|
|
351
|
+
|
|
352
|
+
assert fk is None
|
|
353
|
+
assert vec is None
|
|
354
|
+
assert not nullable
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import types
|
|
4
|
-
from functools import reduce
|
|
5
|
-
from typing import Any, Union, get_args, get_origin
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def resolve_annotation_to_base_types(annotation: Any) -> dict[str, Any]:
|
|
9
|
-
def resolve_nullable_inner_type(_anno: Any) -> tuple[bool, Any]:
|
|
10
|
-
origin = get_origin(_anno)
|
|
11
|
-
|
|
12
|
-
if origin is type(None):
|
|
13
|
-
return True, type(None)
|
|
14
|
-
|
|
15
|
-
if origin in (type(int | str), types.UnionType) or origin is Union:
|
|
16
|
-
args = get_args(_anno)
|
|
17
|
-
non_none_args = [a for a in args if a is not type(None)]
|
|
18
|
-
if len(args) != len(non_none_args):
|
|
19
|
-
if len(non_none_args) == 1:
|
|
20
|
-
return True, non_none_args[0]
|
|
21
|
-
if non_none_args:
|
|
22
|
-
return True, reduce(lambda a, b: a | b, non_none_args)
|
|
23
|
-
return False, _anno
|
|
24
|
-
|
|
25
|
-
return False, _anno
|
|
26
|
-
|
|
27
|
-
def resolve_listable_element_type(_anno: Any) -> Any:
|
|
28
|
-
origin = get_origin(_anno)
|
|
29
|
-
|
|
30
|
-
if origin is list:
|
|
31
|
-
args = get_args(_anno)
|
|
32
|
-
if args:
|
|
33
|
-
return True, args[0]
|
|
34
|
-
return True, Any
|
|
35
|
-
|
|
36
|
-
return False, _anno
|
|
37
|
-
|
|
38
|
-
_null, _inner = resolve_nullable_inner_type(annotation)
|
|
39
|
-
_list, _elem = resolve_listable_element_type(_inner)
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
"base_type": _elem,
|
|
43
|
-
"nullable": _null,
|
|
44
|
-
"listable": _list,
|
|
45
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|