flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"""JSON encoder utilities for Flock objects."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class FlockJSONEncoder(json.JSONEncoder):
|
|
9
|
-
"""Custom JSON encoder for handling Pydantic models and other non-serializable objects."""
|
|
10
|
-
|
|
11
|
-
def default(self, obj: Any) -> Any:
|
|
12
|
-
from pydantic import BaseModel
|
|
13
|
-
|
|
14
|
-
# Handle Pydantic models
|
|
15
|
-
if isinstance(obj, BaseModel):
|
|
16
|
-
return obj.model_dump()
|
|
17
|
-
|
|
18
|
-
# Handle datetime objects
|
|
19
|
-
if isinstance(obj, datetime):
|
|
20
|
-
return obj.isoformat()
|
|
21
|
-
|
|
22
|
-
# Handle sets, convert to list
|
|
23
|
-
if isinstance(obj, set):
|
|
24
|
-
return list(obj)
|
|
25
|
-
|
|
26
|
-
# Handle objects with a to_dict method
|
|
27
|
-
if hasattr(obj, "to_dict") and callable(getattr(obj, "to_dict")):
|
|
28
|
-
return obj.to_dict()
|
|
29
|
-
|
|
30
|
-
# Handle objects with a __dict__ attribute
|
|
31
|
-
if hasattr(obj, "__dict__"):
|
|
32
|
-
return {
|
|
33
|
-
k: v for k, v in obj.__dict__.items() if not k.startswith("_")
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
# Let the parent class handle it or raise TypeError
|
|
37
|
-
try:
|
|
38
|
-
return super().default(obj)
|
|
39
|
-
except TypeError:
|
|
40
|
-
# If all else fails, convert to string
|
|
41
|
-
return str(obj)
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import cloudpickle
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class SecureSerializer:
|
|
5
|
-
"""Security-focused serialization system with capability controls for Flock objects."""
|
|
6
|
-
|
|
7
|
-
# Define capability levels for different modules
|
|
8
|
-
MODULE_CAPABILITIES = {
|
|
9
|
-
# Core Python - unrestricted
|
|
10
|
-
"builtins": "unrestricted",
|
|
11
|
-
"datetime": "unrestricted",
|
|
12
|
-
"re": "unrestricted",
|
|
13
|
-
"math": "unrestricted",
|
|
14
|
-
"json": "unrestricted",
|
|
15
|
-
# Framework modules - unrestricted
|
|
16
|
-
"flock": "unrestricted",
|
|
17
|
-
# System modules - restricted but allowed
|
|
18
|
-
"os": "restricted",
|
|
19
|
-
"io": "restricted",
|
|
20
|
-
"sys": "restricted",
|
|
21
|
-
"subprocess": "high_risk",
|
|
22
|
-
# Network modules - high risk
|
|
23
|
-
"socket": "high_risk",
|
|
24
|
-
"requests": "high_risk",
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
# Functions that should never be serialized
|
|
28
|
-
BLOCKED_FUNCTIONS = {
|
|
29
|
-
"os.system",
|
|
30
|
-
"os.popen",
|
|
31
|
-
"os.spawn",
|
|
32
|
-
"os.exec",
|
|
33
|
-
"subprocess.call",
|
|
34
|
-
"subprocess.run",
|
|
35
|
-
"subprocess.Popen",
|
|
36
|
-
"eval",
|
|
37
|
-
"exec",
|
|
38
|
-
"__import__",
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@staticmethod
|
|
42
|
-
def _get_module_capability(module_name):
|
|
43
|
-
"""Get the capability level for a module."""
|
|
44
|
-
for prefix, level in SecureSerializer.MODULE_CAPABILITIES.items():
|
|
45
|
-
if module_name == prefix or module_name.startswith(f"{prefix}."):
|
|
46
|
-
return level
|
|
47
|
-
return "unknown" # Default to unknown for unlisted modules
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def _is_safe_callable(obj):
|
|
51
|
-
"""Check if a callable is safe to serialize."""
|
|
52
|
-
if not callable(obj) or isinstance(obj, type):
|
|
53
|
-
return True, "Not a callable function"
|
|
54
|
-
|
|
55
|
-
module = obj.__module__
|
|
56
|
-
func_name = (
|
|
57
|
-
f"{module}.{obj.__name__}"
|
|
58
|
-
if hasattr(obj, "__name__")
|
|
59
|
-
else "unknown"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# Check against blocked functions
|
|
63
|
-
if func_name in SecureSerializer.BLOCKED_FUNCTIONS:
|
|
64
|
-
return False, f"Function {func_name} is explicitly blocked"
|
|
65
|
-
|
|
66
|
-
# Check module capability level
|
|
67
|
-
capability = SecureSerializer._get_module_capability(module)
|
|
68
|
-
if capability == "unknown":
|
|
69
|
-
return False, f"Module {module} has unknown security capability"
|
|
70
|
-
|
|
71
|
-
return True, capability
|
|
72
|
-
|
|
73
|
-
@staticmethod
|
|
74
|
-
def serialize(obj, allow_restricted=True, allow_high_risk=False):
|
|
75
|
-
"""Serialize an object with capability checks."""
|
|
76
|
-
if callable(obj) and not isinstance(obj, type):
|
|
77
|
-
is_safe, capability = SecureSerializer._is_safe_callable(obj)
|
|
78
|
-
|
|
79
|
-
if not is_safe:
|
|
80
|
-
raise ValueError(
|
|
81
|
-
f"Cannot serialize unsafe callable: {capability}"
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
if capability == "high_risk" and not allow_high_risk:
|
|
85
|
-
raise ValueError(
|
|
86
|
-
f"High risk callable {obj.__module__}.{obj.__name__} requires explicit permission"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if capability == "restricted" and not allow_restricted:
|
|
90
|
-
raise ValueError(
|
|
91
|
-
f"Restricted callable {obj.__module__}.{obj.__name__} requires explicit permission"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Store metadata about the callable for verification during deserialization
|
|
95
|
-
metadata = {
|
|
96
|
-
"module": obj.__module__,
|
|
97
|
-
"name": getattr(obj, "__name__", "unknown"),
|
|
98
|
-
"capability": capability,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
"__serialized_callable__": True,
|
|
103
|
-
"data": cloudpickle.dumps(obj).hex(),
|
|
104
|
-
"metadata": metadata,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if isinstance(obj, list):
|
|
108
|
-
return [
|
|
109
|
-
SecureSerializer.serialize(
|
|
110
|
-
item, allow_restricted, allow_high_risk
|
|
111
|
-
)
|
|
112
|
-
for item in obj
|
|
113
|
-
]
|
|
114
|
-
|
|
115
|
-
if isinstance(obj, dict):
|
|
116
|
-
return {
|
|
117
|
-
k: SecureSerializer.serialize(
|
|
118
|
-
v, allow_restricted, allow_high_risk
|
|
119
|
-
)
|
|
120
|
-
for k, v in obj.items()
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return obj
|
|
124
|
-
|
|
125
|
-
@staticmethod
|
|
126
|
-
def deserialize(obj, allow_restricted=True, allow_high_risk=False):
|
|
127
|
-
"""Deserialize an object with capability enforcement."""
|
|
128
|
-
if isinstance(obj, dict) and obj.get("__serialized_callable__") is True:
|
|
129
|
-
# Validate the capability level during deserialization
|
|
130
|
-
metadata = obj.get("metadata", {})
|
|
131
|
-
capability = metadata.get("capability", "unknown")
|
|
132
|
-
|
|
133
|
-
if capability == "high_risk" and not allow_high_risk:
|
|
134
|
-
raise ValueError(
|
|
135
|
-
f"Cannot deserialize high risk callable {metadata.get('module')}.{metadata.get('name')}"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
if capability == "restricted" and not allow_restricted:
|
|
139
|
-
raise ValueError(
|
|
140
|
-
f"Cannot deserialize restricted callable {metadata.get('module')}.{metadata.get('name')}"
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
try:
|
|
144
|
-
callable_obj = cloudpickle.loads(bytes.fromhex(obj["data"]))
|
|
145
|
-
|
|
146
|
-
# Additional verification that the deserialized object matches its metadata
|
|
147
|
-
if callable_obj.__module__ != metadata.get("module") or (
|
|
148
|
-
hasattr(callable_obj, "__name__")
|
|
149
|
-
and callable_obj.__name__ != metadata.get("name")
|
|
150
|
-
):
|
|
151
|
-
raise ValueError(
|
|
152
|
-
"Callable metadata mismatch - possible tampering detected"
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
return callable_obj
|
|
156
|
-
except Exception as e:
|
|
157
|
-
raise ValueError(f"Failed to deserialize callable: {e!s}")
|
|
158
|
-
|
|
159
|
-
if isinstance(obj, list):
|
|
160
|
-
return [
|
|
161
|
-
SecureSerializer.deserialize(
|
|
162
|
-
item, allow_restricted, allow_high_risk
|
|
163
|
-
)
|
|
164
|
-
for item in obj
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
if isinstance(obj, dict) and "__serialized_callable__" not in obj:
|
|
168
|
-
return {
|
|
169
|
-
k: SecureSerializer.deserialize(
|
|
170
|
-
v, allow_restricted, allow_high_risk
|
|
171
|
-
)
|
|
172
|
-
for k, v in obj.items()
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return obj
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
# src/flock/core/serialization/serializable.py
|
|
2
|
-
import json
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any, Literal, TypeVar
|
|
6
|
-
|
|
7
|
-
# Use yaml if available, otherwise skip yaml methods
|
|
8
|
-
try:
|
|
9
|
-
import yaml
|
|
10
|
-
|
|
11
|
-
YAML_AVAILABLE = True
|
|
12
|
-
except ImportError:
|
|
13
|
-
YAML_AVAILABLE = False
|
|
14
|
-
|
|
15
|
-
# Use msgpack if available
|
|
16
|
-
try:
|
|
17
|
-
import msgpack
|
|
18
|
-
|
|
19
|
-
MSGPACK_AVAILABLE = True
|
|
20
|
-
except ImportError:
|
|
21
|
-
MSGPACK_AVAILABLE = False
|
|
22
|
-
|
|
23
|
-
# Use cloudpickle
|
|
24
|
-
try:
|
|
25
|
-
import cloudpickle
|
|
26
|
-
|
|
27
|
-
PICKLE_AVAILABLE = True
|
|
28
|
-
except ImportError:
|
|
29
|
-
PICKLE_AVAILABLE = False
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
T = TypeVar("T", bound="Serializable")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class Serializable(ABC):
|
|
36
|
-
"""Base class for all serializable objects in the system.
|
|
37
|
-
|
|
38
|
-
Provides methods for serializing/deserializing objects to various formats.
|
|
39
|
-
Subclasses MUST implement to_dict and from_dict.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
@abstractmethod
|
|
43
|
-
def to_dict(self) -> dict[str, Any]:
|
|
44
|
-
"""Convert instance to a dictionary representation suitable for serialization.
|
|
45
|
-
This method should handle converting nested Serializable objects and callables.
|
|
46
|
-
"""
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
@abstractmethod
|
|
51
|
-
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
52
|
-
"""Create instance from a dictionary representation.
|
|
53
|
-
This method should handle reconstructing nested Serializable objects and callables.
|
|
54
|
-
"""
|
|
55
|
-
pass
|
|
56
|
-
|
|
57
|
-
# --- JSON Methods ---
|
|
58
|
-
def to_json(self, indent: int | None = 2) -> str:
|
|
59
|
-
"""Serialize to JSON string."""
|
|
60
|
-
# Import encoder locally to avoid making it a hard dependency if JSON isn't used
|
|
61
|
-
from .json_encoder import FlockJSONEncoder
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
# Note: to_dict should ideally prepare the structure fully.
|
|
65
|
-
# FlockJSONEncoder is a fallback for types missed by to_dict.
|
|
66
|
-
return json.dumps(
|
|
67
|
-
self.to_dict(), cls=FlockJSONEncoder, indent=indent
|
|
68
|
-
)
|
|
69
|
-
except Exception as e:
|
|
70
|
-
raise RuntimeError(
|
|
71
|
-
f"Failed to serialize {self.__class__.__name__} to JSON: {e}"
|
|
72
|
-
) from e
|
|
73
|
-
|
|
74
|
-
@classmethod
|
|
75
|
-
def from_json(cls: type[T], json_str: str) -> T:
|
|
76
|
-
"""Create instance from JSON string."""
|
|
77
|
-
try:
|
|
78
|
-
data = json.loads(json_str)
|
|
79
|
-
return cls.from_dict(data)
|
|
80
|
-
except json.JSONDecodeError as e:
|
|
81
|
-
raise ValueError(f"Invalid JSON string: {e}") from e
|
|
82
|
-
except Exception as e:
|
|
83
|
-
raise RuntimeError(
|
|
84
|
-
f"Failed to deserialize {cls.__name__} from JSON: {e}"
|
|
85
|
-
) from e
|
|
86
|
-
|
|
87
|
-
# --- YAML Methods ---
|
|
88
|
-
def to_yaml(
|
|
89
|
-
self,
|
|
90
|
-
path_type: Literal["absolute", "relative"] = "relative",
|
|
91
|
-
sort_keys=False,
|
|
92
|
-
default_flow_style=False,
|
|
93
|
-
) -> str:
|
|
94
|
-
"""Serialize to YAML string.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
path_type: How file paths should be formatted ('absolute' or 'relative')
|
|
98
|
-
sort_keys: Whether to sort dictionary keys
|
|
99
|
-
default_flow_style: YAML flow style setting
|
|
100
|
-
"""
|
|
101
|
-
if not YAML_AVAILABLE:
|
|
102
|
-
raise NotImplementedError(
|
|
103
|
-
"YAML support requires PyYAML: pip install pyyaml"
|
|
104
|
-
)
|
|
105
|
-
try:
|
|
106
|
-
# If to_dict supports path_type, pass it; otherwise use standard to_dict
|
|
107
|
-
if "path_type" in self.to_dict.__code__.co_varnames:
|
|
108
|
-
dict_data = self.to_dict(path_type=path_type)
|
|
109
|
-
else:
|
|
110
|
-
dict_data = self.to_dict()
|
|
111
|
-
|
|
112
|
-
return yaml.dump(
|
|
113
|
-
dict_data,
|
|
114
|
-
sort_keys=sort_keys,
|
|
115
|
-
default_flow_style=default_flow_style,
|
|
116
|
-
allow_unicode=True,
|
|
117
|
-
)
|
|
118
|
-
except Exception as e:
|
|
119
|
-
raise RuntimeError(
|
|
120
|
-
f"Failed to serialize {self.__class__.__name__} to YAML: {e}"
|
|
121
|
-
) from e
|
|
122
|
-
|
|
123
|
-
@classmethod
|
|
124
|
-
def from_yaml(cls: type[T], yaml_str: str) -> T:
|
|
125
|
-
"""Create instance from YAML string."""
|
|
126
|
-
if not YAML_AVAILABLE:
|
|
127
|
-
raise NotImplementedError(
|
|
128
|
-
"YAML support requires PyYAML: pip install pyyaml"
|
|
129
|
-
)
|
|
130
|
-
try:
|
|
131
|
-
data = yaml.safe_load(yaml_str)
|
|
132
|
-
if not isinstance(data, dict):
|
|
133
|
-
raise TypeError(
|
|
134
|
-
f"YAML did not yield a dictionary for {cls.__name__}"
|
|
135
|
-
)
|
|
136
|
-
return cls.from_dict(data)
|
|
137
|
-
except yaml.YAMLError as e:
|
|
138
|
-
raise ValueError(f"Invalid YAML string: {e}") from e
|
|
139
|
-
except Exception as e:
|
|
140
|
-
raise RuntimeError(
|
|
141
|
-
f"Failed to deserialize {cls.__name__} from YAML: {e}"
|
|
142
|
-
) from e
|
|
143
|
-
|
|
144
|
-
def to_yaml_file(
|
|
145
|
-
self,
|
|
146
|
-
path: Path | str,
|
|
147
|
-
path_type: Literal["absolute", "relative"] = "relative",
|
|
148
|
-
**yaml_dump_kwargs,
|
|
149
|
-
) -> None:
|
|
150
|
-
"""Serialize to YAML file.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
path: File path to write to
|
|
154
|
-
path_type: How file paths should be formatted ('absolute' or 'relative')
|
|
155
|
-
**yaml_dump_kwargs: Additional arguments to pass to yaml.dump
|
|
156
|
-
"""
|
|
157
|
-
if not YAML_AVAILABLE:
|
|
158
|
-
raise NotImplementedError(
|
|
159
|
-
"YAML support requires PyYAML: pip install pyyaml"
|
|
160
|
-
)
|
|
161
|
-
path = Path(path)
|
|
162
|
-
try:
|
|
163
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
164
|
-
yaml_str = self.to_yaml(path_type=path_type, **yaml_dump_kwargs)
|
|
165
|
-
path.write_text(yaml_str, encoding="utf-8")
|
|
166
|
-
except Exception as e:
|
|
167
|
-
raise RuntimeError(
|
|
168
|
-
f"Failed to write {self.__class__.__name__} to YAML file {path}: {e}"
|
|
169
|
-
) from e
|
|
170
|
-
|
|
171
|
-
@classmethod
|
|
172
|
-
def from_yaml_file(cls: type[T], path: Path | str) -> T:
|
|
173
|
-
"""Create instance from YAML file."""
|
|
174
|
-
if not YAML_AVAILABLE:
|
|
175
|
-
raise NotImplementedError(
|
|
176
|
-
"YAML support requires PyYAML: pip install pyyaml"
|
|
177
|
-
)
|
|
178
|
-
path = Path(path)
|
|
179
|
-
try:
|
|
180
|
-
yaml_str = path.read_text(encoding="utf-8")
|
|
181
|
-
return cls.from_yaml(yaml_str)
|
|
182
|
-
except FileNotFoundError:
|
|
183
|
-
raise
|
|
184
|
-
except Exception as e:
|
|
185
|
-
raise RuntimeError(
|
|
186
|
-
f"Failed to read {cls.__name__} from YAML file {path}: {e}"
|
|
187
|
-
) from e
|
|
188
|
-
|
|
189
|
-
# --- MsgPack Methods ---
|
|
190
|
-
def to_msgpack(self) -> bytes:
|
|
191
|
-
"""Serialize to msgpack bytes."""
|
|
192
|
-
if not MSGPACK_AVAILABLE:
|
|
193
|
-
raise NotImplementedError(
|
|
194
|
-
"MsgPack support requires msgpack: pip install msgpack"
|
|
195
|
-
)
|
|
196
|
-
try:
|
|
197
|
-
# Use default hook for complex types if needed, or rely on to_dict
|
|
198
|
-
return msgpack.packb(self.to_dict(), use_bin_type=True)
|
|
199
|
-
except Exception as e:
|
|
200
|
-
raise RuntimeError(
|
|
201
|
-
f"Failed to serialize {self.__class__.__name__} to MsgPack: {e}"
|
|
202
|
-
) from e
|
|
203
|
-
|
|
204
|
-
@classmethod
|
|
205
|
-
def from_msgpack(cls: type[T], msgpack_bytes: bytes) -> T:
|
|
206
|
-
"""Create instance from msgpack bytes."""
|
|
207
|
-
if not MSGPACK_AVAILABLE:
|
|
208
|
-
raise NotImplementedError(
|
|
209
|
-
"MsgPack support requires msgpack: pip install msgpack"
|
|
210
|
-
)
|
|
211
|
-
try:
|
|
212
|
-
# Use object_hook if custom deserialization is needed beyond from_dict
|
|
213
|
-
data = msgpack.unpackb(msgpack_bytes, raw=False)
|
|
214
|
-
if not isinstance(data, dict):
|
|
215
|
-
raise TypeError(
|
|
216
|
-
f"MsgPack did not yield a dictionary for {cls.__name__}"
|
|
217
|
-
)
|
|
218
|
-
return cls.from_dict(data)
|
|
219
|
-
except Exception as e:
|
|
220
|
-
raise RuntimeError(
|
|
221
|
-
f"Failed to deserialize {cls.__name__} from MsgPack: {e}"
|
|
222
|
-
) from e
|
|
223
|
-
|
|
224
|
-
def to_msgpack_file(self, path: Path | str) -> None:
|
|
225
|
-
"""Serialize to msgpack file."""
|
|
226
|
-
if not MSGPACK_AVAILABLE:
|
|
227
|
-
raise NotImplementedError(
|
|
228
|
-
"MsgPack support requires msgpack: pip install msgpack"
|
|
229
|
-
)
|
|
230
|
-
path = Path(path)
|
|
231
|
-
try:
|
|
232
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
233
|
-
msgpack_bytes = self.to_msgpack()
|
|
234
|
-
path.write_bytes(msgpack_bytes)
|
|
235
|
-
except Exception as e:
|
|
236
|
-
raise RuntimeError(
|
|
237
|
-
f"Failed to write {self.__class__.__name__} to MsgPack file {path}: {e}"
|
|
238
|
-
) from e
|
|
239
|
-
|
|
240
|
-
@classmethod
|
|
241
|
-
def from_msgpack_file(cls: type[T], path: Path | str) -> T:
|
|
242
|
-
"""Create instance from msgpack file."""
|
|
243
|
-
if not MSGPACK_AVAILABLE:
|
|
244
|
-
raise NotImplementedError(
|
|
245
|
-
"MsgPack support requires msgpack: pip install msgpack"
|
|
246
|
-
)
|
|
247
|
-
path = Path(path)
|
|
248
|
-
try:
|
|
249
|
-
msgpack_bytes = path.read_bytes()
|
|
250
|
-
return cls.from_msgpack(msgpack_bytes)
|
|
251
|
-
except FileNotFoundError:
|
|
252
|
-
raise
|
|
253
|
-
except Exception as e:
|
|
254
|
-
raise RuntimeError(
|
|
255
|
-
f"Failed to read {cls.__name__} from MsgPack file {path}: {e}"
|
|
256
|
-
) from e
|
|
257
|
-
|
|
258
|
-
# --- Pickle Methods (Use with caution due to security risks) ---
|
|
259
|
-
def to_pickle(self) -> bytes:
|
|
260
|
-
"""Serialize to pickle bytes using cloudpickle."""
|
|
261
|
-
if not PICKLE_AVAILABLE:
|
|
262
|
-
raise NotImplementedError(
|
|
263
|
-
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
264
|
-
)
|
|
265
|
-
try:
|
|
266
|
-
return cloudpickle.dumps(self)
|
|
267
|
-
except Exception as e:
|
|
268
|
-
raise RuntimeError(
|
|
269
|
-
f"Failed to serialize {self.__class__.__name__} to Pickle: {e}"
|
|
270
|
-
) from e
|
|
271
|
-
|
|
272
|
-
@classmethod
|
|
273
|
-
def from_pickle(cls: type[T], pickle_bytes: bytes) -> T:
|
|
274
|
-
"""Create instance from pickle bytes using cloudpickle."""
|
|
275
|
-
if not PICKLE_AVAILABLE:
|
|
276
|
-
raise NotImplementedError(
|
|
277
|
-
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
278
|
-
)
|
|
279
|
-
try:
|
|
280
|
-
instance = cloudpickle.loads(pickle_bytes)
|
|
281
|
-
if not isinstance(instance, cls):
|
|
282
|
-
raise TypeError(
|
|
283
|
-
f"Deserialized object is not of type {cls.__name__}"
|
|
284
|
-
)
|
|
285
|
-
return instance
|
|
286
|
-
except Exception as e:
|
|
287
|
-
raise RuntimeError(
|
|
288
|
-
f"Failed to deserialize {cls.__name__} from Pickle: {e}"
|
|
289
|
-
) from e
|
|
290
|
-
|
|
291
|
-
def to_pickle_file(self, path: Path | str) -> None:
|
|
292
|
-
"""Serialize to pickle file using cloudpickle."""
|
|
293
|
-
if not PICKLE_AVAILABLE:
|
|
294
|
-
raise NotImplementedError(
|
|
295
|
-
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
296
|
-
)
|
|
297
|
-
path = Path(path)
|
|
298
|
-
try:
|
|
299
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
300
|
-
pickle_bytes = self.to_pickle()
|
|
301
|
-
path.write_bytes(pickle_bytes)
|
|
302
|
-
except Exception as e:
|
|
303
|
-
raise RuntimeError(
|
|
304
|
-
f"Failed to write {self.__class__.__name__} to Pickle file {path}: {e}"
|
|
305
|
-
) from e
|
|
306
|
-
|
|
307
|
-
@classmethod
|
|
308
|
-
def from_pickle_file(cls: type[T], path: Path | str) -> T:
|
|
309
|
-
"""Create instance from pickle file using cloudpickle."""
|
|
310
|
-
if not PICKLE_AVAILABLE:
|
|
311
|
-
raise NotImplementedError(
|
|
312
|
-
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
313
|
-
)
|
|
314
|
-
path = Path(path)
|
|
315
|
-
try:
|
|
316
|
-
pickle_bytes = path.read_bytes()
|
|
317
|
-
return cls.from_pickle(pickle_bytes)
|
|
318
|
-
except FileNotFoundError:
|
|
319
|
-
raise
|
|
320
|
-
except Exception as e:
|
|
321
|
-
raise RuntimeError(
|
|
322
|
-
f"Failed to read {cls.__name__} from Pickle file {path}: {e}"
|
|
323
|
-
) from e
|
|
324
|
-
|
|
325
|
-
# _filter_none_values remains unchanged
|
|
326
|
-
@staticmethod
|
|
327
|
-
def _filter_none_values(data: Any) -> Any:
|
|
328
|
-
"""Filter out None values from dictionaries and lists recursively."""
|
|
329
|
-
if isinstance(data, dict):
|
|
330
|
-
return {
|
|
331
|
-
k: Serializable._filter_none_values(v)
|
|
332
|
-
for k, v in data.items()
|
|
333
|
-
if v is not None
|
|
334
|
-
}
|
|
335
|
-
elif isinstance(data, list):
|
|
336
|
-
# Filter None from list items AND recursively filter within items
|
|
337
|
-
return [
|
|
338
|
-
Serializable._filter_none_values(item)
|
|
339
|
-
for item in data
|
|
340
|
-
if item is not None
|
|
341
|
-
]
|
|
342
|
-
return data
|