arnmatch 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.
arnmatch/__init__.py ADDED
@@ -0,0 +1,131 @@
1
+ """ARN pattern matching using regex patterns."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ import sys
6
+ from dataclasses import dataclass
7
+ from functools import cached_property
8
+
9
+ from .arn_patterns import ARN_PATTERNS
10
+
11
+ # Standard groups that are not resource-specific
12
+ STANDARD_GROUPS = {"Partition", "Region", "Account"}
13
+
14
+
15
+ class ARNMatchError(ValueError):
16
+ """Raised when ARN cannot be matched."""
17
+
18
+ pass
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ARNMatch:
23
+ """Result of matching an ARN against patterns."""
24
+
25
+ partition: str
26
+ service: str
27
+ region: str
28
+ account: str
29
+ resource_type: str # canonical type (from AWS docs)
30
+ resource_type_aliases: list[str] # all known names including Resource Explorer
31
+ groups: dict[str, str]
32
+
33
+ @cached_property
34
+ def resource_id(self) -> str:
35
+ """Extract resource ID using heuristics.
36
+
37
+ Priority (from end, more specific groups come last):
38
+ 1. Group ending with 'Id' (InstanceId, CertificateId, KeyId)
39
+ 2. Group ending with 'Name' as fallback
40
+ 3. Last non-standard group
41
+ """
42
+ resource_groups = list(self.groups.items())
43
+ resource_groups = [(k, v) for k, v in resource_groups if k not in STANDARD_GROUPS]
44
+
45
+ # Look for *Id (from end)
46
+ for key, value in reversed(resource_groups):
47
+ if key.endswith("Id"):
48
+ return value
49
+
50
+ # Fall back to *Name (from end)
51
+ for key, value in reversed(resource_groups):
52
+ if key.endswith("Name"):
53
+ return value
54
+
55
+ # Last resort: last group value
56
+ if resource_groups:
57
+ return resource_groups[-1][1]
58
+
59
+ return ""
60
+
61
+ @cached_property
62
+ def resource_name(self) -> str:
63
+ """Extract resource name using heuristics.
64
+
65
+ Priority (from end, more specific groups come last):
66
+ 1. Group ending with 'Name' (FunctionName, BucketName, StackName)
67
+ 2. Falls back to resource_id
68
+ """
69
+ resource_groups = list(self.groups.items())
70
+ resource_groups = [(k, v) for k, v in resource_groups if k not in STANDARD_GROUPS]
71
+
72
+ # Look for *Name (from end)
73
+ for key, value in reversed(resource_groups):
74
+ if key.endswith("Name"):
75
+ return value
76
+
77
+ # Fall back to resource_id
78
+ return self.resource_id
79
+
80
+
81
+ def arnmatch(arn: str) -> ARNMatch:
82
+ """Match ARN against patterns.
83
+
84
+ Returns ARNMatch with all captured groups.
85
+
86
+ Raises:
87
+ ARNMatchError: If ARN cannot be matched.
88
+ """
89
+ parts = arn.split(":", 5)
90
+ if len(parts) != 6 or parts[0] != "arn":
91
+ raise ARNMatchError(f"Invalid ARN format: {arn}")
92
+
93
+ _, partition, service, region, account, _ = parts
94
+
95
+ if service not in ARN_PATTERNS:
96
+ raise ARNMatchError(f"Unknown service: {service}")
97
+
98
+ for regex, type_names in ARN_PATTERNS[service]:
99
+ match = regex.match(arn)
100
+ if match:
101
+ return ARNMatch(
102
+ partition=partition,
103
+ service=service,
104
+ region=region,
105
+ account=account,
106
+ resource_type=type_names[0], # canonical
107
+ resource_type_aliases=type_names, # all known names
108
+ groups=match.groupdict(),
109
+ )
110
+
111
+ raise ARNMatchError(f"No pattern matched ARN: {arn}")
112
+
113
+
114
+ def main() -> None:
115
+ """CLI entry point."""
116
+ if len(sys.argv) < 2:
117
+ print("Usage: arnmatch <arn>", file=sys.stderr)
118
+ sys.exit(1)
119
+
120
+ arn = sys.argv[1]
121
+ try:
122
+ result = arnmatch(arn)
123
+ print(f"service: {result.service}")
124
+ print(f"region: {result.region}")
125
+ print(f"account: {result.account}")
126
+ print(f"resource_type: {result.resource_type}")
127
+ print(f"resource_id: {result.resource_id}")
128
+ print(f"resource_name: {result.resource_name}")
129
+ except ARNMatchError as e:
130
+ print(f"Error: {e}", file=sys.stderr)
131
+ sys.exit(1)