PraisonAI 3.0.0__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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-memory / JSON file implementation of StateStore.
|
|
3
|
+
|
|
4
|
+
Zero external dependencies - uses Python built-ins.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from .base import StateStore
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MemoryStateStore(StateStore):
|
|
20
|
+
"""
|
|
21
|
+
In-memory state store with optional JSON file persistence.
|
|
22
|
+
|
|
23
|
+
Zero external dependencies - ideal for development and testing.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
# In-memory only
|
|
27
|
+
store = MemoryStateStore()
|
|
28
|
+
|
|
29
|
+
# With file persistence
|
|
30
|
+
store = MemoryStateStore(path="./state.json")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
path: Optional[str] = None,
|
|
36
|
+
auto_save: bool = True,
|
|
37
|
+
save_interval: int = 60,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize memory state store.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
path: Path to JSON file for persistence (None = in-memory only)
|
|
44
|
+
auto_save: Auto-save to file periodically
|
|
45
|
+
save_interval: Save interval in seconds
|
|
46
|
+
"""
|
|
47
|
+
self.path = path
|
|
48
|
+
self.auto_save = auto_save
|
|
49
|
+
self.save_interval = save_interval
|
|
50
|
+
|
|
51
|
+
self._data: Dict[str, Any] = {}
|
|
52
|
+
self._ttls: Dict[str, float] = {} # key -> expiry timestamp
|
|
53
|
+
self._lock = threading.RLock()
|
|
54
|
+
self._last_save = time.time()
|
|
55
|
+
|
|
56
|
+
# Load from file if exists
|
|
57
|
+
if path and os.path.exists(path):
|
|
58
|
+
self._load()
|
|
59
|
+
|
|
60
|
+
logger.info(f"MemoryStateStore initialized (path={path})")
|
|
61
|
+
|
|
62
|
+
def _load(self) -> None:
|
|
63
|
+
"""Load state from JSON file."""
|
|
64
|
+
if not self.path:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
with open(self.path, "r") as f:
|
|
69
|
+
saved = json.load(f)
|
|
70
|
+
self._data = saved.get("data", {})
|
|
71
|
+
self._ttls = saved.get("ttls", {})
|
|
72
|
+
|
|
73
|
+
# Clean expired keys
|
|
74
|
+
now = time.time()
|
|
75
|
+
expired = [k for k, exp in self._ttls.items() if exp <= now]
|
|
76
|
+
for k in expired:
|
|
77
|
+
self._data.pop(k, None)
|
|
78
|
+
self._ttls.pop(k, None)
|
|
79
|
+
|
|
80
|
+
logger.debug(f"Loaded {len(self._data)} keys from {self.path}")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f"Failed to load state from {self.path}: {e}")
|
|
83
|
+
|
|
84
|
+
def _save(self) -> None:
|
|
85
|
+
"""Save state to JSON file."""
|
|
86
|
+
if not self.path:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
os.makedirs(os.path.dirname(self.path) or ".", exist_ok=True)
|
|
91
|
+
with open(self.path, "w") as f:
|
|
92
|
+
json.dump({"data": self._data, "ttls": self._ttls}, f)
|
|
93
|
+
self._last_save = time.time()
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.warning(f"Failed to save state to {self.path}: {e}")
|
|
96
|
+
|
|
97
|
+
def _maybe_save(self) -> None:
|
|
98
|
+
"""Save if auto_save is enabled and interval has passed."""
|
|
99
|
+
if self.auto_save and self.path:
|
|
100
|
+
if time.time() - self._last_save >= self.save_interval:
|
|
101
|
+
self._save()
|
|
102
|
+
|
|
103
|
+
def _check_ttl(self, key: str) -> bool:
|
|
104
|
+
"""Check if key is expired. Returns True if valid, False if expired."""
|
|
105
|
+
if key not in self._ttls:
|
|
106
|
+
return True
|
|
107
|
+
if self._ttls[key] <= time.time():
|
|
108
|
+
self._data.pop(key, None)
|
|
109
|
+
self._ttls.pop(key, None)
|
|
110
|
+
return False
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
def get(self, key: str) -> Optional[Any]:
|
|
114
|
+
"""Get a value by key."""
|
|
115
|
+
with self._lock:
|
|
116
|
+
if not self._check_ttl(key):
|
|
117
|
+
return None
|
|
118
|
+
return self._data.get(key)
|
|
119
|
+
|
|
120
|
+
def set(
|
|
121
|
+
self,
|
|
122
|
+
key: str,
|
|
123
|
+
value: Any,
|
|
124
|
+
ttl: Optional[int] = None
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Set a value with optional TTL."""
|
|
127
|
+
with self._lock:
|
|
128
|
+
self._data[key] = value
|
|
129
|
+
if ttl:
|
|
130
|
+
self._ttls[key] = time.time() + ttl
|
|
131
|
+
elif key in self._ttls:
|
|
132
|
+
del self._ttls[key]
|
|
133
|
+
self._maybe_save()
|
|
134
|
+
|
|
135
|
+
def delete(self, key: str) -> bool:
|
|
136
|
+
"""Delete a key."""
|
|
137
|
+
with self._lock:
|
|
138
|
+
existed = key in self._data
|
|
139
|
+
self._data.pop(key, None)
|
|
140
|
+
self._ttls.pop(key, None)
|
|
141
|
+
self._maybe_save()
|
|
142
|
+
return existed
|
|
143
|
+
|
|
144
|
+
def exists(self, key: str) -> bool:
|
|
145
|
+
"""Check if a key exists."""
|
|
146
|
+
with self._lock:
|
|
147
|
+
if not self._check_ttl(key):
|
|
148
|
+
return False
|
|
149
|
+
return key in self._data
|
|
150
|
+
|
|
151
|
+
def keys(self, pattern: str = "*") -> List[str]:
|
|
152
|
+
"""List keys matching pattern."""
|
|
153
|
+
import fnmatch
|
|
154
|
+
|
|
155
|
+
with self._lock:
|
|
156
|
+
# Clean expired keys first
|
|
157
|
+
now = time.time()
|
|
158
|
+
expired = [k for k, exp in self._ttls.items() if exp <= now]
|
|
159
|
+
for k in expired:
|
|
160
|
+
self._data.pop(k, None)
|
|
161
|
+
self._ttls.pop(k, None)
|
|
162
|
+
|
|
163
|
+
if pattern == "*":
|
|
164
|
+
return list(self._data.keys())
|
|
165
|
+
return [k for k in self._data.keys() if fnmatch.fnmatch(k, pattern)]
|
|
166
|
+
|
|
167
|
+
def ttl(self, key: str) -> Optional[int]:
|
|
168
|
+
"""Get remaining TTL in seconds."""
|
|
169
|
+
with self._lock:
|
|
170
|
+
if key not in self._ttls:
|
|
171
|
+
return None
|
|
172
|
+
remaining = self._ttls[key] - time.time()
|
|
173
|
+
if remaining <= 0:
|
|
174
|
+
self._data.pop(key, None)
|
|
175
|
+
self._ttls.pop(key, None)
|
|
176
|
+
return None
|
|
177
|
+
return int(remaining)
|
|
178
|
+
|
|
179
|
+
def expire(self, key: str, ttl: int) -> bool:
|
|
180
|
+
"""Set TTL on existing key."""
|
|
181
|
+
with self._lock:
|
|
182
|
+
if key not in self._data:
|
|
183
|
+
return False
|
|
184
|
+
self._ttls[key] = time.time() + ttl
|
|
185
|
+
self._maybe_save()
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def hget(self, key: str, field: str) -> Optional[Any]:
|
|
189
|
+
"""Get a field from a hash."""
|
|
190
|
+
with self._lock:
|
|
191
|
+
if not self._check_ttl(key):
|
|
192
|
+
return None
|
|
193
|
+
data = self._data.get(key)
|
|
194
|
+
if not isinstance(data, dict):
|
|
195
|
+
return None
|
|
196
|
+
return data.get(field)
|
|
197
|
+
|
|
198
|
+
def hset(self, key: str, field: str, value: Any) -> None:
|
|
199
|
+
"""Set a field in a hash."""
|
|
200
|
+
with self._lock:
|
|
201
|
+
if key not in self._data or not isinstance(self._data[key], dict):
|
|
202
|
+
self._data[key] = {}
|
|
203
|
+
self._data[key][field] = value
|
|
204
|
+
self._maybe_save()
|
|
205
|
+
|
|
206
|
+
def hgetall(self, key: str) -> Dict[str, Any]:
|
|
207
|
+
"""Get all fields from a hash."""
|
|
208
|
+
with self._lock:
|
|
209
|
+
if not self._check_ttl(key):
|
|
210
|
+
return {}
|
|
211
|
+
data = self._data.get(key)
|
|
212
|
+
if not isinstance(data, dict):
|
|
213
|
+
return {}
|
|
214
|
+
return dict(data)
|
|
215
|
+
|
|
216
|
+
def hdel(self, key: str, *fields: str) -> int:
|
|
217
|
+
"""Delete fields from a hash."""
|
|
218
|
+
with self._lock:
|
|
219
|
+
if key not in self._data or not isinstance(self._data[key], dict):
|
|
220
|
+
return 0
|
|
221
|
+
count = 0
|
|
222
|
+
for field in fields:
|
|
223
|
+
if field in self._data[key]:
|
|
224
|
+
del self._data[key][field]
|
|
225
|
+
count += 1
|
|
226
|
+
self._maybe_save()
|
|
227
|
+
return count
|
|
228
|
+
|
|
229
|
+
def flush(self) -> None:
|
|
230
|
+
"""Force save to file."""
|
|
231
|
+
with self._lock:
|
|
232
|
+
self._save()
|
|
233
|
+
|
|
234
|
+
def clear(self) -> None:
|
|
235
|
+
"""Clear all data."""
|
|
236
|
+
with self._lock:
|
|
237
|
+
self._data.clear()
|
|
238
|
+
self._ttls.clear()
|
|
239
|
+
self._maybe_save()
|
|
240
|
+
|
|
241
|
+
def close(self) -> None:
|
|
242
|
+
"""Close the store and save."""
|
|
243
|
+
with self._lock:
|
|
244
|
+
if self.path:
|
|
245
|
+
self._save()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MongoDB implementation of StateStore.
|
|
3
|
+
|
|
4
|
+
Requires: pymongo
|
|
5
|
+
Install: pip install pymongo
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from .base import StateStore
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MongoDBStateStore(StateStore):
|
|
19
|
+
"""
|
|
20
|
+
MongoDB-based state store.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
store = MongoDBStateStore(
|
|
24
|
+
url="mongodb://localhost:27017",
|
|
25
|
+
database="praisonai"
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
url: str = "mongodb://localhost:27017",
|
|
32
|
+
database: str = "praisonai",
|
|
33
|
+
collection: str = "state",
|
|
34
|
+
):
|
|
35
|
+
try:
|
|
36
|
+
from pymongo import MongoClient
|
|
37
|
+
except ImportError:
|
|
38
|
+
raise ImportError(
|
|
39
|
+
"pymongo is required for MongoDB support. "
|
|
40
|
+
"Install with: pip install pymongo"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self._client = MongoClient(url)
|
|
44
|
+
self._db = self._client[database]
|
|
45
|
+
self._collection = self._db[collection]
|
|
46
|
+
|
|
47
|
+
# Create TTL index for automatic expiration
|
|
48
|
+
self._collection.create_index("expires_at", expireAfterSeconds=0)
|
|
49
|
+
|
|
50
|
+
logger.info(f"Connected to MongoDB: {database}.{collection}")
|
|
51
|
+
|
|
52
|
+
def get(self, key: str) -> Optional[Any]:
|
|
53
|
+
"""Get a value by key."""
|
|
54
|
+
doc = self._collection.find_one({"_id": key})
|
|
55
|
+
if not doc:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# Check TTL
|
|
59
|
+
if doc.get("expires_at") and doc["expires_at"] <= time.time():
|
|
60
|
+
self._collection.delete_one({"_id": key})
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return doc.get("value")
|
|
64
|
+
|
|
65
|
+
def set(
|
|
66
|
+
self,
|
|
67
|
+
key: str,
|
|
68
|
+
value: Any,
|
|
69
|
+
ttl: Optional[int] = None
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Set a value with optional TTL."""
|
|
72
|
+
doc = {"_id": key, "value": value, "updated_at": time.time()}
|
|
73
|
+
|
|
74
|
+
if ttl:
|
|
75
|
+
from datetime import datetime, timedelta
|
|
76
|
+
doc["expires_at"] = datetime.utcnow() + timedelta(seconds=ttl)
|
|
77
|
+
|
|
78
|
+
self._collection.replace_one({"_id": key}, doc, upsert=True)
|
|
79
|
+
|
|
80
|
+
def delete(self, key: str) -> bool:
|
|
81
|
+
"""Delete a key."""
|
|
82
|
+
result = self._collection.delete_one({"_id": key})
|
|
83
|
+
return result.deleted_count > 0
|
|
84
|
+
|
|
85
|
+
def exists(self, key: str) -> bool:
|
|
86
|
+
"""Check if a key exists."""
|
|
87
|
+
doc = self._collection.find_one({"_id": key}, {"_id": 1, "expires_at": 1})
|
|
88
|
+
if not doc:
|
|
89
|
+
return False
|
|
90
|
+
if doc.get("expires_at") and doc["expires_at"] <= time.time():
|
|
91
|
+
return False
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def keys(self, pattern: str = "*") -> List[str]:
|
|
95
|
+
"""List keys matching pattern."""
|
|
96
|
+
if pattern == "*":
|
|
97
|
+
cursor = self._collection.find({}, {"_id": 1})
|
|
98
|
+
else:
|
|
99
|
+
# Convert glob pattern to regex
|
|
100
|
+
import re
|
|
101
|
+
regex = pattern.replace("*", ".*").replace("?", ".")
|
|
102
|
+
cursor = self._collection.find({"_id": {"$regex": f"^{regex}$"}}, {"_id": 1})
|
|
103
|
+
|
|
104
|
+
return [doc["_id"] for doc in cursor]
|
|
105
|
+
|
|
106
|
+
def ttl(self, key: str) -> Optional[int]:
|
|
107
|
+
"""Get remaining TTL in seconds."""
|
|
108
|
+
doc = self._collection.find_one({"_id": key}, {"expires_at": 1})
|
|
109
|
+
if not doc or "expires_at" not in doc:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
remaining = doc["expires_at"].timestamp() - time.time()
|
|
113
|
+
if remaining <= 0:
|
|
114
|
+
return None
|
|
115
|
+
return int(remaining)
|
|
116
|
+
|
|
117
|
+
def expire(self, key: str, ttl: int) -> bool:
|
|
118
|
+
"""Set TTL on existing key."""
|
|
119
|
+
from datetime import datetime, timedelta
|
|
120
|
+
|
|
121
|
+
result = self._collection.update_one(
|
|
122
|
+
{"_id": key},
|
|
123
|
+
{"$set": {"expires_at": datetime.utcnow() + timedelta(seconds=ttl)}}
|
|
124
|
+
)
|
|
125
|
+
return result.modified_count > 0
|
|
126
|
+
|
|
127
|
+
def hget(self, key: str, field: str) -> Optional[Any]:
|
|
128
|
+
"""Get a field from a hash."""
|
|
129
|
+
doc = self._collection.find_one({"_id": key})
|
|
130
|
+
if not doc or not isinstance(doc.get("value"), dict):
|
|
131
|
+
return None
|
|
132
|
+
return doc["value"].get(field)
|
|
133
|
+
|
|
134
|
+
def hset(self, key: str, field: str, value: Any) -> None:
|
|
135
|
+
"""Set a field in a hash."""
|
|
136
|
+
self._collection.update_one(
|
|
137
|
+
{"_id": key},
|
|
138
|
+
{"$set": {f"value.{field}": value, "updated_at": time.time()}},
|
|
139
|
+
upsert=True
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def hgetall(self, key: str) -> Dict[str, Any]:
|
|
143
|
+
"""Get all fields from a hash."""
|
|
144
|
+
doc = self._collection.find_one({"_id": key})
|
|
145
|
+
if not doc or not isinstance(doc.get("value"), dict):
|
|
146
|
+
return {}
|
|
147
|
+
return doc["value"]
|
|
148
|
+
|
|
149
|
+
def hdel(self, key: str, *fields: str) -> int:
|
|
150
|
+
"""Delete fields from a hash."""
|
|
151
|
+
unset = {f"value.{field}": "" for field in fields}
|
|
152
|
+
result = self._collection.update_one({"_id": key}, {"$unset": unset})
|
|
153
|
+
return len(fields) if result.modified_count > 0 else 0
|
|
154
|
+
|
|
155
|
+
def close(self) -> None:
|
|
156
|
+
"""Close the store."""
|
|
157
|
+
if self._client:
|
|
158
|
+
self._client.close()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Redis implementation of StateStore.
|
|
3
|
+
|
|
4
|
+
Requires: redis
|
|
5
|
+
Install: pip install redis
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .base import StateStore
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RedisStateStore(StateStore):
|
|
18
|
+
"""
|
|
19
|
+
Redis-based state store for fast key-value operations.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
store = RedisStateStore(
|
|
23
|
+
url="redis://localhost:6379"
|
|
24
|
+
)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
url: Optional[str] = None,
|
|
30
|
+
host: str = "localhost",
|
|
31
|
+
port: int = 6379,
|
|
32
|
+
db: int = 0,
|
|
33
|
+
password: Optional[str] = None,
|
|
34
|
+
prefix: str = "praison:",
|
|
35
|
+
decode_responses: bool = True,
|
|
36
|
+
socket_timeout: int = 5,
|
|
37
|
+
max_connections: int = 10,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize Redis state store.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
url: Full Redis URL (overrides host/port/db/password)
|
|
44
|
+
host: Redis host
|
|
45
|
+
port: Redis port
|
|
46
|
+
db: Redis database number
|
|
47
|
+
password: Redis password
|
|
48
|
+
prefix: Key prefix for namespacing
|
|
49
|
+
decode_responses: Decode bytes to strings
|
|
50
|
+
socket_timeout: Socket timeout in seconds
|
|
51
|
+
max_connections: Max connections in pool
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
import redis as redis_lib
|
|
55
|
+
except ImportError:
|
|
56
|
+
raise ImportError(
|
|
57
|
+
"redis is required for Redis support. "
|
|
58
|
+
"Install with: pip install redis"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._redis_lib = redis_lib
|
|
62
|
+
self.prefix = prefix
|
|
63
|
+
|
|
64
|
+
if url:
|
|
65
|
+
self._client = redis_lib.from_url(
|
|
66
|
+
url,
|
|
67
|
+
decode_responses=decode_responses,
|
|
68
|
+
socket_timeout=socket_timeout,
|
|
69
|
+
max_connections=max_connections,
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
pool = redis_lib.ConnectionPool(
|
|
73
|
+
host=host,
|
|
74
|
+
port=port,
|
|
75
|
+
db=db,
|
|
76
|
+
password=password,
|
|
77
|
+
decode_responses=decode_responses,
|
|
78
|
+
socket_timeout=socket_timeout,
|
|
79
|
+
max_connections=max_connections,
|
|
80
|
+
)
|
|
81
|
+
self._client = redis_lib.Redis(connection_pool=pool)
|
|
82
|
+
|
|
83
|
+
# Test connection
|
|
84
|
+
self._client.ping()
|
|
85
|
+
logger.info(f"Connected to Redis at {url or f'{host}:{port}'}")
|
|
86
|
+
|
|
87
|
+
def _key(self, key: str) -> str:
|
|
88
|
+
"""Add prefix to key."""
|
|
89
|
+
return f"{self.prefix}{key}"
|
|
90
|
+
|
|
91
|
+
def get(self, key: str) -> Optional[Any]:
|
|
92
|
+
"""Get a value by key."""
|
|
93
|
+
value = self._client.get(self._key(key))
|
|
94
|
+
if value is None:
|
|
95
|
+
return None
|
|
96
|
+
# Try to deserialize JSON
|
|
97
|
+
try:
|
|
98
|
+
return json.loads(value)
|
|
99
|
+
except (json.JSONDecodeError, TypeError):
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
def set(
|
|
103
|
+
self,
|
|
104
|
+
key: str,
|
|
105
|
+
value: Any,
|
|
106
|
+
ttl: Optional[int] = None
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Set a value with optional TTL."""
|
|
109
|
+
# Serialize non-string values
|
|
110
|
+
if not isinstance(value, str):
|
|
111
|
+
value = json.dumps(value)
|
|
112
|
+
|
|
113
|
+
if ttl:
|
|
114
|
+
self._client.setex(self._key(key), ttl, value)
|
|
115
|
+
else:
|
|
116
|
+
self._client.set(self._key(key), value)
|
|
117
|
+
|
|
118
|
+
def delete(self, key: str) -> bool:
|
|
119
|
+
"""Delete a key."""
|
|
120
|
+
return self._client.delete(self._key(key)) > 0
|
|
121
|
+
|
|
122
|
+
def exists(self, key: str) -> bool:
|
|
123
|
+
"""Check if a key exists."""
|
|
124
|
+
return self._client.exists(self._key(key)) > 0
|
|
125
|
+
|
|
126
|
+
def keys(self, pattern: str = "*") -> List[str]:
|
|
127
|
+
"""List keys matching pattern."""
|
|
128
|
+
full_pattern = self._key(pattern)
|
|
129
|
+
keys = self._client.keys(full_pattern)
|
|
130
|
+
# Remove prefix from returned keys
|
|
131
|
+
prefix_len = len(self.prefix)
|
|
132
|
+
return [k[prefix_len:] if k.startswith(self.prefix) else k for k in keys]
|
|
133
|
+
|
|
134
|
+
def ttl(self, key: str) -> Optional[int]:
|
|
135
|
+
"""Get remaining TTL in seconds."""
|
|
136
|
+
result = self._client.ttl(self._key(key))
|
|
137
|
+
if result < 0: # -1 = no TTL, -2 = key doesn't exist
|
|
138
|
+
return None
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
def expire(self, key: str, ttl: int) -> bool:
|
|
142
|
+
"""Set TTL on existing key."""
|
|
143
|
+
return self._client.expire(self._key(key), ttl)
|
|
144
|
+
|
|
145
|
+
def hget(self, key: str, field: str) -> Optional[Any]:
|
|
146
|
+
"""Get a field from a hash."""
|
|
147
|
+
value = self._client.hget(self._key(key), field)
|
|
148
|
+
if value is None:
|
|
149
|
+
return None
|
|
150
|
+
try:
|
|
151
|
+
return json.loads(value)
|
|
152
|
+
except (json.JSONDecodeError, TypeError):
|
|
153
|
+
return value
|
|
154
|
+
|
|
155
|
+
def hset(self, key: str, field: str, value: Any) -> None:
|
|
156
|
+
"""Set a field in a hash."""
|
|
157
|
+
if not isinstance(value, str):
|
|
158
|
+
value = json.dumps(value)
|
|
159
|
+
self._client.hset(self._key(key), field, value)
|
|
160
|
+
|
|
161
|
+
def hgetall(self, key: str) -> Dict[str, Any]:
|
|
162
|
+
"""Get all fields from a hash."""
|
|
163
|
+
data = self._client.hgetall(self._key(key))
|
|
164
|
+
result = {}
|
|
165
|
+
for k, v in data.items():
|
|
166
|
+
try:
|
|
167
|
+
result[k] = json.loads(v)
|
|
168
|
+
except (json.JSONDecodeError, TypeError):
|
|
169
|
+
result[k] = v
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def hdel(self, key: str, *fields: str) -> int:
|
|
173
|
+
"""Delete fields from a hash."""
|
|
174
|
+
if not fields:
|
|
175
|
+
return 0
|
|
176
|
+
return self._client.hdel(self._key(key), *fields)
|
|
177
|
+
|
|
178
|
+
def incr(self, key: str, amount: int = 1) -> int:
|
|
179
|
+
"""Increment a counter."""
|
|
180
|
+
return self._client.incrby(self._key(key), amount)
|
|
181
|
+
|
|
182
|
+
def decr(self, key: str, amount: int = 1) -> int:
|
|
183
|
+
"""Decrement a counter."""
|
|
184
|
+
return self._client.decrby(self._key(key), amount)
|
|
185
|
+
|
|
186
|
+
def close(self) -> None:
|
|
187
|
+
"""Close the store."""
|
|
188
|
+
if self._client:
|
|
189
|
+
self._client.close()
|
|
190
|
+
self._client = None
|