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