apisec-code-bolt 0.1.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.
- apisec_code_bolt/__init__.py +42 -0
- apisec_code_bolt/__main__.py +11 -0
- apisec_code_bolt/analysis/__init__.py +96 -0
- apisec_code_bolt/analysis/analyzer.py +2309 -0
- apisec_code_bolt/analysis/binding_tracker.py +341 -0
- apisec_code_bolt/analysis/call_graph.py +1197 -0
- apisec_code_bolt/analysis/call_graph_types.py +332 -0
- apisec_code_bolt/analysis/call_resolver.py +988 -0
- apisec_code_bolt/analysis/capability_tagger.py +322 -0
- apisec_code_bolt/analysis/config_scanner.py +197 -0
- apisec_code_bolt/analysis/data_flow.py +1883 -0
- apisec_code_bolt/analysis/dependency_extractor.py +959 -0
- apisec_code_bolt/analysis/flow_analysis.py +1406 -0
- apisec_code_bolt/analysis/hof_catalog.py +61 -0
- apisec_code_bolt/analysis/integration_detector.py +1399 -0
- apisec_code_bolt/analysis/literal_scanner.py +300 -0
- apisec_code_bolt/analysis/path_normalizer.py +55 -0
- apisec_code_bolt/analysis/read_site_detector.py +310 -0
- apisec_code_bolt/analysis/request_patterns.py +162 -0
- apisec_code_bolt/analysis/sensitivity_classifier.py +224 -0
- apisec_code_bolt/analysis/sink_evidence.py +333 -0
- apisec_code_bolt/analysis/url_prefix_resolver.py +338 -0
- apisec_code_bolt/cli/__init__.py +5 -0
- apisec_code_bolt/cli/exit_codes.py +17 -0
- apisec_code_bolt/cli/main.py +1069 -0
- apisec_code_bolt/cloud/__init__.py +1 -0
- apisec_code_bolt/cloud/apisec_client.py +118 -0
- apisec_code_bolt/cloud/client.py +255 -0
- apisec_code_bolt/core/__init__.py +75 -0
- apisec_code_bolt/core/config.py +528 -0
- apisec_code_bolt/core/credentials.py +65 -0
- apisec_code_bolt/core/discovery.py +433 -0
- apisec_code_bolt/core/log_format.py +115 -0
- apisec_code_bolt/core/manifest.py +1009 -0
- apisec_code_bolt/core/repo.py +280 -0
- apisec_code_bolt/core/state.py +59 -0
- apisec_code_bolt/core/telemetry.py +451 -0
- apisec_code_bolt/core/types.py +587 -0
- apisec_code_bolt/fingerprinting/__init__.py +1 -0
- apisec_code_bolt/frameworks/__init__.py +29 -0
- apisec_code_bolt/frameworks/_jwt_common.py +50 -0
- apisec_code_bolt/frameworks/auth_helpers.py +437 -0
- apisec_code_bolt/frameworks/base.py +608 -0
- apisec_code_bolt/frameworks/dotnet/__init__.py +17 -0
- apisec_code_bolt/frameworks/dotnet/_path_helpers.py +43 -0
- apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +2546 -0
- apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +559 -0
- apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +545 -0
- apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +732 -0
- apisec_code_bolt/frameworks/dotnet/refit_plugin.py +374 -0
- apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +1239 -0
- apisec_code_bolt/frameworks/java/__init__.py +6 -0
- apisec_code_bolt/frameworks/java/_annotations.py +167 -0
- apisec_code_bolt/frameworks/java/_constraints.py +128 -0
- apisec_code_bolt/frameworks/java/graphql_plugin.py +287 -0
- apisec_code_bolt/frameworks/java/jaxrs_plugin.py +748 -0
- apisec_code_bolt/frameworks/java/jwt_config_extractor.py +361 -0
- apisec_code_bolt/frameworks/java/micronaut_plugin.py +1059 -0
- apisec_code_bolt/frameworks/java/spring_plugin.py +1293 -0
- apisec_code_bolt/frameworks/js/__init__.py +8 -0
- apisec_code_bolt/frameworks/js/express_plugin.py +391 -0
- apisec_code_bolt/frameworks/js/fastify_plugin.py +381 -0
- apisec_code_bolt/frameworks/js/graphql_plugin.py +198 -0
- apisec_code_bolt/frameworks/js/nestjs_plugin.py +423 -0
- apisec_code_bolt/frameworks/python/__init__.py +19 -0
- apisec_code_bolt/frameworks/python/celery_plugin.py +393 -0
- apisec_code_bolt/frameworks/python/click_plugin.py +427 -0
- apisec_code_bolt/frameworks/python/django_plugin.py +867 -0
- apisec_code_bolt/frameworks/python/fastapi/__init__.py +28 -0
- apisec_code_bolt/frameworks/python/fastapi/plugin.py +1390 -0
- apisec_code_bolt/frameworks/python/flask_plugin.py +205 -0
- apisec_code_bolt/frameworks/python/graphql_plugin.py +274 -0
- apisec_code_bolt/frameworks/python/prefect_plugin.py +251 -0
- apisec_code_bolt/frameworks/python/webhook_plugin.py +255 -0
- apisec_code_bolt/parsing/__init__.py +62 -0
- apisec_code_bolt/parsing/base.py +554 -0
- apisec_code_bolt/parsing/csharp/__init__.py +5 -0
- apisec_code_bolt/parsing/csharp/language_services.py +203 -0
- apisec_code_bolt/parsing/csharp/literals.py +72 -0
- apisec_code_bolt/parsing/csharp/parser.py +1158 -0
- apisec_code_bolt/parsing/csharp/type_resolver.py +568 -0
- apisec_code_bolt/parsing/js/__init__.py +5 -0
- apisec_code_bolt/parsing/js/language_services.py +118 -0
- apisec_code_bolt/parsing/js/parser.py +622 -0
- apisec_code_bolt/parsing/jvm/__init__.py +7 -0
- apisec_code_bolt/parsing/jvm/language_services.py +270 -0
- apisec_code_bolt/parsing/jvm/parser.py +774 -0
- apisec_code_bolt/parsing/jvm/type_resolver.py +422 -0
- apisec_code_bolt/parsing/python/__init__.py +150 -0
- apisec_code_bolt/parsing/python/cbv_extractor.py +606 -0
- apisec_code_bolt/parsing/python/constant_resolver.py +500 -0
- apisec_code_bolt/parsing/python/cross_file_resolver.py +1054 -0
- apisec_code_bolt/parsing/python/dynamic_route_detector.py +532 -0
- apisec_code_bolt/parsing/python/expression_utils.py +221 -0
- apisec_code_bolt/parsing/python/extraction_types.py +271 -0
- apisec_code_bolt/parsing/python/language_services.py +487 -0
- apisec_code_bolt/parsing/python/parameter_analyzer.py +789 -0
- apisec_code_bolt/parsing/python/parser.py +719 -0
- apisec_code_bolt/parsing/python/path_resolver.py +576 -0
- apisec_code_bolt/parsing/python/router_registry.py +806 -0
- apisec_code_bolt/parsing/python/type_resolver.py +730 -0
- apisec_code_bolt/parsing/python/visitors.py +1544 -0
- apisec_code_bolt/parsing/services.py +544 -0
- apisec_code_bolt/query/__init__.py +1 -0
- apisec_code_bolt/query/ast_cache.py +182 -0
- apisec_code_bolt/query/executor.py +283 -0
- apisec_code_bolt/query/handlers.py +832 -0
- apisec_code_bolt-0.1.0.dist-info/METADATA +230 -0
- apisec_code_bolt-0.1.0.dist-info/RECORD +111 -0
- apisec_code_bolt-0.1.0.dist-info/WHEEL +4 -0
- apisec_code_bolt-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cloud communication client for manifest upload and Query API."""
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""PAT-authenticated onboarding: register with applicationsservice, upload to reasoning engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .client import CloudClient
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class RegistrationResult:
|
|
19
|
+
"""Result of POST /v1/applications/surface."""
|
|
20
|
+
|
|
21
|
+
app_id: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RepoInfo:
|
|
26
|
+
"""Repository metadata for registration."""
|
|
27
|
+
|
|
28
|
+
repo_name: str
|
|
29
|
+
repo_url: str
|
|
30
|
+
branch: str
|
|
31
|
+
commit_sha: str
|
|
32
|
+
scm_provider: str
|
|
33
|
+
canonical_repo_id: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ApisecClient:
|
|
37
|
+
"""Registers the repo surface with applicationsservice (PAT), then talks to the reasoning engine with the same PAT."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
api_url: str,
|
|
42
|
+
pat: str,
|
|
43
|
+
timeout_seconds: int = 60,
|
|
44
|
+
verify_ssl: bool = True,
|
|
45
|
+
):
|
|
46
|
+
self._api_url = api_url.rstrip("/")
|
|
47
|
+
self._pat = pat
|
|
48
|
+
self._timeout = timeout_seconds
|
|
49
|
+
self._verify_ssl = verify_ssl
|
|
50
|
+
|
|
51
|
+
def register(
|
|
52
|
+
self,
|
|
53
|
+
repo_info: RepoInfo,
|
|
54
|
+
existing_app_id: str | None = None,
|
|
55
|
+
force_new_application: bool = False,
|
|
56
|
+
) -> RegistrationResult:
|
|
57
|
+
"""POST /v1/applications/surface with Bearer PAT; returns app id."""
|
|
58
|
+
body: dict[str, Any] = {
|
|
59
|
+
"repoName": repo_info.repo_name,
|
|
60
|
+
"repoUrl": repo_info.repo_url,
|
|
61
|
+
"branch": repo_info.branch,
|
|
62
|
+
"commitSha": repo_info.commit_sha,
|
|
63
|
+
"scmProvider": repo_info.scm_provider,
|
|
64
|
+
"canonicalRepoId": repo_info.canonical_repo_id,
|
|
65
|
+
}
|
|
66
|
+
if existing_app_id:
|
|
67
|
+
body["existingAppId"] = existing_app_id
|
|
68
|
+
if force_new_application:
|
|
69
|
+
body["forceNewApplication"] = True
|
|
70
|
+
|
|
71
|
+
with httpx.Client(
|
|
72
|
+
base_url=self._api_url,
|
|
73
|
+
timeout=httpx.Timeout(self._timeout),
|
|
74
|
+
verify=self._verify_ssl,
|
|
75
|
+
) as client:
|
|
76
|
+
response = client.post(
|
|
77
|
+
"/v1/applications/surface",
|
|
78
|
+
json=body,
|
|
79
|
+
headers={"Authorization": f"Bearer {self._pat}"},
|
|
80
|
+
)
|
|
81
|
+
response.raise_for_status()
|
|
82
|
+
data = response.json()
|
|
83
|
+
|
|
84
|
+
app_id = data.get("appId") or data.get("app_id")
|
|
85
|
+
if not app_id:
|
|
86
|
+
raise ValueError("surface registration response missing appId")
|
|
87
|
+
result = RegistrationResult(app_id=str(app_id))
|
|
88
|
+
logger.info("Surface registration resolved app_id=%s", result.app_id)
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def upload_and_verify(
|
|
92
|
+
self,
|
|
93
|
+
registration: RegistrationResult,
|
|
94
|
+
manifest_data: dict[str, Any],
|
|
95
|
+
project_root: Path,
|
|
96
|
+
reasoning_engine_url: str | None = None,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
"""Upload manifest to reasoning engine with Bearer PAT and app_id; run verification."""
|
|
99
|
+
engine_url = reasoning_engine_url or self._api_url
|
|
100
|
+
with CloudClient(
|
|
101
|
+
api_url=engine_url,
|
|
102
|
+
api_key=self._pat,
|
|
103
|
+
app_id=registration.app_id,
|
|
104
|
+
verify_ssl=self._verify_ssl,
|
|
105
|
+
) as client:
|
|
106
|
+
from ..query.executor import QueryExecutor
|
|
107
|
+
|
|
108
|
+
executor = QueryExecutor(project_root=project_root)
|
|
109
|
+
result = executor.run_connected(client, manifest_data)
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"analysis_id": result.analysis_id,
|
|
113
|
+
"status": result.final_status,
|
|
114
|
+
"rounds": result.stats.rounds_completed,
|
|
115
|
+
"questions_answered": result.stats.questions_answered,
|
|
116
|
+
"elapsed_seconds": result.stats.elapsed_seconds,
|
|
117
|
+
"error": result.error,
|
|
118
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP client for communicating with the APIsec reasoning engine.
|
|
3
|
+
|
|
4
|
+
Handles manifest upload, question polling, and answer submission.
|
|
5
|
+
All communication is outbound-only (probe initiates).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
from urllib.parse import quote
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class QuestionItem:
|
|
23
|
+
"""A single verification question from the engine."""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
type: str
|
|
27
|
+
gate_id: str
|
|
28
|
+
finding_id: str
|
|
29
|
+
round_number: int
|
|
30
|
+
target_file: str | None = None
|
|
31
|
+
target_function: str | None = None
|
|
32
|
+
target_line: int | None = None
|
|
33
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class QuestionBatchResult:
|
|
38
|
+
"""Result of polling for questions."""
|
|
39
|
+
|
|
40
|
+
status: str
|
|
41
|
+
batch_id: str | None = None
|
|
42
|
+
round_number: int | None = None
|
|
43
|
+
questions: list[QuestionItem] = field(default_factory=list)
|
|
44
|
+
retry_after: int | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class SubmitResult:
|
|
49
|
+
"""Result of submitting answers."""
|
|
50
|
+
|
|
51
|
+
received: bool
|
|
52
|
+
answers_processed: int
|
|
53
|
+
more_questions: bool
|
|
54
|
+
status: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CloudClient:
|
|
58
|
+
"""HTTP client for the reasoning engine API."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
api_url: str,
|
|
63
|
+
api_key: str | None = None,
|
|
64
|
+
*,
|
|
65
|
+
app_id: str | None = None,
|
|
66
|
+
timeout_seconds: int = 60,
|
|
67
|
+
retry_attempts: int = 3,
|
|
68
|
+
verify_ssl: bool = True,
|
|
69
|
+
):
|
|
70
|
+
self._base_url = api_url.rstrip("/")
|
|
71
|
+
self._api_key = api_key
|
|
72
|
+
self._app_id = app_id
|
|
73
|
+
self._timeout = timeout_seconds
|
|
74
|
+
self._retries = retry_attempts
|
|
75
|
+
|
|
76
|
+
headers: dict[str, str] = {}
|
|
77
|
+
if api_key:
|
|
78
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
79
|
+
|
|
80
|
+
self._client = httpx.Client(
|
|
81
|
+
base_url=self._base_url,
|
|
82
|
+
headers=headers,
|
|
83
|
+
timeout=httpx.Timeout(timeout_seconds),
|
|
84
|
+
verify=verify_ssl,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def close(self) -> None:
|
|
88
|
+
self._client.close()
|
|
89
|
+
|
|
90
|
+
def __enter__(self) -> CloudClient:
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def __exit__(self, *args: Any) -> None:
|
|
94
|
+
self.close()
|
|
95
|
+
|
|
96
|
+
# ------------------------------------------------------------------
|
|
97
|
+
# Manifest upload
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def upload_manifest(self, manifest_data: dict[str, Any]) -> tuple[str, str]:
|
|
101
|
+
"""Upload a manifest and return (analysis_id, status)."""
|
|
102
|
+
if not self._app_id:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"app_id is required for manifest upload (register via POST /v1/applications/surface "
|
|
105
|
+
"or set application id in apisec-code-bolt/state.yaml / APISEC_APP_ID)",
|
|
106
|
+
)
|
|
107
|
+
path = f"/api/v1/manifests?app_id={quote(self._app_id, safe='')}"
|
|
108
|
+
response = self._post(path, json=manifest_data)
|
|
109
|
+
data = response.json()
|
|
110
|
+
analysis_id = data["analysis_id"]
|
|
111
|
+
status = data["status"]
|
|
112
|
+
logger.info(
|
|
113
|
+
"Manifest uploaded: analysis_id=%s status=%s message=%s",
|
|
114
|
+
analysis_id,
|
|
115
|
+
status,
|
|
116
|
+
data.get("message", ""),
|
|
117
|
+
)
|
|
118
|
+
return analysis_id, status
|
|
119
|
+
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
# Question polling
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def poll_questions(
|
|
125
|
+
self,
|
|
126
|
+
analysis_id: str,
|
|
127
|
+
poll_timeout: int = 30,
|
|
128
|
+
max_wait: int = 300,
|
|
129
|
+
) -> QuestionBatchResult:
|
|
130
|
+
"""Poll for verification questions with retry and backoff.
|
|
131
|
+
|
|
132
|
+
Keeps polling until questions are ready, analysis is complete,
|
|
133
|
+
or max_wait is exceeded.
|
|
134
|
+
"""
|
|
135
|
+
start = time.monotonic()
|
|
136
|
+
backoff = 1.0
|
|
137
|
+
|
|
138
|
+
while (time.monotonic() - start) < max_wait:
|
|
139
|
+
result = self._fetch_questions(analysis_id)
|
|
140
|
+
|
|
141
|
+
if result.status == "questions_ready":
|
|
142
|
+
return result
|
|
143
|
+
if result.status in ("complete", "failed"):
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
wait = result.retry_after or backoff
|
|
147
|
+
logger.debug("No questions yet, retrying in %.1fs", wait)
|
|
148
|
+
time.sleep(wait)
|
|
149
|
+
backoff = min(backoff * 1.5, 10.0)
|
|
150
|
+
|
|
151
|
+
logger.warning("Question poll timed out after %ds", max_wait)
|
|
152
|
+
return QuestionBatchResult(status="timeout")
|
|
153
|
+
|
|
154
|
+
def _fetch_questions(self, analysis_id: str) -> QuestionBatchResult:
|
|
155
|
+
response = self._get(f"/api/v1/analyses/{analysis_id}/questions")
|
|
156
|
+
data = response.json()
|
|
157
|
+
|
|
158
|
+
questions = [
|
|
159
|
+
QuestionItem(
|
|
160
|
+
id=q["id"],
|
|
161
|
+
type=q["type"],
|
|
162
|
+
gate_id=q["gate_id"],
|
|
163
|
+
finding_id=q["finding_id"],
|
|
164
|
+
round_number=q.get("round_number", 1),
|
|
165
|
+
target_file=q.get("target_file"),
|
|
166
|
+
target_function=q.get("target_function"),
|
|
167
|
+
target_line=q.get("target_line"),
|
|
168
|
+
params=q.get("params", {}),
|
|
169
|
+
)
|
|
170
|
+
for q in data.get("questions", [])
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
return QuestionBatchResult(
|
|
174
|
+
status=data["status"],
|
|
175
|
+
batch_id=data.get("batch_id"),
|
|
176
|
+
round_number=data.get("round_number"),
|
|
177
|
+
questions=questions,
|
|
178
|
+
retry_after=data.get("retry_after"),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
# Answer submission
|
|
183
|
+
# ------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
def submit_answers(
|
|
186
|
+
self,
|
|
187
|
+
analysis_id: str,
|
|
188
|
+
batch_id: str,
|
|
189
|
+
answers: list[dict[str, Any]],
|
|
190
|
+
) -> SubmitResult:
|
|
191
|
+
"""Submit answers for a batch of questions."""
|
|
192
|
+
payload = {"batch_id": batch_id, "answers": answers}
|
|
193
|
+
response = self._post(f"/api/v1/analyses/{analysis_id}/answers", json=payload)
|
|
194
|
+
data = response.json()
|
|
195
|
+
|
|
196
|
+
result = SubmitResult(
|
|
197
|
+
received=data["received"],
|
|
198
|
+
answers_processed=data["answers_processed"],
|
|
199
|
+
more_questions=data["more_questions"],
|
|
200
|
+
status=data["status"],
|
|
201
|
+
)
|
|
202
|
+
logger.info(
|
|
203
|
+
"Answers submitted: batch=%s processed=%d more_questions=%s",
|
|
204
|
+
batch_id,
|
|
205
|
+
result.answers_processed,
|
|
206
|
+
result.more_questions,
|
|
207
|
+
)
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
# Analysis status
|
|
212
|
+
# ------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
def get_analysis_status(self, analysis_id: str) -> dict[str, Any]:
|
|
215
|
+
response = self._get(f"/api/v1/analyses/{analysis_id}")
|
|
216
|
+
return response.json()
|
|
217
|
+
|
|
218
|
+
# ------------------------------------------------------------------
|
|
219
|
+
# HTTP helpers
|
|
220
|
+
# ------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
def _get(self, path: str) -> httpx.Response:
|
|
223
|
+
return self._request("GET", path)
|
|
224
|
+
|
|
225
|
+
def _post(self, path: str, **kwargs: Any) -> httpx.Response:
|
|
226
|
+
return self._request("POST", path, **kwargs)
|
|
227
|
+
|
|
228
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
229
|
+
last_exc: Exception | None = None
|
|
230
|
+
|
|
231
|
+
for attempt in range(self._retries + 1):
|
|
232
|
+
try:
|
|
233
|
+
response = self._client.request(method, path, **kwargs)
|
|
234
|
+
response.raise_for_status()
|
|
235
|
+
return response
|
|
236
|
+
except httpx.HTTPStatusError:
|
|
237
|
+
raise
|
|
238
|
+
except (httpx.TransportError, httpx.TimeoutException) as e:
|
|
239
|
+
last_exc = e
|
|
240
|
+
if attempt < self._retries:
|
|
241
|
+
wait = 2**attempt
|
|
242
|
+
logger.warning(
|
|
243
|
+
"Request %s %s failed (attempt %d/%d): %s — retrying in %ds",
|
|
244
|
+
method,
|
|
245
|
+
path,
|
|
246
|
+
attempt + 1,
|
|
247
|
+
self._retries + 1,
|
|
248
|
+
e,
|
|
249
|
+
wait,
|
|
250
|
+
)
|
|
251
|
+
time.sleep(wait)
|
|
252
|
+
|
|
253
|
+
raise ConnectionError(
|
|
254
|
+
f"Failed after {self._retries + 1} attempts: {last_exc}"
|
|
255
|
+
) from last_exc
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Core types, configuration, and manifest definitions."""
|
|
2
|
+
|
|
3
|
+
from .config import (
|
|
4
|
+
AnalysisConfig,
|
|
5
|
+
CloudConfig,
|
|
6
|
+
CodeBoltConfig,
|
|
7
|
+
DataFlowConfig,
|
|
8
|
+
FileDiscoveryConfig,
|
|
9
|
+
JvmConfig,
|
|
10
|
+
OutputConfig,
|
|
11
|
+
QueryApiConfig,
|
|
12
|
+
get_default_config,
|
|
13
|
+
load_config,
|
|
14
|
+
)
|
|
15
|
+
from .manifest import MANIFEST_VERSION, Manifest
|
|
16
|
+
from .types import (
|
|
17
|
+
AnalysisError,
|
|
18
|
+
AnalysisNote,
|
|
19
|
+
AuthDependencyType,
|
|
20
|
+
AuthSchemeType,
|
|
21
|
+
CallContext,
|
|
22
|
+
CloudCommunicationError,
|
|
23
|
+
CodeBoltError,
|
|
24
|
+
CodeLocation,
|
|
25
|
+
Confidence,
|
|
26
|
+
ConfigurationError,
|
|
27
|
+
FlowContext,
|
|
28
|
+
Framework,
|
|
29
|
+
HttpMethod,
|
|
30
|
+
Language,
|
|
31
|
+
OriginType,
|
|
32
|
+
ParameterLocation,
|
|
33
|
+
ParseError,
|
|
34
|
+
QualifiedName,
|
|
35
|
+
TransformationType,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Enums
|
|
40
|
+
"Language",
|
|
41
|
+
"Framework",
|
|
42
|
+
"OriginType",
|
|
43
|
+
"TransformationType",
|
|
44
|
+
"AuthSchemeType",
|
|
45
|
+
"AuthDependencyType",
|
|
46
|
+
"HttpMethod",
|
|
47
|
+
"ParameterLocation",
|
|
48
|
+
"Confidence",
|
|
49
|
+
# Data types
|
|
50
|
+
"CodeLocation",
|
|
51
|
+
"QualifiedName",
|
|
52
|
+
"CallContext",
|
|
53
|
+
"FlowContext",
|
|
54
|
+
"AnalysisNote",
|
|
55
|
+
# Config
|
|
56
|
+
"CodeBoltConfig",
|
|
57
|
+
"AnalysisConfig",
|
|
58
|
+
"FileDiscoveryConfig",
|
|
59
|
+
"DataFlowConfig",
|
|
60
|
+
"JvmConfig",
|
|
61
|
+
"CloudConfig",
|
|
62
|
+
"QueryApiConfig",
|
|
63
|
+
"OutputConfig",
|
|
64
|
+
"load_config",
|
|
65
|
+
"get_default_config",
|
|
66
|
+
# Manifest
|
|
67
|
+
"Manifest",
|
|
68
|
+
"MANIFEST_VERSION",
|
|
69
|
+
# Exceptions
|
|
70
|
+
"CodeBoltError",
|
|
71
|
+
"ParseError",
|
|
72
|
+
"ConfigurationError",
|
|
73
|
+
"AnalysisError",
|
|
74
|
+
"CloudCommunicationError",
|
|
75
|
+
]
|