loomweave-plugin-python 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.
@@ -0,0 +1,3 @@
1
+ """loomweave-plugin-python — Python language plugin for Loomweave."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,15 @@
1
+ """Entry point for the ``loomweave-plugin-python`` executable.
2
+
3
+ Installs stdout discipline (``stdout_guard``) and hands control to the
4
+ JSON-RPC server loop. ``sys.exit`` threads the server's exit code out to
5
+ the host process.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from loomweave_plugin_python.server import main
13
+
14
+ if __name__ == "__main__":
15
+ sys.exit(main())
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Literal, NotRequired, Protocol, TypedDict
5
+
6
+ if TYPE_CHECKING:
7
+ from collections.abc import Sequence
8
+ from pathlib import Path
9
+
10
+
11
+ class CallsEdgeProperties(TypedDict):
12
+ candidates: list[str]
13
+
14
+
15
+ class CallsRawEdge(TypedDict):
16
+ kind: Literal["calls"]
17
+ from_id: str
18
+ to_id: str
19
+ source_byte_start: int
20
+ source_byte_end: int
21
+ confidence: Literal["resolved", "ambiguous"]
22
+ properties: NotRequired[CallsEdgeProperties]
23
+
24
+
25
+ class Finding(TypedDict):
26
+ subcode: str
27
+ severity: Literal["info", "warning", "error"]
28
+ message: str
29
+ metadata: dict[str, object]
30
+
31
+
32
+ class UnresolvedCallSite(TypedDict):
33
+ caller_entity_id: str
34
+ site_ordinal: int
35
+ source_byte_start: int
36
+ source_byte_end: int
37
+ callee_expr: str
38
+
39
+
40
+ @dataclass
41
+ class CallResolutionResult:
42
+ edges: list[CallsRawEdge] = field(default_factory=list)
43
+ unresolved_call_sites_total: int = 0
44
+ unresolved_call_sites: list[UnresolvedCallSite] = field(default_factory=list)
45
+ pyright_query_latency_ms: list[int] = field(default_factory=list)
46
+ pyright_index_parse_latency_ms: list[int] = field(default_factory=list)
47
+ findings: list[Finding] = field(default_factory=list)
48
+
49
+
50
+ class CallResolver(Protocol):
51
+ def resolve_calls(
52
+ self,
53
+ file_path: str | Path,
54
+ function_ids: Sequence[str],
55
+ ) -> CallResolutionResult: ...
56
+
57
+
58
+ class NoOpCallResolver:
59
+ def resolve_calls(
60
+ self,
61
+ file_path: str | Path,
62
+ function_ids: Sequence[str],
63
+ ) -> CallResolutionResult:
64
+ _ = (file_path, function_ids)
65
+ return CallResolutionResult()
@@ -0,0 +1,75 @@
1
+ """L2 3-segment EntityId assembler matching WP1's Rust ``entity_id()`` byte-for-byte.
2
+
3
+ Per ADR-003 + ADR-022, every Loomweave entity has a 3-segment ID of the
4
+ form ``{plugin_id}:{kind}:{canonical_qualified_name}``.
5
+
6
+ Validation (mirrors ``crates/loomweave-core/src/entity_id.rs``):
7
+
8
+ - ``plugin_id`` and ``kind`` must match the identifier grammar
9
+ ``[a-z][a-z0-9_]*`` (ADR-022).
10
+ - No segment may contain a literal ``:`` (reserved separator).
11
+ - No segment may be empty.
12
+
13
+ The shared fixture ``fixtures/entity_id.json`` (Task 5) drives the
14
+ cross-language parity check: both the Rust assembler and this Python
15
+ assembler consume the same fixture rows and must produce identical
16
+ strings byte-for-byte.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import re
22
+
23
+ _GRAMMAR = re.compile(r"^[a-z][a-z0-9_]*$")
24
+
25
+
26
+ class EntityIdError(ValueError):
27
+ """Base class for all ``entity_id()`` validation failures."""
28
+
29
+
30
+ class EmptySegmentError(EntityIdError):
31
+ """A segment (``plugin_id``, ``kind``, or ``canonical_qualified_name``) was empty."""
32
+
33
+ def __init__(self, field: str) -> None:
34
+ super().__init__(f"segment {field} empty")
35
+ self.field = field
36
+
37
+
38
+ class GrammarViolationError(EntityIdError):
39
+ """A segment did not match the ADR-022 grammar ``[a-z][a-z0-9_]*``."""
40
+
41
+ def __init__(self, field: str, value: str) -> None:
42
+ super().__init__(f"segment {field} violates ADR-022 grammar [a-z][a-z0-9_]*: {value!r}")
43
+ self.field = field
44
+ self.value = value
45
+
46
+
47
+ class SegmentContainsColonError(EntityIdError):
48
+ """A segment contained the reserved ``:`` separator (UQ-WP1-07)."""
49
+
50
+ def __init__(self, field: str, value: str) -> None:
51
+ super().__init__(f"segment {field} contains reserved ':' separator: {value!r}")
52
+ self.field = field
53
+ self.value = value
54
+
55
+
56
+ def _validate_grammar(field: str, value: str) -> None:
57
+ """Mirror ``validate_grammar`` in the Rust side — empty, colon, then regex."""
58
+ if not value:
59
+ raise EmptySegmentError(field)
60
+ if ":" in value:
61
+ raise SegmentContainsColonError(field, value)
62
+ if not _GRAMMAR.fullmatch(value):
63
+ raise GrammarViolationError(field, value)
64
+
65
+
66
+ def entity_id(plugin_id: str, kind: str, canonical_qualified_name: str) -> str:
67
+ """Assemble the 3-segment EntityId string with full validation."""
68
+ _validate_grammar("plugin_id", plugin_id)
69
+ _validate_grammar("kind", kind)
70
+ qn_field = "canonical_qualified_name"
71
+ if not canonical_qualified_name:
72
+ raise EmptySegmentError(qn_field)
73
+ if ":" in canonical_qualified_name:
74
+ raise SegmentContainsColonError(qn_field, canonical_qualified_name)
75
+ return f"{plugin_id}:{kind}:{canonical_qualified_name}"