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,393 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task framework plugin for Celery, Dramatiq, Huey, and RQ.
|
|
3
|
+
|
|
4
|
+
Detects async task entry points from decorator patterns across the four
|
|
5
|
+
most widely-used Python task/worker frameworks.
|
|
6
|
+
|
|
7
|
+
Supported constructs — Celery (5.x):
|
|
8
|
+
- @app.task / @app.task(name="...", bind=True, ...)
|
|
9
|
+
- @shared_task / @shared_task(name="...", ...)
|
|
10
|
+
- @celery.task (alternate Celery instance naming)
|
|
11
|
+
- Class-based tasks (subclasses of celery.Task) — NOT yet supported
|
|
12
|
+
|
|
13
|
+
Supported constructs — Dramatiq (2.x):
|
|
14
|
+
- @dramatiq.actor / @dramatiq.actor(queue_name="...", actor_name="...", ...)
|
|
15
|
+
|
|
16
|
+
Supported constructs — Huey (2.x):
|
|
17
|
+
- @huey.task() / @huey.periodic_task(...)
|
|
18
|
+
- Alternate instance names: @queue.task(), @queue.periodic_task()
|
|
19
|
+
|
|
20
|
+
Supported constructs — RQ (1.x):
|
|
21
|
+
- @job(queue, connection, ...) from rq.decorators
|
|
22
|
+
|
|
23
|
+
Not supported (yet):
|
|
24
|
+
- Celery class-based tasks (subclass of celery.Task with run() method)
|
|
25
|
+
- Celery canvas primitives (chain, group, chord as entry points)
|
|
26
|
+
- Dynamic task registration (app.register_task)
|
|
27
|
+
- Dramatiq middleware hooks
|
|
28
|
+
- Huey pre/post-execute hooks
|
|
29
|
+
- RQ queue.enqueue() calls as entry points
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import Any, ClassVar
|
|
35
|
+
|
|
36
|
+
from ...core.types import (
|
|
37
|
+
Confidence,
|
|
38
|
+
Framework,
|
|
39
|
+
HttpMethod,
|
|
40
|
+
Language,
|
|
41
|
+
ParameterLocation,
|
|
42
|
+
)
|
|
43
|
+
from ...parsing.base import ParsedDecorator, ParsedFile, ParsedFunction
|
|
44
|
+
from ..base import (
|
|
45
|
+
BaseFrameworkPlugin,
|
|
46
|
+
ExtractedAuthDependency,
|
|
47
|
+
ExtractedAuthScheme,
|
|
48
|
+
ExtractedDependency,
|
|
49
|
+
ExtractedMiddleware,
|
|
50
|
+
ExtractedParameter,
|
|
51
|
+
ExtractedResponse,
|
|
52
|
+
ExtractedRoute,
|
|
53
|
+
FrameworkPluginRegistry,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# ── Import detection ────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
CELERY_IMPORTS = frozenset(
|
|
59
|
+
{
|
|
60
|
+
"celery",
|
|
61
|
+
"celery.app",
|
|
62
|
+
"celery.shared_task",
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
DRAMATIQ_IMPORTS = frozenset(
|
|
67
|
+
{
|
|
68
|
+
"dramatiq",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
HUEY_IMPORTS = frozenset(
|
|
73
|
+
{
|
|
74
|
+
"huey",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
RQ_IMPORTS = frozenset(
|
|
79
|
+
{
|
|
80
|
+
"rq",
|
|
81
|
+
"rq.decorators",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
ALL_TASK_IMPORTS = CELERY_IMPORTS | DRAMATIQ_IMPORTS | HUEY_IMPORTS | RQ_IMPORTS
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ── Decorator name sets ─────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
# Celery: @app.task, @celery_app.task, @shared_task
|
|
91
|
+
CELERY_TASK_DECORATORS: set[str] = {
|
|
92
|
+
"task", # @app.task / @celery.task (attribute form)
|
|
93
|
+
"shared_task", # @shared_task (bare import from celery)
|
|
94
|
+
"celery.shared_task", # @celery.shared_task (qualified)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Dramatiq: @dramatiq.actor
|
|
98
|
+
DRAMATIQ_ACTOR_DECORATORS: set[str] = {
|
|
99
|
+
"actor", # @broker.actor (attribute) or @dramatiq.actor (qualified)
|
|
100
|
+
"dramatiq.actor", # @dramatiq.actor
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Huey: @huey.task(), @huey.periodic_task()
|
|
104
|
+
HUEY_TASK_DECORATORS: set[str] = {
|
|
105
|
+
"task", # @huey.task() (attribute)
|
|
106
|
+
"periodic_task", # @huey.periodic_task()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# RQ: @job(queue, connection)
|
|
110
|
+
RQ_JOB_DECORATORS: set[str] = {
|
|
111
|
+
"job", # @job (from rq.decorators import job)
|
|
112
|
+
"rq.decorators.job", # qualified import
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
ALL_TASK_DECORATORS = (
|
|
116
|
+
CELERY_TASK_DECORATORS | DRAMATIQ_ACTOR_DECORATORS | HUEY_TASK_DECORATORS | RQ_JOB_DECORATORS
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ── Which framework set detected? ──────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _DetectedFramework:
|
|
124
|
+
"""Tracks which task framework(s) are present in a file."""
|
|
125
|
+
|
|
126
|
+
__slots__ = ("celery", "dramatiq", "huey", "rq")
|
|
127
|
+
|
|
128
|
+
def __init__(self) -> None:
|
|
129
|
+
self.celery = False
|
|
130
|
+
self.dramatiq = False
|
|
131
|
+
self.huey = False
|
|
132
|
+
self.rq = False
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def any(self) -> bool:
|
|
136
|
+
return self.celery or self.dramatiq or self.huey or self.rq
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _detect_task_frameworks(parsed_file: ParsedFile) -> _DetectedFramework:
|
|
140
|
+
found = _DetectedFramework()
|
|
141
|
+
for imp in parsed_file.imports:
|
|
142
|
+
module = imp.module or ""
|
|
143
|
+
mod = module.split(".")[0]
|
|
144
|
+
if mod == "celery" or module.endswith(".celery"):
|
|
145
|
+
# Catches direct `from celery import ...` AND the common Django
|
|
146
|
+
# convention of `from myapp.celery import app` where the app's
|
|
147
|
+
# Celery instance lives in a local `celery.py` module.
|
|
148
|
+
found.celery = True
|
|
149
|
+
elif mod == "dramatiq":
|
|
150
|
+
found.dramatiq = True
|
|
151
|
+
elif mod == "huey":
|
|
152
|
+
found.huey = True
|
|
153
|
+
elif mod == "rq":
|
|
154
|
+
found.rq = True
|
|
155
|
+
for name in imp.names:
|
|
156
|
+
if name == "shared_task":
|
|
157
|
+
found.celery = True
|
|
158
|
+
elif name == "job" and imp.module and "rq" in imp.module:
|
|
159
|
+
found.rq = True
|
|
160
|
+
return found
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ── Plugin ──────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class CeleryPlugin(BaseFrameworkPlugin):
|
|
167
|
+
"""Plugin for Celery, Dramatiq, Huey, and RQ task frameworks."""
|
|
168
|
+
|
|
169
|
+
FRAMEWORK: ClassVar[Framework] = Framework.CELERY
|
|
170
|
+
LANGUAGE: ClassVar[Language] = Language.PYTHON
|
|
171
|
+
DETECTION_IMPORTS: ClassVar[frozenset[str]] = ALL_TASK_IMPORTS
|
|
172
|
+
|
|
173
|
+
# ------------------------------------------------------------------
|
|
174
|
+
# Detection
|
|
175
|
+
# ------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def detect(self, parsed_file: ParsedFile) -> bool:
|
|
178
|
+
return _detect_task_frameworks(parsed_file).any
|
|
179
|
+
|
|
180
|
+
# ------------------------------------------------------------------
|
|
181
|
+
# Route (task) extraction
|
|
182
|
+
# ------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
def extract_routes(
|
|
185
|
+
self,
|
|
186
|
+
parsed_file: ParsedFile,
|
|
187
|
+
context: Any = None,
|
|
188
|
+
) -> list[ExtractedRoute]:
|
|
189
|
+
fw = _detect_task_frameworks(parsed_file)
|
|
190
|
+
routes: list[ExtractedRoute] = []
|
|
191
|
+
|
|
192
|
+
for func in parsed_file.functions:
|
|
193
|
+
route = self._try_extract_task(func, fw)
|
|
194
|
+
if route:
|
|
195
|
+
routes.append(route)
|
|
196
|
+
|
|
197
|
+
for cls in parsed_file.classes:
|
|
198
|
+
for method in cls.methods:
|
|
199
|
+
route = self._try_extract_task(method, fw)
|
|
200
|
+
if route:
|
|
201
|
+
routes.append(route)
|
|
202
|
+
|
|
203
|
+
return routes
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
# Stubs — task frameworks don't define HTTP auth/deps/middleware
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def extract_dependencies(self, parsed_file: ParsedFile) -> list[ExtractedDependency]:
|
|
210
|
+
return []
|
|
211
|
+
|
|
212
|
+
def extract_auth_schemes(self, parsed_file: ParsedFile) -> list[ExtractedAuthScheme]:
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
def extract_auth_dependencies(
|
|
216
|
+
self,
|
|
217
|
+
parsed_file: ParsedFile,
|
|
218
|
+
known_scheme_names: set[str] | None = None,
|
|
219
|
+
**kwargs: Any,
|
|
220
|
+
) -> list[ExtractedAuthDependency]:
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
def extract_middleware(self, parsed_file: ParsedFile) -> list[ExtractedMiddleware]:
|
|
224
|
+
return []
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
# Internal
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def _try_extract_task(
|
|
231
|
+
self,
|
|
232
|
+
func: ParsedFunction,
|
|
233
|
+
fw: _DetectedFramework,
|
|
234
|
+
) -> ExtractedRoute | None:
|
|
235
|
+
"""If *func* has a task decorator, return an ExtractedRoute with kind="task"."""
|
|
236
|
+
dec, origin = self._find_task_decorator(func, fw)
|
|
237
|
+
if dec is None:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
task_name = self._resolve_task_name(dec, func, origin)
|
|
241
|
+
params = self._extract_parameters(func, origin)
|
|
242
|
+
kind = "scheduled" if self._is_periodic(dec) else "task"
|
|
243
|
+
|
|
244
|
+
return ExtractedRoute(
|
|
245
|
+
kind=kind,
|
|
246
|
+
method=HttpMethod.POST,
|
|
247
|
+
path=task_name,
|
|
248
|
+
handler_function=func.qualified_name,
|
|
249
|
+
handler_location=func.location,
|
|
250
|
+
path_params=[],
|
|
251
|
+
query_params=params,
|
|
252
|
+
header_params=[],
|
|
253
|
+
cookie_params=[],
|
|
254
|
+
body=None,
|
|
255
|
+
response=ExtractedResponse(),
|
|
256
|
+
confidence=Confidence.HIGH,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def _find_task_decorator(
|
|
260
|
+
self,
|
|
261
|
+
func: ParsedFunction,
|
|
262
|
+
fw: _DetectedFramework,
|
|
263
|
+
) -> tuple[ParsedDecorator | None, str]:
|
|
264
|
+
"""Return (decorator, origin_framework) if a task decorator is found."""
|
|
265
|
+
for dec in func.decorators:
|
|
266
|
+
base = self._decorator_base_name(dec)
|
|
267
|
+
|
|
268
|
+
if fw.celery and base in CELERY_TASK_DECORATORS:
|
|
269
|
+
return dec, "celery"
|
|
270
|
+
if fw.dramatiq and base in DRAMATIQ_ACTOR_DECORATORS:
|
|
271
|
+
return dec, "dramatiq"
|
|
272
|
+
if fw.huey and base in HUEY_TASK_DECORATORS:
|
|
273
|
+
return dec, "huey"
|
|
274
|
+
if fw.rq and base in RQ_JOB_DECORATORS:
|
|
275
|
+
return dec, "rq"
|
|
276
|
+
|
|
277
|
+
return None, ""
|
|
278
|
+
|
|
279
|
+
def _resolve_task_name(
|
|
280
|
+
self,
|
|
281
|
+
dec: ParsedDecorator,
|
|
282
|
+
func: ParsedFunction,
|
|
283
|
+
origin: str,
|
|
284
|
+
) -> str:
|
|
285
|
+
"""Derive the canonical task name.
|
|
286
|
+
|
|
287
|
+
Priority: explicit ``name=`` kwarg > first positional string arg >
|
|
288
|
+
function qualified name.
|
|
289
|
+
"""
|
|
290
|
+
name_kwarg_key = "actor_name" if origin == "dramatiq" else "name"
|
|
291
|
+
explicit = self._extract_decorator_arg(dec, name_kwarg_key)
|
|
292
|
+
if explicit and isinstance(explicit, str):
|
|
293
|
+
return explicit
|
|
294
|
+
|
|
295
|
+
if dec.positional_args:
|
|
296
|
+
first = dec.positional_args[0]
|
|
297
|
+
if isinstance(first, str):
|
|
298
|
+
return first
|
|
299
|
+
|
|
300
|
+
return func.qualified_name.full
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def _is_periodic(dec: ParsedDecorator) -> bool:
|
|
304
|
+
"""True if the decorator indicates a periodic/scheduled task (Huey)."""
|
|
305
|
+
name = dec.name or ""
|
|
306
|
+
return "periodic" in name.lower()
|
|
307
|
+
|
|
308
|
+
def _extract_parameters(
|
|
309
|
+
self,
|
|
310
|
+
func: ParsedFunction,
|
|
311
|
+
origin: str,
|
|
312
|
+
) -> list[ExtractedParameter]:
|
|
313
|
+
"""Extract task argument parameters from the function signature.
|
|
314
|
+
|
|
315
|
+
Celery ``bind=True`` injects ``self`` as the first parameter — skip it.
|
|
316
|
+
"""
|
|
317
|
+
params: list[ExtractedParameter] = []
|
|
318
|
+
skip_first = False
|
|
319
|
+
|
|
320
|
+
if origin == "celery":
|
|
321
|
+
for dec in func.decorators:
|
|
322
|
+
bind_val = self._extract_decorator_arg(dec, "bind")
|
|
323
|
+
if bind_val in (True, "True", "true"):
|
|
324
|
+
skip_first = True
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
skipped = False
|
|
328
|
+
for p in func.parameters:
|
|
329
|
+
if p.name in ("self", "cls"):
|
|
330
|
+
continue
|
|
331
|
+
if skip_first and not skipped:
|
|
332
|
+
skipped = True
|
|
333
|
+
continue
|
|
334
|
+
if p.is_variadic or p.is_keyword_variadic:
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
params.append(
|
|
338
|
+
ExtractedParameter(
|
|
339
|
+
name=p.name,
|
|
340
|
+
location=ParameterLocation.TASK_ARGUMENT,
|
|
341
|
+
type_annotation=p.type_annotation,
|
|
342
|
+
required=p.default_value is None,
|
|
343
|
+
default_value=p.default_value,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
return params
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def _decorator_base_name(dec: ParsedDecorator) -> str:
|
|
351
|
+
"""Normalise a decorator to a lookup key.
|
|
352
|
+
|
|
353
|
+
Handles:
|
|
354
|
+
- ``celery.shared_task`` (qualified)
|
|
355
|
+
- ``shared_task`` (bare import)
|
|
356
|
+
- ``app.task`` (attribute on Celery instance)
|
|
357
|
+
- ``dramatiq.actor`` (qualified)
|
|
358
|
+
- ``huey.task`` / ``huey.periodic_task`` (attribute on Huey instance)
|
|
359
|
+
- ``job`` (bare RQ import)
|
|
360
|
+
"""
|
|
361
|
+
if dec.qualified_name:
|
|
362
|
+
qn = dec.qualified_name.full
|
|
363
|
+
parts = qn.rsplit(".", 1)
|
|
364
|
+
if len(parts) == 2:
|
|
365
|
+
mod, attr = parts
|
|
366
|
+
if mod in ("celery", "dramatiq", "rq.decorators"):
|
|
367
|
+
return f"{mod}.{attr}"
|
|
368
|
+
if attr in (
|
|
369
|
+
"task",
|
|
370
|
+
"shared_task",
|
|
371
|
+
"actor",
|
|
372
|
+
"periodic_task",
|
|
373
|
+
"job",
|
|
374
|
+
):
|
|
375
|
+
return attr
|
|
376
|
+
return qn
|
|
377
|
+
|
|
378
|
+
name = dec.name
|
|
379
|
+
if "." in name:
|
|
380
|
+
_obj, _, attr = name.rpartition(".")
|
|
381
|
+
if attr in (
|
|
382
|
+
"task",
|
|
383
|
+
"shared_task",
|
|
384
|
+
"actor",
|
|
385
|
+
"periodic_task",
|
|
386
|
+
"job",
|
|
387
|
+
):
|
|
388
|
+
return attr
|
|
389
|
+
return name
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
_celery_plugin = CeleryPlugin()
|
|
393
|
+
FrameworkPluginRegistry.register(_celery_plugin)
|