code-node-graph 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.
- code_node/__init__.py +40 -0
- code_node/__main__.py +7 -0
- code_node/analysis/__init__.py +3 -0
- code_node/analysis/aws_model.py +197 -0
- code_node/analysis/findings.py +462 -0
- code_node/analysis/graph.py +780 -0
- code_node/analysis/mapping.py +114 -0
- code_node/analysis/model.py +289 -0
- code_node/analysis/reader.py +1926 -0
- code_node/analysis/syntax.py +220 -0
- code_node/analysis/terms.py +505 -0
- code_node/cli.py +316 -0
- code_node/deploys/__init__.py +321 -0
- code_node/deploys/sam.py +371 -0
- code_node/deploys/serverless.py +117 -0
- code_node/kit/__init__.py +4 -0
- code_node/kit/blocks.py +184 -0
- code_node/kit/fmt.py +389 -0
- code_node/kit/highlight.py +131 -0
- code_node/kit/render.py +1066 -0
- code_node/kit/text.py +81 -0
- code_node/libraries/__init__.py +333 -0
- code_node/libraries/llm.py +124 -0
- code_node/libraries/mongo.py +169 -0
- code_node/libraries/redis.py +253 -0
- code_node/libraries/sql.py +232 -0
- code_node/pages.py +92 -0
- code_node/platforms/__init__.py +203 -0
- code_node/platforms/aws_lambda.py +290 -0
- code_node/platforms/web.py +301 -0
- code_node/project.py +402 -0
- code_node/report/__init__.py +3 -0
- code_node/report/brands.py +244 -0
- code_node/report/build.py +119 -0
- code_node/report/editor.py +40 -0
- code_node/report/graph.py +1411 -0
- code_node/report/logos/README.md +8 -0
- code_node/report/logos/anthropic-icon.svg +1 -0
- code_node/report/logos/aws-amplify.svg +1 -0
- code_node/report/logos/aws-api-gateway.svg +1 -0
- code_node/report/logos/aws-appflow.svg +1 -0
- code_node/report/logos/aws-appsync.svg +1 -0
- code_node/report/logos/aws-athena.svg +1 -0
- code_node/report/logos/aws-backup.svg +1 -0
- code_node/report/logos/aws-batch.svg +1 -0
- code_node/report/logos/aws-certificate-manager.svg +1 -0
- code_node/report/logos/aws-cloudformation.svg +1 -0
- code_node/report/logos/aws-cloudfront.svg +1 -0
- code_node/report/logos/aws-cloudtrail.svg +1 -0
- code_node/report/logos/aws-cloudwatch.svg +1 -0
- code_node/report/logos/aws-codebuild.svg +1 -0
- code_node/report/logos/aws-codecommit.svg +1 -0
- code_node/report/logos/aws-codedeploy.svg +1 -0
- code_node/report/logos/aws-codepipeline.svg +1 -0
- code_node/report/logos/aws-cognito.svg +1 -0
- code_node/report/logos/aws-config.svg +1 -0
- code_node/report/logos/aws-documentdb.svg +1 -0
- code_node/report/logos/aws-dynamodb.svg +1 -0
- code_node/report/logos/aws-ec2.svg +1 -0
- code_node/report/logos/aws-ecs.svg +1 -0
- code_node/report/logos/aws-eks.svg +1 -0
- code_node/report/logos/aws-elasticache.svg +1 -0
- code_node/report/logos/aws-elb.svg +1 -0
- code_node/report/logos/aws-eventbridge.svg +1 -0
- code_node/report/logos/aws-glacier.svg +1 -0
- code_node/report/logos/aws-glue.svg +1 -0
- code_node/report/logos/aws-iam.svg +1 -0
- code_node/report/logos/aws-keyspaces.svg +1 -0
- code_node/report/logos/aws-kinesis.svg +1 -0
- code_node/report/logos/aws-kms.svg +1 -0
- code_node/report/logos/aws-lake-formation.svg +1 -0
- code_node/report/logos/aws-lambda.svg +1 -0
- code_node/report/logos/aws-lightsail.svg +1 -0
- code_node/report/logos/aws-mq.svg +1 -0
- code_node/report/logos/aws-msk.svg +1 -0
- code_node/report/logos/aws-neptune.svg +1 -0
- code_node/report/logos/aws-open-search.svg +1 -0
- code_node/report/logos/aws-quicksight.svg +1 -0
- code_node/report/logos/aws-rds.svg +1 -0
- code_node/report/logos/aws-redshift.svg +1 -0
- code_node/report/logos/aws-route53.svg +1 -0
- code_node/report/logos/aws-s3.svg +1 -0
- code_node/report/logos/aws-secrets-manager.svg +1 -0
- code_node/report/logos/aws-ses.svg +1 -0
- code_node/report/logos/aws-shield.svg +1 -0
- code_node/report/logos/aws-sns.svg +1 -0
- code_node/report/logos/aws-sqs.svg +1 -0
- code_node/report/logos/aws-step-functions.svg +1 -0
- code_node/report/logos/aws-systems-manager.svg +1 -0
- code_node/report/logos/aws-timestream.svg +1 -0
- code_node/report/logos/aws-waf.svg +1 -0
- code_node/report/logos/aws-xray.svg +1 -0
- code_node/report/logos/aws.svg +1 -0
- code_node/report/logos/azure-icon.svg +1 -0
- code_node/report/logos/django-icon.svg +1 -0
- code_node/report/logos/elasticsearch.svg +1 -0
- code_node/report/logos/fastapi-icon.svg +1 -0
- code_node/report/logos/firebase-icon.svg +1 -0
- code_node/report/logos/flask.svg +1 -0
- code_node/report/logos/google-cloud.svg +1 -0
- code_node/report/logos/kafka-icon.svg +1 -0
- code_node/report/logos/mariadb-icon.svg +1 -0
- code_node/report/logos/mongodb-icon.svg +1 -0
- code_node/report/logos/mysql-icon.svg +1 -0
- code_node/report/logos/openai-icon.svg +1 -0
- code_node/report/logos/postgresql.svg +1 -0
- code_node/report/logos/python.svg +1 -0
- code_node/report/logos/rabbitmq-icon.svg +1 -0
- code_node/report/logos/redis.svg +1 -0
- code_node/report/logos/serverless.svg +1 -0
- code_node/report/logos/slack-icon.svg +1 -0
- code_node/report/logos/small/aws-api-gateway.svg +1 -0
- code_node/report/logos/small/aws-appflow.svg +1 -0
- code_node/report/logos/small/aws-appsync.svg +1 -0
- code_node/report/logos/small/aws-athena.svg +1 -0
- code_node/report/logos/small/aws-backup.svg +1 -0
- code_node/report/logos/small/aws-batch.svg +1 -0
- code_node/report/logos/small/aws-certificate-manager.svg +1 -0
- code_node/report/logos/small/aws-cloudformation.svg +1 -0
- code_node/report/logos/small/aws-cloudfront.svg +1 -0
- code_node/report/logos/small/aws-cloudtrail.svg +1 -0
- code_node/report/logos/small/aws-cloudwatch.svg +1 -0
- code_node/report/logos/small/aws-codebuild.svg +1 -0
- code_node/report/logos/small/aws-codecommit.svg +1 -0
- code_node/report/logos/small/aws-codedeploy.svg +1 -0
- code_node/report/logos/small/aws-codepipeline.svg +1 -0
- code_node/report/logos/small/aws-cognito.svg +1 -0
- code_node/report/logos/small/aws-config.svg +1 -0
- code_node/report/logos/small/aws-documentdb.svg +1 -0
- code_node/report/logos/small/aws-dynamodb.svg +1 -0
- code_node/report/logos/small/aws-ec2.svg +1 -0
- code_node/report/logos/small/aws-ecs.svg +1 -0
- code_node/report/logos/small/aws-eks.svg +1 -0
- code_node/report/logos/small/aws-elasticache.svg +1 -0
- code_node/report/logos/small/aws-elb.svg +1 -0
- code_node/report/logos/small/aws-eventbridge.svg +1 -0
- code_node/report/logos/small/aws-glacier.svg +1 -0
- code_node/report/logos/small/aws-glue.svg +1 -0
- code_node/report/logos/small/aws-iam.svg +1 -0
- code_node/report/logos/small/aws-keyspaces.svg +1 -0
- code_node/report/logos/small/aws-kinesis.svg +1 -0
- code_node/report/logos/small/aws-kms.svg +1 -0
- code_node/report/logos/small/aws-lake-formation.svg +1 -0
- code_node/report/logos/small/aws-lambda.svg +1 -0
- code_node/report/logos/small/aws-mq.svg +1 -0
- code_node/report/logos/small/aws-msk.svg +1 -0
- code_node/report/logos/small/aws-neptune.svg +1 -0
- code_node/report/logos/small/aws-open-search.svg +1 -0
- code_node/report/logos/small/aws-quicksight.svg +1 -0
- code_node/report/logos/small/aws-rds.svg +1 -0
- code_node/report/logos/small/aws-redshift.svg +1 -0
- code_node/report/logos/small/aws-route53.svg +1 -0
- code_node/report/logos/small/aws-s3.svg +1 -0
- code_node/report/logos/small/aws-secrets-manager.svg +1 -0
- code_node/report/logos/small/aws-ses.svg +1 -0
- code_node/report/logos/small/aws-shield.svg +1 -0
- code_node/report/logos/small/aws-sns.svg +1 -0
- code_node/report/logos/small/aws-sqs.svg +1 -0
- code_node/report/logos/small/aws-step-functions.svg +1 -0
- code_node/report/logos/small/aws-systems-manager.svg +1 -0
- code_node/report/logos/small/aws-timestream.svg +1 -0
- code_node/report/logos/small/aws-waf.svg +1 -0
- code_node/report/logos/small/aws-xray.svg +1 -0
- code_node/report/logos/snowflake-icon.svg +1 -0
- code_node/report/logos/sqlite.svg +1 -0
- code_node/report/logos/stripe.svg +1 -0
- code_node/report/logos/supabase-icon.svg +1 -0
- code_node/report/logos/twilio-icon.svg +1 -0
- code_node/report/outline.py +219 -0
- code_node/report/style.py +358 -0
- code_node/report/tabs.py +73 -0
- code_node/report/walk.py +624 -0
- code_node/serve.py +277 -0
- code_node_graph-0.1.0.dist-info/METADATA +165 -0
- code_node_graph-0.1.0.dist-info/RECORD +178 -0
- code_node_graph-0.1.0.dist-info/WHEEL +4 -0
- code_node_graph-0.1.0.dist-info/entry_points.txt +2 -0
- code_node_graph-0.1.0.dist-info/licenses/LICENSE +201 -0
code_node/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
code-node: see how Python code works, read with ast and never run. A graph of who calls whom from an entry point and
|
|
3
|
+
what each path reaches (AWS calls, HTTP, environment variables), and the steps of each call.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .analysis.graph import CodeGraph, CodeSpan, GraphEdge, GraphNode, code_graph
|
|
9
|
+
from .analysis.mapping import map_code
|
|
10
|
+
from .analysis.model import CodeMap, Deployment, LibraryCall
|
|
11
|
+
from .deploys import Deploy, find_deployment, register_deploy
|
|
12
|
+
from .libraries import Library, register_library
|
|
13
|
+
from .platforms import Platform, get_platform, register_platform
|
|
14
|
+
from .project import EntryPoint, Project, entry_points, read_project
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"CodeGraph",
|
|
20
|
+
"CodeMap",
|
|
21
|
+
"CodeSpan",
|
|
22
|
+
"Deploy",
|
|
23
|
+
"Deployment",
|
|
24
|
+
"EntryPoint",
|
|
25
|
+
"GraphEdge",
|
|
26
|
+
"GraphNode",
|
|
27
|
+
"Library",
|
|
28
|
+
"LibraryCall",
|
|
29
|
+
"Platform",
|
|
30
|
+
"Project",
|
|
31
|
+
"code_graph",
|
|
32
|
+
"entry_points",
|
|
33
|
+
"find_deployment",
|
|
34
|
+
"get_platform",
|
|
35
|
+
"map_code",
|
|
36
|
+
"read_project",
|
|
37
|
+
"register_deploy",
|
|
38
|
+
"register_library",
|
|
39
|
+
"register_platform",
|
|
40
|
+
]
|
code_node/__main__.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS operations named from botocore's own service models (no AWS call), the IAM
|
|
3
|
+
action each needs, and the event a handler takes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
import functools
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Iterable
|
|
13
|
+
|
|
14
|
+
from .terms import _IAM_ACTIONS, _IAM_PREFIXES, _READS_PREFIXES, _RUNS_PREFIXES, _SENDS_PREFIXES, _SERVICE_NAMES
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .model import EventField
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _service_name(service: str) -> str:
|
|
21
|
+
"""
|
|
22
|
+
's3' -> 'S3', 'dynamodb' -> 'DynamoDB', 'stepfunctions' -> 'Step Functions': the name people use.
|
|
23
|
+
"""
|
|
24
|
+
if service in _SERVICE_NAMES:
|
|
25
|
+
return _SERVICE_NAMES[service]
|
|
26
|
+
model = _aws_model(service)
|
|
27
|
+
return model["name"] if model else service
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@functools.lru_cache(maxsize=1)
|
|
31
|
+
def _botocore_loader() -> Any:
|
|
32
|
+
from botocore.loaders import create_loader
|
|
33
|
+
|
|
34
|
+
return create_loader()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _tokens(value: Any) -> tuple[str, ...]:
|
|
38
|
+
"""
|
|
39
|
+
A paginator's token setting as names: 'NextMarker || Contents[-1].Key' -> ('NextMarker',).
|
|
40
|
+
"""
|
|
41
|
+
values = value if isinstance(value, list) else [value] if value else []
|
|
42
|
+
return tuple(m.group(0) for v in values if (m := re.match(r"\w+", str(v))))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@functools.lru_cache(maxsize=256)
|
|
46
|
+
def _aws_model(service: str) -> dict[str, Any] | None:
|
|
47
|
+
"""
|
|
48
|
+
What botocore's own copy of a service's API says, read from the files boto3 installs (no AWS call): its
|
|
49
|
+
operations by method name, which of them come in pages, its name and its IAM prefix. None for a service this
|
|
50
|
+
boto3 doesn't know.
|
|
51
|
+
"""
|
|
52
|
+
if not service or not re.fullmatch(r"[a-z0-9-]+", service):
|
|
53
|
+
return None
|
|
54
|
+
try:
|
|
55
|
+
from botocore import xform_name
|
|
56
|
+
from botocore.exceptions import BotoCoreError
|
|
57
|
+
except ImportError: # without botocore, AWS calls are known only by their methods' names
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
loader = _botocore_loader()
|
|
61
|
+
try:
|
|
62
|
+
model = loader.load_service_model(service, "service-2")
|
|
63
|
+
except (BotoCoreError, OSError, ValueError):
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
pages = loader.load_service_model(service, "paginators-1").get("pagination", {})
|
|
67
|
+
except (BotoCoreError, OSError, ValueError):
|
|
68
|
+
pages = {}
|
|
69
|
+
meta = model.get("metadata", {})
|
|
70
|
+
return {
|
|
71
|
+
"ops": {xform_name(op): op for op in model.get("operations", {})},
|
|
72
|
+
"pages": {
|
|
73
|
+
op: (
|
|
74
|
+
frozenset(_tokens(spec.get("input_token"))),
|
|
75
|
+
_tokens(spec.get("output_token")) + _tokens(spec.get("more_results")),
|
|
76
|
+
str(spec.get("limit_key") or ""),
|
|
77
|
+
)
|
|
78
|
+
for op, spec in pages.items()
|
|
79
|
+
},
|
|
80
|
+
"name": meta.get("serviceId") or service,
|
|
81
|
+
"iam": meta.get("signingName") or meta.get("endpointPrefix") or service,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _access(operation: str) -> str:
|
|
86
|
+
"""
|
|
87
|
+
What an AWS operation does to what it touches: 'reads', 'writes', 'sends' (a message or an event) or 'runs'
|
|
88
|
+
(invokes or starts something).
|
|
89
|
+
"""
|
|
90
|
+
if operation.startswith(_SENDS_PREFIXES):
|
|
91
|
+
return "sends"
|
|
92
|
+
if operation.startswith(_RUNS_PREFIXES):
|
|
93
|
+
return "runs"
|
|
94
|
+
return "reads" if operation.startswith(_READS_PREFIXES) else "writes"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _iam_action(service: str, operation: str) -> str:
|
|
98
|
+
"""
|
|
99
|
+
The IAM action an operation needs: ('s3', 'ListObjectsV2') -> 's3:ListBucket'.
|
|
100
|
+
"""
|
|
101
|
+
if (service, operation) in _IAM_ACTIONS:
|
|
102
|
+
return _IAM_ACTIONS[(service, operation)]
|
|
103
|
+
model = _aws_model(service)
|
|
104
|
+
prefix = _IAM_PREFIXES.get(service) or (model["iam"] if model else service)
|
|
105
|
+
return f"{prefix}:{operation}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _event_label(path: tuple[str, ...]) -> str:
|
|
109
|
+
"""
|
|
110
|
+
('Records', '[]', 'body', '{json}', 'bucket') -> 'Records[].body (JSON).bucket'.
|
|
111
|
+
"""
|
|
112
|
+
out = ""
|
|
113
|
+
for part in path:
|
|
114
|
+
if part == "[]":
|
|
115
|
+
out += "[]"
|
|
116
|
+
elif part in ("{json}", "{base64}"):
|
|
117
|
+
out += " (JSON)" if part == "{json}" else " (base64)"
|
|
118
|
+
else:
|
|
119
|
+
out += ("." if out else "") + part
|
|
120
|
+
return out
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _event_kind(fields: Iterable[EventField]) -> str | None:
|
|
124
|
+
"""
|
|
125
|
+
What sends the event, from the fields the code reads: 'an SQS batch', 'an HTTP request', ... None when it
|
|
126
|
+
can't tell.
|
|
127
|
+
"""
|
|
128
|
+
paths = [tuple(p.lower() for p in f.path) for f in fields]
|
|
129
|
+
|
|
130
|
+
def has(*parts: str) -> bool:
|
|
131
|
+
return any(path[: len(parts)] == parts for path in paths)
|
|
132
|
+
|
|
133
|
+
for parts, kind in (
|
|
134
|
+
(("records", "[]", "s3"), "an S3 event"),
|
|
135
|
+
(("records", "[]", "sns"), "an SNS message"),
|
|
136
|
+
(("records", "[]", "dynamodb"), "a DynamoDB stream batch"),
|
|
137
|
+
(("records", "[]", "kinesis"), "a Kinesis batch"),
|
|
138
|
+
(("records", "[]", "body"), "an SQS batch"),
|
|
139
|
+
(("records", "[]", "receipthandle"), "an SQS batch"),
|
|
140
|
+
(("records", "[]", "messageid"), "an SQS batch"),
|
|
141
|
+
(("awslogs",), "CloudWatch Logs"),
|
|
142
|
+
(("detail-type",), "an EventBridge event"),
|
|
143
|
+
(("actiongroup",), "a Bedrock agent action"),
|
|
144
|
+
(("apipath",), "a Bedrock agent action"),
|
|
145
|
+
):
|
|
146
|
+
if has(*parts):
|
|
147
|
+
return kind
|
|
148
|
+
if any(
|
|
149
|
+
has(key)
|
|
150
|
+
for key in (
|
|
151
|
+
"httpmethod",
|
|
152
|
+
"pathparameters",
|
|
153
|
+
"querystringparameters",
|
|
154
|
+
"requestcontext",
|
|
155
|
+
"headers",
|
|
156
|
+
"rawpath",
|
|
157
|
+
"routekey",
|
|
158
|
+
"multivalueheaders",
|
|
159
|
+
"rawquerystring",
|
|
160
|
+
)
|
|
161
|
+
):
|
|
162
|
+
return "an HTTP request"
|
|
163
|
+
if has("detail") and has("source"):
|
|
164
|
+
return "an EventBridge event"
|
|
165
|
+
if has("records"):
|
|
166
|
+
return "a batch of records"
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _sample_event(fields: Iterable[EventField]) -> Any:
|
|
171
|
+
"""
|
|
172
|
+
A test event with every field the code reads, its values '...': {'Records': [{'body': '{"bucket": "..."}'}]},
|
|
173
|
+
for the console's Test tab. JSON read from a text field is written as text, the way it arrives.
|
|
174
|
+
"""
|
|
175
|
+
root: dict[str, Any] = {}
|
|
176
|
+
for f in fields:
|
|
177
|
+
if "*" in f.path:
|
|
178
|
+
continue
|
|
179
|
+
node = root
|
|
180
|
+
for part in f.path:
|
|
181
|
+
node = node.setdefault(part, {})
|
|
182
|
+
|
|
183
|
+
def finish(node: dict[str, Any]) -> Any:
|
|
184
|
+
if not node:
|
|
185
|
+
return "..."
|
|
186
|
+
if "{json}" in node:
|
|
187
|
+
return json.dumps(finish(node["{json}"]))
|
|
188
|
+
if "{base64}" in node:
|
|
189
|
+
inner = finish(node["{base64}"])
|
|
190
|
+
text = inner if isinstance(inner, str) else json.dumps(inner)
|
|
191
|
+
return base64.b64encode(text.encode()).decode() if text != "..." else "..."
|
|
192
|
+
keys = [k for k in node if k != "[]"]
|
|
193
|
+
if not keys and "[]" in node:
|
|
194
|
+
return [finish(node["[]"])]
|
|
195
|
+
return {k: finish(node[k]) for k in keys}
|
|
196
|
+
|
|
197
|
+
return finish(root)
|