arnmatch 2026.1.1__tar.gz → 2026.1.3__tar.gz

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.
@@ -32,23 +32,25 @@ uv run arnmatch <arn>
32
32
  ### Core Library (`src/arnmatch/`)
33
33
 
34
34
  - `__init__.py` - Main module with `arnmatch(arn)` function and `ARN` dataclass
35
- - `arn_patterns.py` - Generated file containing compiled regex patterns indexed by service
35
+ - `arn_patterns.py` - Generated file containing compiled regex patterns indexed by service and AWS SDK services mapping
36
36
 
37
- The `arnmatch()` function splits the ARN, looks up patterns by service, and returns an `ARN` with partition, service, region, account, resource_type, and captured groups. The `resource_id` and `resource_name` properties use heuristics (prefer groups ending in "Id" or "Name").
37
+ The `arnmatch()` function splits the ARN, looks up patterns by service, and returns an `ARN` with partition, service, region, account, resource_type, and captured groups. The `resource_id` and `resource_name` properties use heuristics (prefer groups ending in "Id" or "Name"). The `aws_sdk_services` property returns boto3 client names for the service.
38
38
 
39
39
  ### Code Generation (`codegen/`)
40
40
 
41
41
  - `scraper.py` - Scrapes AWS service authorization reference pages, caches results with joblib
42
42
  - `codegen.py` - Processes resources and generates `arn_patterns.py`
43
+ - `index_sdk.py` - Maps ARN service names to AWS SDK (boto3) client names
43
44
 
44
- Data flow: AWS docs → `scraper.py` → raw resources → `codegen.py` → `codegen/build/arn_patterns.py` → (copied by `make build`) → `src/arnmatch/arn_patterns.py`
45
+ Data flow: AWS docs → `scraper.py` → raw resources → `codegen.py` + `index_sdk.py` → `codegen/build/arn_patterns.py` → (copied by `make build`) → `src/arnmatch/arn_patterns.py`
45
46
 
46
47
  ### Key Design Decisions
47
48
 
48
49
  1. **Pattern ordering**: Patterns sorted by specificity (more literal segments first) for correct matching
49
50
  2. **Service index**: O(1) lookup by service before pattern matching
50
51
  3. **Overrides in codegen.py**: `PATTERN_OVERRIDES` fixes AWS docs that use wildcards instead of capture groups; `PATTERN_INCLUDES` adds patterns not in docs (EKS k8s resources, Inspector legacy)
51
- 4. **Zero runtime dependencies**: Only codegen has external deps (requests, beautifulsoup4, joblib)
52
+ 4. **SDK service mapping**: `index_sdk.py` maps ARN service names to boto3 client names using botocore metadata (signingName/endpointPrefix), with manual overrides for edge cases and excludes for discontinued/console-only services
53
+ 5. **Zero runtime dependencies**: Only codegen has external deps (requests, beautifulsoup4, joblib, boto3)
52
54
 
53
55
  ## Build Notes
54
56
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arnmatch
3
- Version: 2026.1.1
3
+ Version: 2026.1.3
4
4
  Summary: Parse AWS ARNs into structured data (2000+ resource types)
5
5
  Author-email: Andrey Gubarev <andrey@andreygubarev.com>
6
6
  Requires-Python: >=3.10
@@ -37,6 +37,7 @@ pip install arnmatch
37
37
  ```bash
38
38
  $ uvx arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
39
39
  aws_service: lambda
40
+ aws_sdk_services: lambda
40
41
  aws_region: us-east-1
41
42
  aws_account: 123456789012
42
43
  resource_type: function
@@ -52,13 +53,14 @@ from arnmatch import arnmatch
52
53
  arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function"
53
54
  result = arnmatch(arn)
54
55
 
55
- print(result.aws_service) # lambda
56
- print(result.aws_region) # us-east-1
57
- print(result.aws_account) # 123456789012
58
- print(result.resource_type) # function
59
- print(result.resource_id) # my-function
60
- print(result.resource_name) # my-function
61
- print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
56
+ print(result.aws_service) # lambda
57
+ print(result.aws_sdk_services) # ['lambda']
58
+ print(result.aws_region) # us-east-1
59
+ print(result.aws_account) # 123456789012
60
+ print(result.resource_type) # function
61
+ print(result.resource_id) # my-function
62
+ print(result.resource_name) # my-function
63
+ print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
62
64
  ```
63
65
 
64
66
  ## API Reference
@@ -89,6 +91,7 @@ Properties:
89
91
  |----------|-------------|
90
92
  | `resource_id` | Resource identifier (prefers groups ending in `Id`, falls back to `Name`, then last group) |
91
93
  | `resource_name` | Resource name (prefers groups ending in `Name`, falls back to `resource_id`) |
94
+ | `aws_sdk_services` | List of boto3 client names for this service (e.g., `['elb', 'elbv2']` for elasticloadbalancing) |
92
95
 
93
96
  ### `ARNError`
94
97
 
@@ -29,6 +29,7 @@ pip install arnmatch
29
29
  ```bash
30
30
  $ uvx arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
31
31
  aws_service: lambda
32
+ aws_sdk_services: lambda
32
33
  aws_region: us-east-1
33
34
  aws_account: 123456789012
34
35
  resource_type: function
@@ -44,13 +45,14 @@ from arnmatch import arnmatch
44
45
  arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function"
45
46
  result = arnmatch(arn)
46
47
 
47
- print(result.aws_service) # lambda
48
- print(result.aws_region) # us-east-1
49
- print(result.aws_account) # 123456789012
50
- print(result.resource_type) # function
51
- print(result.resource_id) # my-function
52
- print(result.resource_name) # my-function
53
- print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
48
+ print(result.aws_service) # lambda
49
+ print(result.aws_sdk_services) # ['lambda']
50
+ print(result.aws_region) # us-east-1
51
+ print(result.aws_account) # 123456789012
52
+ print(result.resource_type) # function
53
+ print(result.resource_id) # my-function
54
+ print(result.resource_name) # my-function
55
+ print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
54
56
  ```
55
57
 
56
58
  ## API Reference
@@ -81,6 +83,7 @@ Properties:
81
83
  |----------|-------------|
82
84
  | `resource_id` | Resource identifier (prefers groups ending in `Id`, falls back to `Name`, then last group) |
83
85
  | `resource_name` | Resource name (prefers groups ending in `Name`, falls back to `resource_id`) |
86
+ | `aws_sdk_services` | List of boto3 client names for this service (e.g., `['elb', 'elbv2']` for elasticloadbalancing) |
84
87
 
85
88
  ### `ARNError`
86
89
 
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
 
10
10
  from scraper import AWSScraper
11
11
  from index_sdk import SDKServiceIndexer
12
+ from index_sdk_resources import SDKResourceIndexer
12
13
 
13
14
  log = logging.getLogger(__name__)
14
15
 
@@ -264,8 +265,22 @@ class CodeGenerator:
264
265
  # Write SDK services mapping
265
266
  f.write("# Auto-generated mapping: ARN service -> AWS SDK client names\n")
266
267
  f.write("AWS_SDK_SERVICES = {\n")
267
- for arn_svc, clients in sorted(sdk_services_mapping.items()):
268
- f.write(f" {arn_svc!r}: {clients!r},\n")
268
+ for arn_service, clients in sorted(sdk_services_mapping.items()):
269
+ f.write(f" {arn_service!r}: {clients!r},\n")
270
+ f.write("}\n\n")
271
+
272
+ # Write SDK default service mapping
273
+ f.write("# Default SDK for multi-SDK services\n")
274
+ f.write("AWS_SDK_SERVICES_DEFAULT = {\n")
275
+ for arn_service, sdk in sorted(SDKResourceIndexer.DEFAULT_SERVICE.items()):
276
+ f.write(f" {arn_service!r}: {sdk!r},\n")
277
+ f.write("}\n\n")
278
+
279
+ # Write SDK service overrides (resource-level)
280
+ f.write("# Resource-level SDK overrides: resource_type -> sdk_client\n")
281
+ f.write("AWS_SDK_SERVICES_OVERRIDE = {\n")
282
+ for arn_service, overrides in sorted(SDKResourceIndexer.OVERRIDE_SERVICE.items()):
283
+ f.write(f" {arn_service!r}: {overrides!r},\n")
269
284
  f.write("}\n")
270
285
 
271
286
  log.info(f"Wrote {len(resources)} patterns for {len(by_service)} services to {output_path}")
@@ -318,6 +333,10 @@ def main():
318
333
  sdk_indexer = SDKServiceIndexer()
319
334
  sdk_mapping = sdk_indexer.process(arn_services)
320
335
 
336
+ # Validate multi-SDK services have DEFAULT_SERVICE entries
337
+ sdk_resource_indexer = SDKResourceIndexer()
338
+ sdk_resource_indexer.process(sdk_mapping)
339
+
321
340
  # Generate
322
341
  BUILD_DIR.mkdir(exist_ok=True)
323
342
  generator = CodeGenerator()
@@ -0,0 +1,199 @@
1
+ """Maps ARN resource types to specific SDK clients for multi-SDK services.
2
+
3
+ Why aws_sdk_services (plural) exists alongside aws_sdk_service (singular):
4
+
5
+ Some services share identical ARN formats but are fundamentally different engines.
6
+ For example, RDS, Neptune, and DocumentDB all use the same ARN structure:
7
+ arn:aws:rds:region:account:db:instance-name
8
+
9
+ The ARN alone cannot distinguish between these engines - you need external context
10
+ (tags, API calls, or prior knowledge) to determine the correct SDK. In these cases,
11
+ aws_sdk_service returns the default (rds), while aws_sdk_services returns all
12
+ possible SDKs [rds, neptune, docdb] for the caller to disambiguate.
13
+ """
14
+
15
+
16
+ class SDKResourceIndexer:
17
+ """SDK defaults for services with multiple SDK clients.
18
+
19
+ DEFAULT_SERVICE: Service-level default where all resources use a single SDK.
20
+ Format: "arn_service" -> "sdk_client"
21
+
22
+ OVERRIDE_SERVICE: Resource-level overrides where different resources use different SDKs.
23
+ Format: "arn_service" -> {"resource_type": "sdk_client", ...}
24
+ """
25
+
26
+ # Service-level default - the SDK responsible for most resources
27
+ # (other auto-detected SDKs are typically runtime/data/query clients)
28
+ DEFAULT_SERVICE = {
29
+ "apigateway": "apigateway", # v1 REST API; v2 is HTTP/WebSocket
30
+ "appconfig": "appconfig", # appconfigdata is runtime-only
31
+ "aws-marketplace": "marketplace-catalog", # entity management
32
+ "bedrock": "bedrock", # model management
33
+ "bedrock-agentcore": "bedrock-agentcore-control", # control plane
34
+ "cassandra": "keyspaces", # keyspacesstreams is CDC
35
+ "chime": "chime", # others are specialized SDKs
36
+ "cloudhsm": "cloudhsmv2", # v1 deprecated
37
+ "cloudsearch": "cloudsearch", # domain is query-only
38
+ "connect": "connect", # contact-lens is analytics
39
+ "connect-campaigns": "connectcampaignsv2", # v1 deprecated
40
+ "dynamodb": "dynamodb", # streams is CDC
41
+ "elasticloadbalancing": "elbv2", # ALB/NLB/GLB; elb is classic
42
+ "es": "opensearch", # Elasticsearch rebranded
43
+ "execute-api": "apigatewaymanagementapi", # WebSocket management
44
+ "forecast": "forecast", # forecastquery is runtime
45
+ "greengrass": "greengrassv2", # v1 deprecated
46
+ "ivs": "ivs", # ivs-realtime is stages
47
+ "kinesisanalytics": "kinesisanalyticsv2", # v1 deprecated
48
+ "kinesisvideo": "kinesisvideo", # others are media streaming
49
+ "lex": "lexv2-models", # v1 deprecated
50
+ "mediastore": "mediastore", # data is object operations
51
+ "mgh": "mgh", # config is home region only
52
+ "partnercentral": "partnercentral-selling", # primary module
53
+ "payment-cryptography": "payment-cryptography", # data is crypto ops
54
+ "personalize": "personalize", # events/runtime are runtime
55
+ "rds": "rds", # docdb/neptune are different engines
56
+ "route53-recovery-control": "route53-recovery-control-config", # cluster is data plane
57
+ "s3": "s3", # s3control is account-level
58
+ "sagemaker": "sagemaker", # others are runtime/edge/metrics
59
+ "servicecatalog": "servicecatalog", # appregistry is separate
60
+ "ses": "sesv2", # v1 deprecated
61
+ "sms-voice": "pinpoint-sms-voice-v2", # v1 deprecated
62
+ "sso": "sso-admin", # sso is portal access
63
+ "timestream": "timestream-write", # query is queries only
64
+ "wisdom": "qconnect", # rebranded to Q Connect
65
+ }
66
+
67
+ # Resource-level overrides - only non-default SDK mappings
68
+ # Format: "arn_service" -> {"resource_type": "sdk_client", ...}
69
+ OVERRIDE_SERVICE = {
70
+ "apigateway": {
71
+ # v2 (HTTP/WebSocket API) resources
72
+ "ApiMappings": "apigatewayv2",
73
+ "ApiMapping": "apigatewayv2",
74
+ "Apis": "apigatewayv2",
75
+ "Api": "apigatewayv2",
76
+ "Cors": "apigatewayv2",
77
+ "ExportedAPI": "apigatewayv2",
78
+ "Integrations": "apigatewayv2",
79
+ "RouteRequestParameter": "apigatewayv2",
80
+ "RouteResponses": "apigatewayv2",
81
+ "RouteResponse": "apigatewayv2",
82
+ "RouteSettings": "apigatewayv2",
83
+ "Routes": "apigatewayv2",
84
+ "Route": "apigatewayv2",
85
+ },
86
+ "bedrock": {
87
+ # bedrock-agent resources
88
+ "agent-alias": "bedrock-agent",
89
+ "agent": "bedrock-agent",
90
+ "default-prompt-router": "bedrock-agent",
91
+ "flow-alias": "bedrock-agent",
92
+ "flow-execution": "bedrock-agent",
93
+ "flow": "bedrock-agent",
94
+ "knowledge-base": "bedrock-agent",
95
+ "prompt-router": "bedrock-agent",
96
+ "prompt-version": "bedrock-agent",
97
+ "prompt": "bedrock-agent",
98
+ # bedrock-agent-runtime
99
+ "session": "bedrock-agent-runtime",
100
+ # bedrock-runtime
101
+ "async-invoke": "bedrock-runtime",
102
+ },
103
+ "chime": {
104
+ "meeting": "chime-sdk-meetings",
105
+ "app-instance": "chime-sdk-identity",
106
+ "app-instance-bot": "chime-sdk-identity",
107
+ "app-instance-user": "chime-sdk-identity",
108
+ "channel": "chime-sdk-messaging",
109
+ "channel-flow": "chime-sdk-messaging",
110
+ "media-insights-pipeline-configuration": "chime-sdk-media-pipelines",
111
+ "media-pipeline": "chime-sdk-media-pipelines",
112
+ "media-pipeline-kinesis-video-stream-pool": "chime-sdk-media-pipelines",
113
+ "sip-media-application": "chime-sdk-voice",
114
+ "voice-connector-group": "chime-sdk-voice",
115
+ "voice-connector": "chime-sdk-voice",
116
+ "voice-profile": "chime-sdk-voice",
117
+ "voice-profile-domain": "chime-sdk-voice",
118
+ },
119
+ "dynamodb": {
120
+ "stream": "dynamodbstreams",
121
+ },
122
+ "elasticloadbalancing": {
123
+ "loadbalancer": "elb", # classic
124
+ },
125
+ "greengrass": {
126
+ # v1 resources (v2 is default)
127
+ "bulkDeployment": "greengrass",
128
+ "certificateAuthority": "greengrass",
129
+ "connectivityInfo": "greengrass",
130
+ "connectorDefinitionVersion": "greengrass",
131
+ "connectorDefinition": "greengrass",
132
+ "coreDefinitionVersion": "greengrass",
133
+ "coreDefinition": "greengrass",
134
+ "deviceDefinitionVersion": "greengrass",
135
+ "deviceDefinition": "greengrass",
136
+ "functionDefinitionVersion": "greengrass",
137
+ "functionDefinition": "greengrass",
138
+ "groupVersion": "greengrass",
139
+ "group": "greengrass",
140
+ "loggerDefinitionVersion": "greengrass",
141
+ "loggerDefinition": "greengrass",
142
+ "resourceDefinitionVersion": "greengrass",
143
+ "resourceDefinition": "greengrass",
144
+ "subscriptionDefinitionVersion": "greengrass",
145
+ "subscriptionDefinition": "greengrass",
146
+ "thingRuntimeConfig": "greengrass",
147
+ },
148
+ "ivs": {
149
+ # ivs-realtime resources
150
+ "Composition": "ivs-realtime",
151
+ "Encoder-Configuration": "ivs-realtime",
152
+ "Ingest-Configuration": "ivs-realtime",
153
+ "Public-Key": "ivs-realtime",
154
+ "Stage": "ivs-realtime",
155
+ "Storage-Configuration": "ivs-realtime",
156
+ },
157
+ "lex": {
158
+ # v1 resources
159
+ "channel": "lex-models",
160
+ "intent version": "lex-models",
161
+ "slottype version": "lex-models",
162
+ },
163
+ "mediastore": {
164
+ "folder": "mediastore-data",
165
+ "object": "mediastore-data",
166
+ },
167
+ "partnercentral": {
168
+ "BenefitAllocation": "partnercentral-benefits",
169
+ "BenefitApplication": "partnercentral-benefits",
170
+ "Benefit": "partnercentral-benefits",
171
+ "ChannelHandshake": "partnercentral-channel",
172
+ "ProgramManagementAccount": "partnercentral-account",
173
+ },
174
+ "s3": {
175
+ "accessgrant": "s3control",
176
+ "accessgrantslocation": "s3control",
177
+ "accessgrantsinstance": "s3control",
178
+ "accesspoint": "s3control",
179
+ "accesspointobject": "s3control",
180
+ "job": "s3control",
181
+ "multiregionaccesspoint": "s3control",
182
+ "multiregionaccesspointrequestarn": "s3control",
183
+ "storagelensconfiguration": "s3control",
184
+ "storagelensgroup": "s3control",
185
+ },
186
+ "servicecatalog": {
187
+ "Application": "servicecatalog-appregistry",
188
+ "AttributeGroup": "servicecatalog-appregistry",
189
+ },
190
+ }
191
+
192
+ def process(self, sdk_mapping):
193
+ """Validate all multi-SDK services have a DEFAULT_SERVICE entry."""
194
+ missing = {}
195
+ for arn_service, sdks in sdk_mapping.items():
196
+ if len(sdks) > 1 and arn_service not in self.DEFAULT_SERVICE:
197
+ missing[arn_service] = sdks
198
+ if missing:
199
+ raise RuntimeError(f"Missing DEFAULT_SERVICE for multi-SDK services: {missing}")
@@ -1,12 +1,17 @@
1
1
  """ARN pattern matching using regex patterns."""
2
2
 
3
- __version__ = "2026.01.1"
3
+ __version__ = "2026.01.3"
4
4
 
5
5
  import sys
6
6
  from dataclasses import dataclass
7
7
  from functools import cached_property
8
8
 
9
- from .arn_patterns import ARN_PATTERNS, AWS_SDK_SERVICES
9
+ from .arn_patterns import (
10
+ ARN_PATTERNS,
11
+ AWS_SDK_SERVICES,
12
+ AWS_SDK_SERVICES_DEFAULT,
13
+ AWS_SDK_SERVICES_OVERRIDE,
14
+ )
10
15
 
11
16
  # Standard groups that are not resource-specific
12
17
  STANDARD_GROUPS = {"Partition", "Region", "Account"}
@@ -78,7 +83,7 @@ class ARN:
78
83
  return self.resource_id
79
84
 
80
85
  @cached_property
81
- def aws_sdk_services(self):
86
+ def aws_sdk_services(self) -> list[str]:
82
87
  """Get AWS SDK (boto3) client names for this resource's service.
83
88
 
84
89
  Returns list of client names that can interact with this resource type.
@@ -88,6 +93,26 @@ class ARN:
88
93
  """
89
94
  return AWS_SDK_SERVICES.get(self.aws_service, [])
90
95
 
96
+ @cached_property
97
+ def aws_sdk_service(self) -> str | None:
98
+ """Get the AWS SDK (boto3) client name for this resource.
99
+
100
+ Returns single client name. Checks resource-level overrides first,
101
+ then falls back to service default. Returns None if no SDK exists.
102
+ """
103
+ # Check resource-level override
104
+ overrides = AWS_SDK_SERVICES_OVERRIDE.get(self.aws_service)
105
+ if overrides and self.resource_type in overrides:
106
+ return overrides[self.resource_type]
107
+
108
+ # Fall back to service-level
109
+ sdks = self.aws_sdk_services
110
+ if len(sdks) == 1:
111
+ return sdks[0]
112
+ if len(sdks) > 1:
113
+ return AWS_SDK_SERVICES_DEFAULT.get(self.aws_service)
114
+ return None
115
+
91
116
 
92
117
  def arnmatch(arn: str) -> ARN:
93
118
  """Match ARN against patterns.
@@ -132,6 +157,8 @@ def main() -> None:
132
157
  try:
133
158
  result = arnmatch(arn)
134
159
  print(f"aws_service: {result.aws_service}")
160
+ print(f"aws_sdk_service: {result.aws_sdk_service}")
161
+ print(f"aws_sdk_services: {','.join(result.aws_sdk_services)}")
135
162
  print(f"aws_region: {result.aws_region}")
136
163
  print(f"aws_account: {result.aws_account}")
137
164
  print(f"resource_type: {result.resource_type}")
@@ -3155,3 +3155,59 @@ AWS_SDK_SERVICES = {
3155
3155
  'workspaces-web': ['workspaces-web'],
3156
3156
  'xray': ['xray'],
3157
3157
  }
3158
+
3159
+ # Default SDK for multi-SDK services
3160
+ AWS_SDK_SERVICES_DEFAULT = {
3161
+ 'apigateway': 'apigateway',
3162
+ 'appconfig': 'appconfig',
3163
+ 'aws-marketplace': 'marketplace-catalog',
3164
+ 'bedrock': 'bedrock',
3165
+ 'bedrock-agentcore': 'bedrock-agentcore-control',
3166
+ 'cassandra': 'keyspaces',
3167
+ 'chime': 'chime',
3168
+ 'cloudhsm': 'cloudhsmv2',
3169
+ 'cloudsearch': 'cloudsearch',
3170
+ 'connect': 'connect',
3171
+ 'connect-campaigns': 'connectcampaignsv2',
3172
+ 'dynamodb': 'dynamodb',
3173
+ 'elasticloadbalancing': 'elbv2',
3174
+ 'es': 'opensearch',
3175
+ 'execute-api': 'apigatewaymanagementapi',
3176
+ 'forecast': 'forecast',
3177
+ 'greengrass': 'greengrassv2',
3178
+ 'ivs': 'ivs',
3179
+ 'kinesisanalytics': 'kinesisanalyticsv2',
3180
+ 'kinesisvideo': 'kinesisvideo',
3181
+ 'lex': 'lexv2-models',
3182
+ 'mediastore': 'mediastore',
3183
+ 'mgh': 'mgh',
3184
+ 'partnercentral': 'partnercentral-selling',
3185
+ 'payment-cryptography': 'payment-cryptography',
3186
+ 'personalize': 'personalize',
3187
+ 'rds': 'rds',
3188
+ 'route53-recovery-control': 'route53-recovery-control-config',
3189
+ 's3': 's3',
3190
+ 'sagemaker': 'sagemaker',
3191
+ 'servicecatalog': 'servicecatalog',
3192
+ 'ses': 'sesv2',
3193
+ 'sms-voice': 'pinpoint-sms-voice-v2',
3194
+ 'sso': 'sso-admin',
3195
+ 'timestream': 'timestream-write',
3196
+ 'wisdom': 'qconnect',
3197
+ }
3198
+
3199
+ # Resource-level SDK overrides: resource_type -> sdk_client
3200
+ AWS_SDK_SERVICES_OVERRIDE = {
3201
+ 'apigateway': {'ApiMappings': 'apigatewayv2', 'ApiMapping': 'apigatewayv2', 'Apis': 'apigatewayv2', 'Api': 'apigatewayv2', 'Cors': 'apigatewayv2', 'ExportedAPI': 'apigatewayv2', 'Integrations': 'apigatewayv2', 'RouteRequestParameter': 'apigatewayv2', 'RouteResponses': 'apigatewayv2', 'RouteResponse': 'apigatewayv2', 'RouteSettings': 'apigatewayv2', 'Routes': 'apigatewayv2', 'Route': 'apigatewayv2'},
3202
+ 'bedrock': {'agent-alias': 'bedrock-agent', 'agent': 'bedrock-agent', 'default-prompt-router': 'bedrock-agent', 'flow-alias': 'bedrock-agent', 'flow-execution': 'bedrock-agent', 'flow': 'bedrock-agent', 'knowledge-base': 'bedrock-agent', 'prompt-router': 'bedrock-agent', 'prompt-version': 'bedrock-agent', 'prompt': 'bedrock-agent', 'session': 'bedrock-agent-runtime', 'async-invoke': 'bedrock-runtime'},
3203
+ 'chime': {'meeting': 'chime-sdk-meetings', 'app-instance': 'chime-sdk-identity', 'app-instance-bot': 'chime-sdk-identity', 'app-instance-user': 'chime-sdk-identity', 'channel': 'chime-sdk-messaging', 'channel-flow': 'chime-sdk-messaging', 'media-insights-pipeline-configuration': 'chime-sdk-media-pipelines', 'media-pipeline': 'chime-sdk-media-pipelines', 'media-pipeline-kinesis-video-stream-pool': 'chime-sdk-media-pipelines', 'sip-media-application': 'chime-sdk-voice', 'voice-connector-group': 'chime-sdk-voice', 'voice-connector': 'chime-sdk-voice', 'voice-profile': 'chime-sdk-voice', 'voice-profile-domain': 'chime-sdk-voice'},
3204
+ 'dynamodb': {'stream': 'dynamodbstreams'},
3205
+ 'elasticloadbalancing': {'loadbalancer': 'elb'},
3206
+ 'greengrass': {'bulkDeployment': 'greengrass', 'certificateAuthority': 'greengrass', 'connectivityInfo': 'greengrass', 'connectorDefinitionVersion': 'greengrass', 'connectorDefinition': 'greengrass', 'coreDefinitionVersion': 'greengrass', 'coreDefinition': 'greengrass', 'deviceDefinitionVersion': 'greengrass', 'deviceDefinition': 'greengrass', 'functionDefinitionVersion': 'greengrass', 'functionDefinition': 'greengrass', 'groupVersion': 'greengrass', 'group': 'greengrass', 'loggerDefinitionVersion': 'greengrass', 'loggerDefinition': 'greengrass', 'resourceDefinitionVersion': 'greengrass', 'resourceDefinition': 'greengrass', 'subscriptionDefinitionVersion': 'greengrass', 'subscriptionDefinition': 'greengrass', 'thingRuntimeConfig': 'greengrass'},
3207
+ 'ivs': {'Composition': 'ivs-realtime', 'Encoder-Configuration': 'ivs-realtime', 'Ingest-Configuration': 'ivs-realtime', 'Public-Key': 'ivs-realtime', 'Stage': 'ivs-realtime', 'Storage-Configuration': 'ivs-realtime'},
3208
+ 'lex': {'channel': 'lex-models', 'intent version': 'lex-models', 'slottype version': 'lex-models'},
3209
+ 'mediastore': {'folder': 'mediastore-data', 'object': 'mediastore-data'},
3210
+ 'partnercentral': {'BenefitAllocation': 'partnercentral-benefits', 'BenefitApplication': 'partnercentral-benefits', 'Benefit': 'partnercentral-benefits', 'ChannelHandshake': 'partnercentral-channel', 'ProgramManagementAccount': 'partnercentral-account'},
3211
+ 's3': {'accessgrant': 's3control', 'accessgrantslocation': 's3control', 'accessgrantsinstance': 's3control', 'accesspoint': 's3control', 'accesspointobject': 's3control', 'job': 's3control', 'multiregionaccesspoint': 's3control', 'multiregionaccesspointrequestarn': 's3control', 'storagelensconfiguration': 's3control', 'storagelensgroup': 's3control'},
3212
+ 'servicecatalog': {'Application': 'servicecatalog-appregistry', 'AttributeGroup': 'servicecatalog-appregistry'},
3213
+ }
@@ -10,12 +10,20 @@ def test_acm():
10
10
  assert result.resource_type == "certificate"
11
11
  assert result.attributes["CertificateId"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
12
12
  assert result.aws_sdk_services == ["acm"]
13
+ assert result.aws_sdk_service == "acm"
13
14
 
14
15
 
15
16
  def test_apigateway():
17
+ # v1 REST API (default)
16
18
  result = arnmatch("arn:aws:apigateway:us-east-1::/restapis/abc123def4")
17
19
  assert result.resource_type == "RestApi"
18
20
  assert result.attributes["RestApiId"] == "abc123def4"
21
+ assert result.aws_sdk_service == "apigateway"
22
+
23
+ # v2 HTTP API (override)
24
+ result = arnmatch("arn:aws:apigateway:us-east-1::/apis/abc123def4")
25
+ assert result.resource_type == "Api"
26
+ assert result.aws_sdk_service == "apigatewayv2"
19
27
 
20
28
 
21
29
  def test_athena():
@@ -100,9 +108,18 @@ def test_datasync():
100
108
 
101
109
 
102
110
  def test_dynamodb():
111
+ # table (default)
103
112
  result = arnmatch("arn:aws:dynamodb:us-east-1:012345678901:table/table1")
104
113
  assert result.resource_type == "table"
105
114
  assert result.attributes["TableName"] == "table1"
115
+ assert result.aws_sdk_service == "dynamodb"
116
+
117
+ # stream (override)
118
+ result = arnmatch(
119
+ "arn:aws:dynamodb:us-east-1:012345678901:table/table1/stream/2024-01-01T00:00:00.000"
120
+ )
121
+ assert result.resource_type == "stream"
122
+ assert result.aws_sdk_service == "dynamodbstreams"
106
123
 
107
124
 
108
125
  def test_ec2():
@@ -220,30 +237,33 @@ def test_elasticfilesystem():
220
237
 
221
238
 
222
239
  def test_elasticloadbalancing():
223
- # Classic LB
240
+ # Classic LB (override)
224
241
  result = arnmatch(
225
242
  "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/a0123456789abcdef0123456789abcde"
226
243
  )
227
244
  assert result.resource_type == "loadbalancer"
228
245
  assert result.attributes["LoadBalancerName"] == "a0123456789abcdef0123456789abcde"
246
+ assert result.aws_sdk_service == "elb"
229
247
 
230
- # ALB
248
+ # ALB (default)
231
249
  result = arnmatch(
232
250
  "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/app/alb-application-lb-name/0123456789abcdef"
233
251
  )
234
252
  assert result.resource_type == "loadbalancer/app/"
235
253
  assert result.attributes["LoadBalancerName"] == "alb-application-lb-name"
236
254
  assert result.attributes["LoadBalancerId"] == "0123456789abcdef"
255
+ assert result.aws_sdk_service == "elbv2"
237
256
 
238
- # NLB
257
+ # NLB (default)
239
258
  result = arnmatch(
240
259
  "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/net/nlb-network-load-balancer/0123456789abcdef"
241
260
  )
242
261
  assert result.resource_type == "loadbalancer/net/"
243
262
  assert result.attributes["LoadBalancerName"] == "nlb-network-load-balancer"
244
263
  assert result.attributes["LoadBalancerId"] == "0123456789abcdef"
264
+ assert result.aws_sdk_service == "elbv2"
245
265
 
246
- # Target group
266
+ # Target group (default)
247
267
  result = arnmatch(
248
268
  "arn:aws:elasticloadbalancing:us-east-1:012345678901:targetgroup/target-grp-1/0123456789abcdef"
249
269
  )
@@ -252,6 +272,7 @@ def test_elasticloadbalancing():
252
272
  assert result.attributes["TargetGroupId"] == "0123456789abcdef"
253
273
  # elasticloadbalancing maps to multiple SDK clients
254
274
  assert result.aws_sdk_services == ["elb", "elbv2"]
275
+ assert result.aws_sdk_service == "elbv2"
255
276
 
256
277
 
257
278
  def test_es():
@@ -364,10 +385,17 @@ def test_route53():
364
385
 
365
386
 
366
387
  def test_s3():
388
+ # bucket (default)
367
389
  result = arnmatch("arn:aws:s3:::example-bucket-01")
368
390
  assert result.resource_type == "bucket"
369
391
  assert result.attributes["BucketName"] == "example-bucket-01"
370
392
  assert result.aws_sdk_services == ["s3", "s3control"]
393
+ assert result.aws_sdk_service == "s3"
394
+
395
+ # accesspoint (override)
396
+ result = arnmatch("arn:aws:s3:us-east-1:012345678901:accesspoint/my-access-point")
397
+ assert result.resource_type == "accesspoint"
398
+ assert result.aws_sdk_service == "s3control"
371
399
 
372
400
 
373
401
  def test_secretsmanager():
File without changes
File without changes
File without changes
File without changes
File without changes