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 +70 -0
- awgraph/base.py +139 -0
- awgraph/degradation.py +48 -0
- awgraph/graph.py +4866 -0
- awgraph/logging.py +11 -0
- awgraph/registry.py +780 -0
- awgraph/store.py +658 -0
- awgraph-1.0.0.dist-info/METADATA +141 -0
- awgraph-1.0.0.dist-info/RECORD +12 -0
- awgraph-1.0.0.dist-info/WHEEL +5 -0
- awgraph-1.0.0.dist-info/licenses/LICENSE +202 -0
- awgraph-1.0.0.dist-info/top_level.txt +1 -0
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
|