awgraph 1.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.
awgraph/__init__.py ADDED
@@ -0,0 +1,70 @@
1
+ """awgraph — Semantic Python Code Graph.
2
+
3
+ A lightweight, portable Python AST indexer with call graphs, embeddings support,
4
+ and hybrid keyword+semantic search. Originally developed as part of AitherOS's
5
+ CodeGraph faculty, now available as a public package.
6
+
7
+ ## What it does
8
+
9
+ - Real AST parsing (not regex)
10
+ - Extracts functions, classes, and methods into chunks
11
+ - Builds call graphs (what calls what, what's called by what)
12
+ - Hybrid search: keyword (FTS5) + semantic (embeddings)
13
+ - SQLite storage with WAL mode for concurrent access
14
+ - Optional NumPy for fast vector operations
15
+
16
+ ## What it doesn't do
17
+
18
+ - PDFs, web pages, or "universal" anything — just Python code
19
+ - Embeddings by default — bring your own or use plugin hooks
20
+ - Network access by default — embeddings API must be provided externally
21
+ - Persistence across instances — in-memory by design; SQLite for durability
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ from awgraph import CodeGraph
27
+
28
+ # Create an indexer
29
+ graph = CodeGraph()
30
+
31
+ # Index a directory
32
+ await graph.index_codebase("/path/to/code")
33
+
34
+ # Query
35
+ chunks = await graph.query("rate limiter", limit=10)
36
+
37
+ for chunk in chunks:
38
+ print(f"{chunk.name}: {chunk.calls}")
39
+ ```
40
+
41
+ ## Architecture
42
+
43
+ - **graph.py**: CodeGraph engine, AST parsing, call graphs, chunking
44
+ - **store.py**: SQLite+FTS5 backend for storage and search
45
+ - **registry.py**: Multi-root manager for indexing external repos
46
+ - **base.py**: Minimal faculty graph base class
47
+ - **logging.py**: Standard Python logging shim
48
+ - **degradation.py**: Optional dependency tracking
49
+
50
+ Plugin support via awgraph.plugins (not yet public).
51
+ """
52
+
53
+ from __future__ import annotations
54
+
55
+ from awgraph.graph import CodeGraph, CodeChunk
56
+ from awgraph.registry import CodeGraphRegistry, get_codegraph_registry
57
+ from awgraph.store import CodeGraphStore
58
+ from awgraph.base import BaseFacultyGraph, GraphSyncConfig
59
+
60
+ __version__ = "1.0.0"
61
+
62
+ __all__ = [
63
+ "CodeGraph",
64
+ "CodeChunk",
65
+ "CodeGraphRegistry",
66
+ "CodeGraphStore",
67
+ "BaseFacultyGraph",
68
+ "GraphSyncConfig",
69
+ "get_codegraph_registry",
70
+ ]
awgraph/base.py ADDED
@@ -0,0 +1,139 @@
1
+ """
2
+ Minimal base class for awgraph.
3
+
4
+ Provides the core interface a faculty graph implements, without the AitherOS
5
+ integrations (sync, provenance, integrity). This is what the public package
6
+ exposes.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Any, Dict, List
13
+
14
+
15
+ @dataclass
16
+ class GraphSyncConfig:
17
+ """Stub for compatibility; sync is not available in the public package."""
18
+ enabled: bool = False
19
+ domain: str = ""
20
+ batch_size: int = 20
21
+ flush_interval: float = 5.0
22
+ source_graph: str = ""
23
+ provenance: bool = False
24
+ provenance_node_type: str = "claim"
25
+
26
+
27
+ class BaseFacultyGraph:
28
+ """
29
+ Minimal base class for a faculty graph.
30
+
31
+ In AitherOS, this also provides sync to AitherKnowledgeGraph, provenance
32
+ emission, and integrity tracking. The public package drops those features,
33
+ keeping only the core interface: query(), stats(), and graph_query().
34
+ """
35
+
36
+ _sync_config: GraphSyncConfig = GraphSyncConfig()
37
+ _scope_level: str = "platform"
38
+
39
+ def __init__(self):
40
+ self._sync_config = GraphSyncConfig()
41
+
42
+ def _queue_sync(self, node_data: Dict[str, Any], tenant_id: str = "platform") -> None:
43
+ """No-op: sync to AitherKnowledgeGraph is not available in public package."""
44
+ pass
45
+
46
+ def _queue_deletion(self, node_id: str, tenant_id: str = "platform") -> None:
47
+ """No-op: sync is not available in public package."""
48
+ pass
49
+
50
+ # SYNC on purpose: every call site is `self._flush_to_bus()` with no await.
51
+ # Declaring it async made each call return a coroutine nobody awaits, which
52
+ # Python reports only as a RuntimeWarning — so the no-op would have looked
53
+ # like it ran while doing nothing, in a package whose whole promise is that
54
+ # the stub is inert rather than silently broken.
55
+ def _flush_to_bus(self, *args: Any, **kwargs: Any) -> None:
56
+ """No-op: there is no event bus in the standalone package.
57
+
58
+ This one is not decoration. Indexing calls it, so omitting it made the
59
+ package import cleanly and then die with AttributeError on the FIRST
60
+ index_codebase() call — the shape where a vendored package looks fine to
61
+ every import check and is broken for the person who pip-installed it.
62
+ An import test cannot catch this; only actually indexing something can.
63
+
64
+ Subclasses in a host application may override it to re-attach their own
65
+ event plumbing; nothing here needs to know that they exist.
66
+ """
67
+ return None
68
+
69
+ def _emit_provenance(
70
+ self,
71
+ node_id: str,
72
+ name: str,
73
+ properties: Dict[str, Any],
74
+ tenant_id: str = "platform",
75
+ ) -> None:
76
+ """No-op: provenance emission is not available in public package."""
77
+ pass
78
+
79
+ def graph_stats(self) -> Dict[str, Any]:
80
+ """Inventory: how many nodes does this graph hold?
81
+
82
+ Subclasses may override. Default derives counts from public attributes.
83
+ """
84
+ entities: Dict[str, int] = {}
85
+ for name, val in vars(self).items():
86
+ if name.startswith("_") or name.startswith("by_"):
87
+ continue
88
+ if not isinstance(val, (dict, list, set, tuple)):
89
+ continue
90
+ entities[name] = len(val)
91
+ return {"nodes": sum(entities.values()), "containers": entities}
92
+
93
+ async def graph_query(
94
+ self, text: str, limit: int = 10,
95
+ ) -> List[Dict[str, Any]]:
96
+ """Uniform cross-graph query.
97
+
98
+ Adapts this graph's search() or query() method by parameter name.
99
+ Subclasses may override for a better native path.
100
+ """
101
+ import asyncio
102
+ import inspect
103
+
104
+ fn = getattr(self, "search", None) or getattr(self, "query", None)
105
+ if not callable(fn):
106
+ return []
107
+
108
+ try:
109
+ sig = inspect.signature(fn)
110
+ except (TypeError, ValueError):
111
+ return []
112
+
113
+ # Bind by parameter name (not position)
114
+ kwargs = {}
115
+ for p in sig.parameters.values():
116
+ if p.name in ("query", "query_str", "text", "prompt", "search"):
117
+ kwargs[p.name] = text
118
+ elif p.name in ("limit", "max_results", "top_k", "k"):
119
+ kwargs[p.name] = limit
120
+
121
+ # Call sync or async
122
+ try:
123
+ if asyncio.iscoroutinefunction(fn):
124
+ return await fn(**kwargs)
125
+ else:
126
+ loop = asyncio.get_event_loop()
127
+ return await loop.run_in_executor(None, fn, **kwargs)
128
+ except TypeError:
129
+ # Fall back to no kwargs if binding failed
130
+ try:
131
+ if asyncio.iscoroutinefunction(fn):
132
+ return await fn(text, limit)
133
+ else:
134
+ loop = asyncio.get_event_loop()
135
+ return await loop.run_in_executor(None, lambda: fn(text, limit))
136
+ except Exception:
137
+ return []
138
+ except Exception:
139
+ return []
awgraph/degradation.py ADDED
@@ -0,0 +1,48 @@
1
+ """DegradationRegistry shim for awgraph.
2
+
3
+ Replaces lib.core.DegradationRegistry with a no-op that tracks optional
4
+ dependencies gracefully.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import Any, Optional
9
+
10
+
11
+ class SubsystemTier(Enum):
12
+ """Severity of a subsystem failure."""
13
+ CORE = "core"
14
+ CRITICAL = "critical"
15
+ COGNITIVE = "cognitive"
16
+ AUXILIARY = "auxiliary"
17
+
18
+
19
+ class DegradationRegistry:
20
+ """Track optional dependency availability.
21
+
22
+ No-op in the public package; just silently tracks OK/failed states.
23
+ """
24
+
25
+ def __init__(self):
26
+ self._ok: dict[str, Any] = {}
27
+ self._failed: dict[str, Any] = {}
28
+
29
+ def register_ok(self, name: str, module: str, tier: SubsystemTier) -> None:
30
+ """Mark a subsystem as OK."""
31
+ self._ok[name] = {"module": module, "tier": tier}
32
+
33
+ def register_failed(
34
+ self, name: str, module: str, error: Exception, tier: SubsystemTier,
35
+ ) -> None:
36
+ """Mark a subsystem as failed."""
37
+ self._failed[name] = {"module": module, "error": str(error), "tier": tier}
38
+
39
+
40
+ _GLOBAL_REGISTRY: Optional[DegradationRegistry] = None
41
+
42
+
43
+ def get_registry() -> DegradationRegistry:
44
+ """Get the global degradation registry."""
45
+ global _GLOBAL_REGISTRY
46
+ if _GLOBAL_REGISTRY is None:
47
+ _GLOBAL_REGISTRY = DegradationRegistry()
48
+ return _GLOBAL_REGISTRY