htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SharedCache - Unified caching layer for all HtmlGraph operations.
|
|
3
|
+
|
|
4
|
+
Consolidates 16+ separate cache implementations.
|
|
5
|
+
Provides:
|
|
6
|
+
- Centralized cache with LRU eviction
|
|
7
|
+
- TTL-based expiration
|
|
8
|
+
- Pattern-based invalidation
|
|
9
|
+
- Thread-safe singleton access
|
|
10
|
+
- Metrics and observability
|
|
11
|
+
|
|
12
|
+
All implementations MUST pass SharedCacheComplianceTests.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
pass # For forward references
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CacheInvalidationPattern(Enum):
|
|
25
|
+
"""Cache invalidation patterns."""
|
|
26
|
+
|
|
27
|
+
SINGLE_KEY = "single" # Invalidate one key
|
|
28
|
+
PREFIX_PATTERN = "prefix" # Invalidate keys matching prefix (e.g., "feature:*")
|
|
29
|
+
RESOURCE_CHANGED = "resource" # Invalidate all derived caches
|
|
30
|
+
CLEAR_ALL = "all" # Clear entire cache
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_shared_cache() -> "SharedCache":
|
|
34
|
+
"""
|
|
35
|
+
Get singleton instance of SharedCache.
|
|
36
|
+
|
|
37
|
+
Ensures only one cache instance across application.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
SharedCache singleton
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> cache = get_shared_cache()
|
|
44
|
+
>>> cache.set("mykey", value, ttl=3600)
|
|
45
|
+
>>> value = cache.get("mykey")
|
|
46
|
+
"""
|
|
47
|
+
if not hasattr(get_shared_cache, "_instance"):
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"SharedCache not initialized. Call initialize_shared_cache() first."
|
|
50
|
+
)
|
|
51
|
+
instance: SharedCache = get_shared_cache._instance # type: ignore[attr-defined]
|
|
52
|
+
return instance
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SharedCacheError(Exception):
|
|
56
|
+
"""Base exception for cache operations."""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CacheKeyError(SharedCacheError):
|
|
62
|
+
"""Raised when cache key is invalid."""
|
|
63
|
+
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CacheCapacityError(SharedCacheError):
|
|
68
|
+
"""Raised when cache reaches max capacity and can't evict."""
|
|
69
|
+
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SharedCache(ABC):
|
|
74
|
+
"""
|
|
75
|
+
Unified caching layer for all HtmlGraph data access.
|
|
76
|
+
|
|
77
|
+
Addresses cache fragmentation and invalidation gaps:
|
|
78
|
+
- 16+ separate cache implementations → 1 unified cache
|
|
79
|
+
- 4+ critical invalidation gaps → Centralized invalidation
|
|
80
|
+
- No cache coherence → Pattern-based invalidation
|
|
81
|
+
- No metrics → Built-in cache statistics
|
|
82
|
+
|
|
83
|
+
CONTRACT:
|
|
84
|
+
1. **Coherence**: Cache always consistent with storage
|
|
85
|
+
2. **Atomicity**: get/set operations are atomic
|
|
86
|
+
3. **Isolation**: Concurrent access safe
|
|
87
|
+
4. **Efficiency**: O(1) get/set, O(1) delete with O(n) patterns
|
|
88
|
+
5. **Observability**: Metrics tracked automatically
|
|
89
|
+
|
|
90
|
+
INVALIDATION SIGNALS:
|
|
91
|
+
When underlying data changes, cache must be invalidated:
|
|
92
|
+
- Feature changed → invalidate "feature:*", "dependency:*", "analytics:*"
|
|
93
|
+
- Track changed → invalidate "track:*", "feature:*"
|
|
94
|
+
- Analytics invalidate → invalidate "dependency:*", "priority:*", "recommendation:*"
|
|
95
|
+
|
|
96
|
+
CACHE KEYS (Convention):
|
|
97
|
+
- "feature:{id}" - Individual feature
|
|
98
|
+
- "feature:list:{filter}" - Feature list with filter
|
|
99
|
+
- "track:{id}" - Individual track
|
|
100
|
+
- "dependency:{id}" - Dependency analysis
|
|
101
|
+
- "priority:{id}" - Priority score
|
|
102
|
+
- "recommendation:*" - Recommendations
|
|
103
|
+
|
|
104
|
+
PERFORMANCE:
|
|
105
|
+
- get(key): O(1) average
|
|
106
|
+
- set(key, value, ttl): O(1) average
|
|
107
|
+
- delete(key): O(1) average
|
|
108
|
+
- delete_pattern(pattern): O(n) where n = matching keys
|
|
109
|
+
- clear(): O(n) where n = total keys
|
|
110
|
+
- size(): O(1)
|
|
111
|
+
|
|
112
|
+
MEMORY MANAGEMENT:
|
|
113
|
+
- LRU (Least Recently Used) eviction when max_size exceeded
|
|
114
|
+
- TTL-based automatic expiration
|
|
115
|
+
- Configurable max cache size
|
|
116
|
+
|
|
117
|
+
THREAD SAFETY:
|
|
118
|
+
- Thread-safe for concurrent reads
|
|
119
|
+
- Thread-safe for concurrent writes (serialized)
|
|
120
|
+
- No deadlock risk
|
|
121
|
+
- Atomic operations (no partial states visible)
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# ===== GET OPERATIONS =====
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
def get(self, key: str) -> Any | None:
|
|
128
|
+
"""
|
|
129
|
+
Retrieve cached value by key.
|
|
130
|
+
|
|
131
|
+
Returns None if key not found or expired.
|
|
132
|
+
Updates LRU tracking (moves item to "most recently used").
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
key: Cache key (e.g., "feature:feat-001")
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Cached value if found and not expired, None otherwise
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
CacheKeyError: If key format is invalid
|
|
142
|
+
|
|
143
|
+
Performance: O(1) average case
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
>>> cache = get_shared_cache()
|
|
147
|
+
>>> value = cache.get("feature:feat-001")
|
|
148
|
+
>>> if value is None:
|
|
149
|
+
... # Recompute and cache
|
|
150
|
+
... value = load_feature("feat-001")
|
|
151
|
+
... cache.set("feature:feat-001", value)
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def get_or_compute(
|
|
157
|
+
self, key: str, compute_fn: Callable[[], Any], ttl: int | None = None
|
|
158
|
+
) -> Any:
|
|
159
|
+
"""
|
|
160
|
+
Get cached value or compute and cache if missing.
|
|
161
|
+
|
|
162
|
+
Avoids cache-miss pattern boilerplate:
|
|
163
|
+
cache.get() → check None → compute → cache.set()
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
key: Cache key
|
|
167
|
+
compute_fn: Function taking no args, returns value to cache
|
|
168
|
+
ttl: Time-to-live in seconds (None = use default)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Cached or newly computed value
|
|
172
|
+
|
|
173
|
+
Performance: O(1) if cached, O(?) if computed
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> def load_deps():
|
|
177
|
+
... return analyze_dependencies("feat-001")
|
|
178
|
+
>>> deps = cache.get_or_compute("dependency:feat-001", load_deps)
|
|
179
|
+
"""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def exists(self, key: str) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Check if key exists in cache.
|
|
186
|
+
|
|
187
|
+
Does NOT count as access for LRU purposes.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
key: Cache key to check
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if key exists and not expired, False otherwise
|
|
194
|
+
|
|
195
|
+
Performance: O(1)
|
|
196
|
+
|
|
197
|
+
Examples:
|
|
198
|
+
>>> if cache.exists("feature:feat-001"):
|
|
199
|
+
... print("Already cached")
|
|
200
|
+
"""
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
# ===== SET OPERATIONS =====
|
|
204
|
+
|
|
205
|
+
@abstractmethod
|
|
206
|
+
def set(self, key: str, value: Any, ttl: int | None = None) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Cache a value with optional time-to-live.
|
|
209
|
+
|
|
210
|
+
If key exists, updates value and TTL.
|
|
211
|
+
If cache at max capacity, evicts LRU item.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
key: Cache key (e.g., "feature:feat-001")
|
|
215
|
+
value: Value to cache (must be serializable)
|
|
216
|
+
ttl: Time-to-live in seconds (None = default)
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
CacheKeyError: If key format invalid
|
|
220
|
+
CacheCapacityError: If can't make space
|
|
221
|
+
|
|
222
|
+
Performance: O(1) average case
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
>>> feature = load_feature("feat-001")
|
|
226
|
+
>>> cache.set("feature:feat-001", feature, ttl=3600) # 1 hour
|
|
227
|
+
>>> cache.set("priority:feat-001", 0.95) # Use default TTL
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def set_many(self, items: dict[str, Any], ttl: int | None = None) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Cache multiple key-value pairs at once.
|
|
235
|
+
|
|
236
|
+
More efficient than multiple set() calls.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
items: Dict of key -> value to cache
|
|
240
|
+
ttl: TTL for all items (None = default)
|
|
241
|
+
|
|
242
|
+
Performance: O(k) where k = items count
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
>>> cache.set_many({
|
|
246
|
+
... "feature:feat-001": feature1,
|
|
247
|
+
... "feature:feat-002": feature2,
|
|
248
|
+
... "feature:feat-003": feature3,
|
|
249
|
+
... }, ttl=3600)
|
|
250
|
+
"""
|
|
251
|
+
...
|
|
252
|
+
|
|
253
|
+
# ===== DELETE OPERATIONS =====
|
|
254
|
+
|
|
255
|
+
@abstractmethod
|
|
256
|
+
def delete(self, key: str) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Delete single cached value.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
key: Cache key to delete
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
True if deleted, False if not found
|
|
265
|
+
|
|
266
|
+
Performance: O(1)
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
>>> cache.delete("feature:feat-001")
|
|
270
|
+
"""
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
@abstractmethod
|
|
274
|
+
def delete_pattern(self, pattern: str) -> int:
|
|
275
|
+
"""
|
|
276
|
+
Delete all cached values matching pattern.
|
|
277
|
+
|
|
278
|
+
Patterns use prefix matching:
|
|
279
|
+
- "feature:*" → deletes all keys starting with "feature:"
|
|
280
|
+
- "dependency:feat-*" → deletes "dependency:feat-001", etc.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
pattern: Prefix pattern to match (e.g., "feature:*")
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Number of keys deleted
|
|
287
|
+
|
|
288
|
+
Performance: O(n) where n = matching keys
|
|
289
|
+
|
|
290
|
+
Examples:
|
|
291
|
+
>>> # Invalidate all feature cache when features change
|
|
292
|
+
>>> count = cache.delete_pattern("feature:*")
|
|
293
|
+
>>> print(f"Invalidated {count} cached features")
|
|
294
|
+
|
|
295
|
+
>>> # Invalidate all analytics when dependencies change
|
|
296
|
+
>>> cache.delete_pattern("dependency:*")
|
|
297
|
+
>>> cache.delete_pattern("priority:*")
|
|
298
|
+
>>> cache.delete_pattern("recommendation:*")
|
|
299
|
+
"""
|
|
300
|
+
...
|
|
301
|
+
|
|
302
|
+
@abstractmethod
|
|
303
|
+
def clear(self) -> int:
|
|
304
|
+
"""
|
|
305
|
+
Clear all cached values.
|
|
306
|
+
|
|
307
|
+
Args: None
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Number of items cleared
|
|
311
|
+
|
|
312
|
+
Examples:
|
|
313
|
+
>>> cache.clear()
|
|
314
|
+
"""
|
|
315
|
+
...
|
|
316
|
+
|
|
317
|
+
# ===== BATCH OPERATIONS =====
|
|
318
|
+
|
|
319
|
+
@abstractmethod
|
|
320
|
+
def get_many(self, keys: list[str]) -> dict[str, Any]:
|
|
321
|
+
"""
|
|
322
|
+
Retrieve multiple cached values at once.
|
|
323
|
+
|
|
324
|
+
Returns only found keys (missing keys skipped).
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
keys: List of cache keys
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dict of key -> value for found items
|
|
331
|
+
|
|
332
|
+
Performance: O(k) where k = keys count
|
|
333
|
+
|
|
334
|
+
Examples:
|
|
335
|
+
>>> values = cache.get_many([
|
|
336
|
+
... "feature:feat-001",
|
|
337
|
+
... "feature:feat-002",
|
|
338
|
+
... "feature:feat-003",
|
|
339
|
+
... ])
|
|
340
|
+
>>> for key, value in values.items():
|
|
341
|
+
... print(f"{key}: {value}")
|
|
342
|
+
"""
|
|
343
|
+
...
|
|
344
|
+
|
|
345
|
+
@abstractmethod
|
|
346
|
+
def delete_many(self, keys: list[str]) -> int:
|
|
347
|
+
"""
|
|
348
|
+
Delete multiple cached values at once.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
keys: List of cache keys to delete
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Number of keys successfully deleted
|
|
355
|
+
|
|
356
|
+
Performance: O(k) where k = keys count
|
|
357
|
+
|
|
358
|
+
Examples:
|
|
359
|
+
>>> count = cache.delete_many([
|
|
360
|
+
... "feature:feat-001",
|
|
361
|
+
... "feature:feat-002",
|
|
362
|
+
... ])
|
|
363
|
+
"""
|
|
364
|
+
...
|
|
365
|
+
|
|
366
|
+
# ===== INVALIDATION HELPERS =====
|
|
367
|
+
|
|
368
|
+
@abstractmethod
|
|
369
|
+
def invalidate_feature(self, feature_id: str) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Invalidate all caches related to a feature.
|
|
372
|
+
|
|
373
|
+
Convenience method that invalidates:
|
|
374
|
+
- Feature data: "feature:{id}"
|
|
375
|
+
- Feature lists: "feature:list:*"
|
|
376
|
+
- Dependencies: "dependency:{id}" + "dependency:*:blocking_for_{id}"
|
|
377
|
+
- Analytics: "priority:{id}", "recommendation:*"
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
feature_id: Feature ID to invalidate
|
|
381
|
+
|
|
382
|
+
Examples:
|
|
383
|
+
>>> # When feature changes
|
|
384
|
+
>>> cache.invalidate_feature("feat-001")
|
|
385
|
+
"""
|
|
386
|
+
...
|
|
387
|
+
|
|
388
|
+
@abstractmethod
|
|
389
|
+
def invalidate_track(self, track_id: str) -> None:
|
|
390
|
+
"""
|
|
391
|
+
Invalidate all caches related to a track.
|
|
392
|
+
|
|
393
|
+
Invalidates:
|
|
394
|
+
- Track data: "track:{id}"
|
|
395
|
+
- Track features: "track:{id}:features"
|
|
396
|
+
- Analytics: cascades to feature invalidation
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
track_id: Track ID to invalidate
|
|
400
|
+
|
|
401
|
+
Examples:
|
|
402
|
+
>>> cache.invalidate_track("track-planning")
|
|
403
|
+
"""
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
@abstractmethod
|
|
407
|
+
def invalidate_analytics(self) -> None:
|
|
408
|
+
"""
|
|
409
|
+
Invalidate all analytics caches.
|
|
410
|
+
|
|
411
|
+
Used when dependencies change or major data update occurs.
|
|
412
|
+
|
|
413
|
+
Invalidates:
|
|
414
|
+
- "dependency:*"
|
|
415
|
+
- "priority:*"
|
|
416
|
+
- "recommendation:*"
|
|
417
|
+
- "critical_path:*"
|
|
418
|
+
- "blocking:*"
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
>>> # When dependencies added/removed
|
|
422
|
+
>>> cache.invalidate_analytics()
|
|
423
|
+
"""
|
|
424
|
+
...
|
|
425
|
+
|
|
426
|
+
# ===== OBSERVABILITY =====
|
|
427
|
+
|
|
428
|
+
@abstractmethod
|
|
429
|
+
def size(self) -> int:
|
|
430
|
+
"""
|
|
431
|
+
Get current number of cached items.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Count of items in cache
|
|
435
|
+
|
|
436
|
+
Performance: O(1)
|
|
437
|
+
|
|
438
|
+
Examples:
|
|
439
|
+
>>> print(f"Cache has {cache.size()} items")
|
|
440
|
+
"""
|
|
441
|
+
...
|
|
442
|
+
|
|
443
|
+
@abstractmethod
|
|
444
|
+
def stats(self) -> dict[str, Any]:
|
|
445
|
+
"""
|
|
446
|
+
Get detailed cache statistics.
|
|
447
|
+
|
|
448
|
+
Returns dict with:
|
|
449
|
+
- hits: Total cache hits
|
|
450
|
+
- misses: Total cache misses
|
|
451
|
+
- hit_rate: Hit rate (0-1)
|
|
452
|
+
- evictions: Number of items evicted
|
|
453
|
+
- size: Current item count
|
|
454
|
+
- capacity: Max item count
|
|
455
|
+
- memory_bytes: Approximate memory usage
|
|
456
|
+
- avg_load_ms: Average load time for computed values
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Dict with cache metrics
|
|
460
|
+
|
|
461
|
+
Performance: O(1)
|
|
462
|
+
|
|
463
|
+
Examples:
|
|
464
|
+
>>> metrics = cache.stats()
|
|
465
|
+
>>> print(f"Hit rate: {metrics['hit_rate']:.1%}")
|
|
466
|
+
>>> print(f"Evictions: {metrics['evictions']}")
|
|
467
|
+
>>> print(f"Memory: {metrics['memory_bytes'] / 1024:.1f} KB")
|
|
468
|
+
"""
|
|
469
|
+
...
|
|
470
|
+
|
|
471
|
+
@abstractmethod
|
|
472
|
+
def reset_stats(self) -> None:
|
|
473
|
+
"""
|
|
474
|
+
Reset cache statistics to zero.
|
|
475
|
+
|
|
476
|
+
Clears hit/miss counters but keeps cached data.
|
|
477
|
+
|
|
478
|
+
Examples:
|
|
479
|
+
>>> cache.reset_stats()
|
|
480
|
+
"""
|
|
481
|
+
...
|
|
482
|
+
|
|
483
|
+
# ===== CONFIGURATION =====
|
|
484
|
+
|
|
485
|
+
@abstractmethod
|
|
486
|
+
def configure(
|
|
487
|
+
self,
|
|
488
|
+
max_size: int | None = None,
|
|
489
|
+
default_ttl: int | None = None,
|
|
490
|
+
metrics_enabled: bool | None = None,
|
|
491
|
+
) -> None:
|
|
492
|
+
"""
|
|
493
|
+
Configure cache behavior.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
max_size: Max cached items (default: 1000)
|
|
497
|
+
default_ttl: Default time-to-live in seconds (default: 3600)
|
|
498
|
+
metrics_enabled: Track stats automatically (default: True)
|
|
499
|
+
|
|
500
|
+
Examples:
|
|
501
|
+
>>> cache.configure(max_size=5000, default_ttl=7200)
|
|
502
|
+
"""
|
|
503
|
+
...
|
|
504
|
+
|
|
505
|
+
@abstractmethod
|
|
506
|
+
def is_configured(self) -> bool:
|
|
507
|
+
"""
|
|
508
|
+
Check if cache is properly configured.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
True if cache is ready to use
|
|
512
|
+
|
|
513
|
+
Examples:
|
|
514
|
+
>>> if not cache.is_configured():
|
|
515
|
+
... cache.configure(max_size=1000)
|
|
516
|
+
"""
|
|
517
|
+
...
|
|
518
|
+
|
|
519
|
+
# ===== DEBUG / UTILITY =====
|
|
520
|
+
|
|
521
|
+
@abstractmethod
|
|
522
|
+
def debug_info(self) -> dict[str, Any]:
|
|
523
|
+
"""
|
|
524
|
+
Get detailed debug information.
|
|
525
|
+
|
|
526
|
+
Includes:
|
|
527
|
+
- All cached keys
|
|
528
|
+
- TTL for each key
|
|
529
|
+
- Access count for each key
|
|
530
|
+
- LRU order
|
|
531
|
+
- Memory per item
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Dict with detailed cache state
|
|
535
|
+
|
|
536
|
+
Examples:
|
|
537
|
+
>>> info = cache.debug_info()
|
|
538
|
+
>>> for key in info['keys']:
|
|
539
|
+
... print(f"{key}: {info['memory'][key]} bytes")
|
|
540
|
+
"""
|
|
541
|
+
...
|
|
542
|
+
|
|
543
|
+
@abstractmethod
|
|
544
|
+
def validate_integrity(self) -> bool:
|
|
545
|
+
"""
|
|
546
|
+
Validate cache internal consistency.
|
|
547
|
+
|
|
548
|
+
Checks:
|
|
549
|
+
- No expired items accessible
|
|
550
|
+
- LRU order correct
|
|
551
|
+
- Size tracking accurate
|
|
552
|
+
- All items parseable
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
True if cache valid, False if corrupted
|
|
556
|
+
|
|
557
|
+
Examples:
|
|
558
|
+
>>> if not cache.validate_integrity():
|
|
559
|
+
... cache.clear() # Rebuild
|
|
560
|
+
... cache.reload()
|
|
561
|
+
"""
|
|
562
|
+
...
|
|
563
|
+
|
|
564
|
+
# ===== SINGLETON MANAGEMENT =====
|
|
565
|
+
|
|
566
|
+
@classmethod
|
|
567
|
+
@abstractmethod
|
|
568
|
+
def initialize(
|
|
569
|
+
cls, max_size: int = 1000, default_ttl: int = 3600, metrics_enabled: bool = True
|
|
570
|
+
) -> "SharedCache":
|
|
571
|
+
"""
|
|
572
|
+
Initialize singleton cache instance.
|
|
573
|
+
|
|
574
|
+
Must be called once at application startup.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
max_size: Max cached items
|
|
578
|
+
default_ttl: Default time-to-live
|
|
579
|
+
metrics_enabled: Enable statistics tracking
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Initialized SharedCache singleton
|
|
583
|
+
|
|
584
|
+
Examples:
|
|
585
|
+
>>> cache = SharedCache.initialize(max_size=5000)
|
|
586
|
+
>>> # Later, get singleton:
|
|
587
|
+
>>> cache = get_shared_cache()
|
|
588
|
+
"""
|
|
589
|
+
...
|
|
590
|
+
|
|
591
|
+
@classmethod
|
|
592
|
+
@abstractmethod
|
|
593
|
+
def get_instance(cls) -> "SharedCache":
|
|
594
|
+
"""
|
|
595
|
+
Get singleton instance.
|
|
596
|
+
|
|
597
|
+
Must call initialize() once before calling this.
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
SharedCache singleton
|
|
601
|
+
|
|
602
|
+
Raises:
|
|
603
|
+
RuntimeError: If not initialized
|
|
604
|
+
|
|
605
|
+
Examples:
|
|
606
|
+
>>> cache = SharedCache.get_instance()
|
|
607
|
+
"""
|
|
608
|
+
...
|
|
609
|
+
|
|
610
|
+
@classmethod
|
|
611
|
+
@abstractmethod
|
|
612
|
+
def reset_instance(cls) -> None:
|
|
613
|
+
"""
|
|
614
|
+
Reset singleton (for testing only).
|
|
615
|
+
|
|
616
|
+
Clears instance and forces re-initialization next time.
|
|
617
|
+
|
|
618
|
+
Examples:
|
|
619
|
+
>>> SharedCache.reset_instance() # Testing only
|
|
620
|
+
"""
|
|
621
|
+
...
|