File size: 6,733 Bytes
33a4fd2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d231444
33a4fd2
 
d231444
 
33a4fd2
d231444
 
33a4fd2
 
 
 
d231444
 
 
 
 
 
 
 
 
 
 
 
 
 
33a4fd2
 
 
 
d231444
 
 
 
 
33a4fd2
 
 
 
d231444
 
 
 
 
33a4fd2
 
 
 
 
 
d231444
33a4fd2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d231444
33a4fd2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d231444
33a4fd2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""API Surface Intelligence data models.

Uses Python enums for all categorical values to eliminate raw-string bugs
and make exhaustive-match checking possible in tests.

Kept in a dedicated module following the existing convention:
  models/churn.py, models/call_graph.py, models/symbol.py, etc.
"""

from __future__ import annotations

from enum import Enum
from typing import Dict, List, Optional
from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Enumerations
# ---------------------------------------------------------------------------


class Visibility(str, Enum):
    """How broadly a symbol is accessible."""

    PUBLIC = "public"  # exported / part of the module's public interface
    INTERNAL = "internal"  # used within the package but not exported
    PRIVATE = "private"  # convention-private (underscore prefix, etc.)
    UNKNOWN = "unknown"  # could not be determined with sufficient confidence


class ApiKind(str, Enum):
    """What role the API plays in the system."""

    EXPORTED = "exported"  # explicit export statement (JS/TS)
    ROUTE = "route"  # HTTP route handler (FastAPI, Flask, Express)
    CLI_ENTRY = "cli_entry"  # CLI command entry point
    MAIN_ENTRY = "main_entry"  # __main__ or if __name__ == '__main__'
    PUBLIC_CLASS = "public_class"  # exported / non-private class
    PUBLIC_FUNCTION = "public_function"  # exported / non-private top-level function
    PUBLIC_METHOD = "public_method"  # non-private method of a public class
    INTERFACE = "interface"  # TypeScript interface
    ENUM_TYPE = "enum_type"  # TypeScript / Python enum
    INTERNAL_HELPER = (
        "internal_helper"  # internal function/method not part of public surface
    )
    UNKNOWN = "unknown"


class ApiStatus(str, Enum):
    """Lifecycle status of an API."""

    STABLE = "stable"
    DEPRECATED = "deprecated"  # annotated as deprecated
    EXPERIMENTAL = "experimental"  # annotated as experimental / beta
    UNKNOWN = "unknown"


class BreakingChangeKind(str, Enum):
    """Type of breaking change detected."""

    REMOVED_EXPORT = "removed_export"  # public symbol removed entirely
    RENAMED_EXPORT = "renamed_export"  # likely rename (remove + add with similar name)
    SIGNATURE_CHANGED = "signature_changed"  # param count changed
    VISIBILITY_REDUCED = "visibility_reduced"  # was public, now private/internal


# ---------------------------------------------------------------------------
# Core classification unit
# ---------------------------------------------------------------------------


class ClassifiedSymbol(BaseModel):
    """A symbol from the Symbol Index enriched with API surface metadata.

    Attributes:
        name:                  Symbol name.
        qualified:             Dot-qualified name (Class.method or function).
        symbol_type:           function | class | method | interface | enum.
        file_path:             Relative file path.
        line_number:           1-indexed definition line.
        language:              Source language.
        parent_class:          Enclosing class for methods.
        visibility:            PUBLIC | INTERNAL | PRIVATE | UNKNOWN.
        api_kind:              Categorical role.
        status:                STABLE | DEPRECATED | EXPERIMENTAL | UNKNOWN.
        confidence:            0.0–1.0 classification confidence.
        classification_reason: Human-readable explanation of the classification.
        param_count:           Number of formal parameters (for breaking-change detection).
        is_async:              True if the function/method is declared async.
        decorators:            Decorator names found on this symbol (best-effort).
        fan_in:                Number of distinct callers from the call graph (0 if unavailable).
        is_orphan:             True when visibility=PUBLIC but fan_in=0 (unused public API).
    """

    name: str
    qualified: str
    symbol_type: str
    file_path: str
    line_number: int = 1
    language: str = "unknown"
    parent_class: Optional[str] = None

    visibility: Visibility = Visibility.UNKNOWN
    api_kind: ApiKind = ApiKind.UNKNOWN
    status: ApiStatus = ApiStatus.UNKNOWN
    confidence: float = Field(0.0, ge=0.0, le=1.0)
    classification_reason: str = ""

    param_count: int = 0
    is_async: bool = False
    decorators: List[str] = Field(default_factory=list)

    fan_in: int = 0
    is_orphan: bool = False


# ---------------------------------------------------------------------------
# Breaking change record
# ---------------------------------------------------------------------------


class BreakingChange(BaseModel):
    """A single breaking API change between two surface snapshots.

    Attributes:
        kind:                Type of change.
        symbol_name:         Affected symbol name.
        file_path:           File path (from the before snapshot, or after for additions).
        before_param_count:  Parameter count before the change (None if not applicable).
        after_param_count:   Parameter count after the change.
        severity:            "high" | "medium" | "low".
        description:         Human-readable explanation.
    """

    kind: BreakingChangeKind
    symbol_name: str
    file_path: str = ""
    before_param_count: Optional[int] = None
    after_param_count: Optional[int] = None
    severity: str = "high"
    description: str = ""


# ---------------------------------------------------------------------------
# Summary / persisted report
# ---------------------------------------------------------------------------


class APISurfaceStats(BaseModel):
    """Aggregate statistics for one API surface report."""

    total_symbols: int = 0
    public_count: int = 0
    internal_count: int = 0
    private_count: int = 0
    unknown_count: int = 0
    deprecated_count: int = 0
    experimental_count: int = 0
    route_count: int = 0
    entry_point_count: int = 0
    orphan_public_count: int = 0
    by_language: Dict[str, int] = Field(default_factory=dict)


class APISurface(BaseModel):
    """Full API surface report for a repository.

    Written to data/api_surface/{owner}_{repo}.json by APISurfaceService.

    Attributes:
        repo:         Repository identifier (owner/repo).
        generated_at: ISO-8601 UTC timestamp.
        symbols:      All classified symbols.
        stats:        Aggregate statistics.
        warning:      Optional diagnostic message.
    """

    repo: str
    generated_at: str
    symbols: List[ClassifiedSymbol] = Field(default_factory=list)
    stats: APISurfaceStats = Field(default_factory=APISurfaceStats)
    warning: Optional[str] = None