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.

Files changed (359) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {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