arnmatch 2026.2.0__tar.gz → 2026.2.2__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.
Files changed (47) hide show
  1. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/.github/workflows/workflow.yml +1 -1
  2. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/CLAUDE.md +12 -11
  3. arnmatch-2026.2.2/LICENSE +182 -0
  4. arnmatch-2026.2.2/PKG-INFO +144 -0
  5. arnmatch-2026.2.2/README.md +134 -0
  6. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/pyproject.toml +2 -0
  7. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/src/arnmatch/__init__.py +23 -1
  8. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/tests/test_arnmatch.py +30 -0
  9. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/uv.lock +81 -0
  10. arnmatch-2026.2.0/AGENTS.md +0 -299
  11. arnmatch-2026.2.0/PKG-INFO +0 -121
  12. arnmatch-2026.2.0/README.md +0 -113
  13. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/.gitignore +0 -0
  14. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/.python-version +0 -0
  15. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/Makefile +0 -0
  16. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/.gitignore +0 -0
  17. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/Makefile +0 -0
  18. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/cache/CloudFormationResourceSpecification.json +0 -0
  19. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/cache/CloudFormationResources.json +0 -0
  20. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/cache/CloudFormationServices.json +0 -0
  21. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/cache/SDKServices.json +0 -0
  22. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/codegen.py +0 -0
  23. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/codegen_python.py +0 -0
  24. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/index_arn.py +0 -0
  25. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/index_cfn.py +0 -0
  26. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/index_cfn_resources.py +0 -0
  27. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/index_sdk.py +0 -0
  28. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/index_sdk_resources.py +0 -0
  29. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/arn_excludes.json +0 -0
  30. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/arn_excludes_resources.json +0 -0
  31. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/arn_includes.json +0 -0
  32. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/arn_overrides.json +0 -0
  33. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/cfn_excludes.json +0 -0
  34. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/cfn_overrides.json +0 -0
  35. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/cfn_resources_excludes.json +0 -0
  36. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/cfn_resources_overrides.json +0 -0
  37. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/lowercase_transforms.json +0 -0
  38. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/sdk_excludes.json +0 -0
  39. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/sdk_overrides.json +0 -0
  40. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/sdk_resources_defaults.json +0 -0
  41. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/rules/sdk_resources_overrides.json +0 -0
  42. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/scraper.py +0 -0
  43. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/transform.py +0 -0
  44. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/codegen/utils.py +0 -0
  45. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/docs/CloudFormation.md +0 -0
  46. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/src/arnmatch/arn_patterns.py +0 -0
  47. {arnmatch-2026.2.0 → arnmatch-2026.2.2}/tests/integration/test_resource_explorer.py +0 -0
@@ -3,7 +3,7 @@ name: Release
3
3
  on:
4
4
  push:
5
5
  tags:
6
- - "v*"
6
+ - "20[0-9][0-9].[0-9]*.[0-9]*"
7
7
 
8
8
  jobs:
9
9
  release:
@@ -12,14 +12,13 @@ arnmatch is a zero-dependency Python library that parses AWS ARNs into structure
12
12
  make lint # Run ruff linter
13
13
  make test # Run pytest tests
14
14
  make check # Run lint and test
15
+ make generate # Regenerate patterns from AWS docs (runs codegen pipeline)
15
16
  make build # Copy generated patterns to src + build wheel/tarball
16
17
  make publish # Build and upload to PyPI
17
18
  make clean # Remove build artifacts
18
- ```
19
19
 
20
- Run codegen to regenerate patterns from AWS docs:
21
- ```bash
22
- uv run codegen/codegen.py
20
+ # Integration test (requires AWS credentials + Resource Explorer)
21
+ make test-integration
23
22
  ```
24
23
 
25
24
  Test locally:
@@ -32,30 +31,32 @@ uv run arnmatch <arn>
32
31
  ### Core Library (`src/arnmatch/`)
33
32
 
34
33
  - `__init__.py` - Main module with `arnmatch(arn)` function and `ARN` dataclass
35
- - `arn_patterns.py` - Generated file containing compiled regex patterns indexed by service and AWS SDK services mapping
34
+ - `arn_patterns.py` - Generated file containing compiled regex patterns indexed by service, SDK services mapping, and CloudFormation resource types
36
35
 
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.
36
+ 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. Key properties: `resource_id`/`resource_name` use heuristics (prefer groups ending in "Id" or "Name"), `aws_sdk_service` returns the specific boto3 client for this resource type, `cloudformation_resource` returns the CFN type (e.g., `AWS::Lambda::Function`).
38
37
 
39
38
  ### Code Generation (`codegen/`)
40
39
 
41
40
  - `scraper.py` - Scrapes AWS service authorization reference pages, caches results with joblib
42
- - `codegen.py` - Processes resources and generates `arn_patterns.py`
43
- - `index_sdk.py` - Maps ARN service names to AWS SDK (boto3) client names
41
+ - `codegen.py` - Main orchestrator: processes resources and generates `arn_patterns.yaml`
42
+ - `codegen_python.py` - Converts YAML to Python with compiled regex patterns
43
+ - `index_*.py` - Indexers for ARN patterns, SDK clients, and CloudFormation types
44
+ - `transform.py` - Normalizes resource type names (e.g., `loadbalancer/app/` → `loadbalancer-app`)
44
45
 
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`
46
+ Data flow: AWS docs → `scraper.py` → raw resources → `codegen.py` `arn_patterns.yaml` → `codegen_python.py` → `codegen/build/arn_patterns.py` → (copied by `make build`) → `src/arnmatch/arn_patterns.py`
46
47
 
47
48
  ### Key Design Decisions
48
49
 
49
50
  1. **Pattern ordering**: Patterns sorted by specificity (more literal segments first) for correct matching
50
51
  2. **Service index**: O(1) lookup by service before pattern matching
51
52
  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)
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
+ 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. Rules are stored in `codegen/rules/*.json`
53
54
  5. **Zero runtime dependencies**: Only codegen has external deps (requests, beautifulsoup4, joblib, boto3)
54
55
 
55
56
  ## Build Notes
56
57
 
57
58
  - Uses `uv` for package management (not pip)
58
59
  - Build system is hatchling with dynamic version from `src/arnmatch/__init__.py`
59
- - **Versioning**: CalVer format `YYYY.0M.MICRO` (e.g., `2026.01.0`)
60
+ - **Versioning**: CalVer format `YYYY.MM.MICRO` (e.g., `2026.2.0`)
60
61
  - Always run `make build` before publishing to ensure patterns are current
61
62
  - Scraper cache lives in `.cache/` - delete if AWS docs change significantly
@@ -0,0 +1,182 @@
1
+ Copyright 2026 Andrey Gubarev
2
+
3
+ Apache License
4
+ Version 2.0, January 2004
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction, and
12
+ distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ "Licensor" shall mean the copyright owner or entity authorized by the
15
+ copyright owner that is granting the License.
16
+
17
+ "Legal Entity" shall mean the union of the acting entity and all other
18
+ entities that control, are controlled by, or are under common control with
19
+ that entity. For the purposes of this definition, "control" means (i) the
20
+ power, direct or indirect, to cause the direction or management of such
21
+ entity, whether by contract or otherwise, or (ii) ownership of fifty percent
22
+ (50%) or more of the outstanding shares, or (iii) beneficial ownership of
23
+ such entity.
24
+
25
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
26
+ permissions granted by this License.
27
+
28
+ "Source" form shall mean the preferred form for making modifications,
29
+ including but not limited to software source code, documentation source,
30
+ and configuration files.
31
+
32
+ "Object" form shall mean any form resulting from mechanical transformation
33
+ or translation of a Source form, including but not limited to compiled
34
+ object code, generated documentation, and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or Object form,
37
+ made available under the License, as indicated by a copyright notice that
38
+ is included in or attached to the work (an example is provided in the
39
+ Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object form,
42
+ that is based on (or derived from) the Work and for which the editorial
43
+ revisions, annotations, elaborations, or other modifications represent, as
44
+ a whole, an original work of authorship. For the purposes of this License,
45
+ Derivative Works shall not include works that remain separable from, or
46
+ merely link (or bind by name) to the interfaces of, the Work and Derivative
47
+ Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including the original
50
+ version of the Work and any modifications or additions to that Work or
51
+ Derivative Works thereof, that is intentionally submitted to Licensor for
52
+ inclusion in the Work by the copyright owner or by an individual or Legal
53
+ Entity authorized to submit on behalf of the copyright owner. For the
54
+ purposes of this definition, "submitted" means any form of electronic,
55
+ verbal, or written communication sent to the Licensor or its
56
+ representatives, including but not limited to communication on electronic
57
+ mailing lists, source code control systems, and issue tracking systems that
58
+ are managed by, or on behalf of, the Licensor for the purpose of discussing
59
+ and improving the Work, but excluding communication that is conspicuously
60
+ marked or otherwise designated in writing by the copyright owner as
61
+ "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity on
64
+ behalf of whom a Contribution has been received by Licensor and subsequently
65
+ incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License.
68
+
69
+ Subject to the terms and conditions of this License, each Contributor hereby
70
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
71
+ irrevocable copyright license to reproduce, prepare Derivative Works of,
72
+ publicly display, publicly perform, sublicense, and distribute the Work and
73
+ such Derivative Works in Source or Object form.
74
+
75
+ 3. Grant of Patent License.
76
+
77
+ Subject to the terms and conditions of this License, each Contributor hereby
78
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
79
+ irrevocable (except as stated in this section) patent license to make, have
80
+ made, use, offer to sell, sell, import, and otherwise transfer the Work,
81
+ where such license applies only to those patent claims licensable by such
82
+ Contributor that are necessarily infringed by their Contribution(s) alone
83
+ or by combination of their Contribution(s) with the Work to which such
84
+ Contribution(s) was submitted. If You institute patent litigation against
85
+ any entity (including a cross-claim or counterclaim in a lawsuit) alleging
86
+ that the Work or a Contribution incorporated within the Work constitutes
87
+ direct or contributory patent infringement, then any patent licenses granted
88
+ to You under this License for that Work shall terminate as of the date such
89
+ litigation is filed.
90
+
91
+ 4. Redistribution.
92
+
93
+ You may reproduce and distribute copies of the Work or Derivative Works
94
+ thereof in any medium, with or without modifications, and in Source or
95
+ Object form, provided that You meet the following conditions:
96
+
97
+ (a) You must give any other recipients of the Work or Derivative Works a
98
+ copy of this License; and
99
+
100
+ (b) You must cause any modified files to carry prominent notices stating
101
+ that You changed the files; and
102
+
103
+ (c) You must retain, in the Source form of any Derivative Works that You
104
+ distribute, all copyright, patent, trademark, and attribution notices
105
+ from the Source form of the Work, excluding those notices that do not
106
+ pertain to any part of the Derivative Works; and
107
+
108
+ (d) If the Work includes a "NOTICE" text file as part of its distribution,
109
+ then any Derivative Works that You distribute must include a readable
110
+ copy of the attribution notices contained within such NOTICE file,
111
+ excluding those notices that do not pertain to any part of the
112
+ Derivative Works, in at least one of the following places: within a
113
+ NOTICE text file distributed as part of the Derivative Works; within
114
+ the Source form or documentation, if provided along with the Derivative
115
+ Works; or, within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents of the
117
+ NOTICE file are for informational purposes only and do not modify the
118
+ License. You may add Your own attribution notices within Derivative
119
+ Works that You distribute, alongside or as an addendum to the NOTICE
120
+ text from the Work, provided that such additional attribution notices
121
+ cannot be construed as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and may
124
+ provide additional or different license terms and conditions for use,
125
+ reproduction, or distribution of Your modifications, or for any such
126
+ Derivative Works as a whole, provided Your use, reproduction, and
127
+ distribution of the Work otherwise complies with the conditions stated
128
+ in this License.
129
+
130
+ 5. Submission of Contributions.
131
+
132
+ Unless You explicitly state otherwise, any Contribution intentionally
133
+ submitted for inclusion in the Work by You to the Licensor shall be under
134
+ the terms and conditions of this License, without any additional terms or
135
+ conditions. Notwithstanding the above, nothing herein shall supersede or
136
+ modify the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks.
140
+
141
+ This License does not grant permission to use the trade names, trademarks,
142
+ service marks, or product names of the Licensor, except as required for
143
+ reasonable and customary use in describing the origin of the Work and
144
+ reproducing the content of the NOTICE file.
145
+
146
+ 7. Disclaimer of Warranty.
147
+
148
+ Unless required by applicable law or agreed to in writing, Licensor provides
149
+ the Work (and each Contributor provides its Contributions) on an "AS IS"
150
+ BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
151
+ implied, including, without limitation, any warranties or conditions of
152
+ TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
153
+ PURPOSE. You are solely responsible for determining the appropriateness of
154
+ using or redistributing the Work and assume any risks associated with Your
155
+ exercise of permissions under this License.
156
+
157
+ 8. Limitation of Liability.
158
+
159
+ In no event and under no legal theory, whether in tort (including
160
+ negligence), contract, or otherwise, unless required by applicable law
161
+ (such as deliberate and grossly negligent acts) or agreed to in writing,
162
+ shall any Contributor be liable to You for damages, including any direct,
163
+ indirect, special, incidental, or consequential damages of any character
164
+ arising as a result of this License or out of the use or inability to use
165
+ the Work (including but not limited to damages for loss of goodwill, work
166
+ stoppage, computer failure or malfunction, or any and all other commercial
167
+ damages or losses), even if such Contributor has been advised of the
168
+ possibility of such damages.
169
+
170
+ 9. Accepting Warranty or Additional Liability.
171
+
172
+ While redistributing the Work or Derivative Works thereof, You may choose
173
+ to offer, and charge a fee for, acceptance of support, warranty, indemnity,
174
+ or other liability obligations and/or rights consistent with this License.
175
+ However, in accepting such obligations, You may act only on Your own behalf
176
+ and on Your sole responsibility, not on behalf of any other Contributor,
177
+ and only if You agree to indemnify, defend, and hold each Contributor
178
+ harmless for any liability incurred by, or claims asserted against, such
179
+ Contributor by reason of your accepting any such warranty or additional
180
+ liability.
181
+
182
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: arnmatch
3
+ Version: 2026.2.2
4
+ Summary: Parse AWS ARNs into structured data (2000+ resource types)
5
+ Author-email: Andrey Gubarev <andrey@andreygubarev.com>
6
+ License-Expression: Apache-2.0
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # arnmatch
12
+
13
+ Parse AWS ARNs into structured data.
14
+
15
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
16
+ [![PyPI](https://img.shields.io/pypi/v/arnmatch)](https://pypi.org/project/arnmatch/)
17
+
18
+ ## Overview
19
+
20
+ Working with AWS at scale raises questions that are surprisingly hard to answer:
21
+
22
+ 1. **What resource does this ARN represent?** - ARN formats vary across services with no consistent parsing rules
23
+ 2. **What ARN formats exist?** - No single source documents all valid ARN patterns
24
+ 3. **What resource types exist on AWS?** - Scattered across 300+ service documentation pages
25
+ 4. **What CloudFormation type maps to this ARN?** - No direct ARN-to-CFN mapping exists
26
+
27
+ arnmatch answers these questions by:
28
+ - Parsing ARNs into structured components (service, region, account, resource type, resource ID)
29
+ - Providing a complete index of 2000+ resource types from 300+ AWS services
30
+ - Mapping ARNs to CloudFormation resource types (e.g., `arn:aws:lambda:...:function:X` → `AWS::Lambda::Function`)
31
+
32
+ Patterns are auto-generated from [AWS Service Authorization Reference](https://docs.aws.amazon.com/service-authorization/latest/reference/).
33
+
34
+ ## Features
35
+
36
+ - Zero runtime dependencies
37
+ - 300+ AWS services, 2000+ resource types
38
+ - Patterns auto-generated from AWS official documentation
39
+ - CLI and library interface
40
+ - CloudFormation resource type mapping
41
+ - Boto3 SDK service name mapping
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install arnmatch
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ### CLI
52
+
53
+ ```bash
54
+ $ arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
55
+ aws_service: lambda
56
+ aws_sdk_service: lambda
57
+ aws_sdk_services: lambda
58
+ aws_region: us-east-1
59
+ aws_account: 123456789012
60
+ resource_type: function
61
+ resource_id: my-function
62
+ resource_name: my-function
63
+ cloudformation_resource: AWS::Lambda::Function
64
+ ```
65
+
66
+ ### Library
67
+
68
+ ```python
69
+ from arnmatch import arnmatch
70
+
71
+ result = arnmatch("arn:aws:lambda:us-east-1:123456789012:function:my-function")
72
+
73
+ result.aws_service # "lambda"
74
+ result.aws_region # "us-east-1"
75
+ result.aws_account # "123456789012"
76
+ result.resource_type # "function"
77
+ result.resource_id # "my-function"
78
+ result.resource_name # "my-function"
79
+ result.cloudformation_resource # "AWS::Lambda::Function"
80
+ result.aws_sdk_service # "lambda"
81
+
82
+ # Get a boto3 client for the service
83
+ client = result.client()
84
+ client.get_function(FunctionName=result.resource_name)
85
+ ```
86
+
87
+ ## API Reference
88
+
89
+ ### `arnmatch(arn: str) -> ARN`
90
+
91
+ Parse an ARN string and return structured data.
92
+
93
+ Raises `ARNError` if the ARN format is invalid or no pattern matches.
94
+
95
+ ### `ARN`
96
+
97
+ Dataclass with parsed ARN components:
98
+
99
+ | Field | Type | Description |
100
+ |-------|------|-------------|
101
+ | `aws_partition` | `str` | AWS partition (aws, aws-cn, aws-us-gov) |
102
+ | `aws_service` | `str` | AWS service name |
103
+ | `aws_region` | `str` | AWS region (may be empty for global resources) |
104
+ | `aws_account` | `str` | AWS account ID |
105
+ | `resource_type` | `str` | Canonical resource type from AWS docs |
106
+ | `resource_types` | `list[str]` | All known names for this resource type |
107
+ | `attributes` | `dict[str, str]` | All captured attributes from the ARN |
108
+ | `aws_sdk_service` | `str \| None` | Primary boto3 client name |
109
+ | `cloudformation_resource` | `str \| None` | CloudFormation resource type |
110
+
111
+ Properties:
112
+
113
+ | Property | Description |
114
+ |----------|-------------|
115
+ | `resource_id` | Resource identifier (prefers attributes ending in `Id`, then `Name`, then last attribute) |
116
+ | `resource_name` | Resource name (prefers attributes ending in `Name`, falls back to `resource_id`) |
117
+ | `aws_sdk_services` | List of boto3 client names (e.g., `['elb', 'elbv2']` for elasticloadbalancing) |
118
+
119
+ Methods:
120
+
121
+ | Method | Description |
122
+ |--------|-------------|
123
+ | `client(session=None)` | Returns a boto3 client for the resource's service. Pass an optional `boto3.Session` or uses default. Raises `ValueError` if no SDK mapping exists. |
124
+
125
+ ### `ARNError`
126
+
127
+ Exception raised when ARN parsing fails. Inherits from `ValueError`.
128
+
129
+ ## Development
130
+
131
+ Prerequisites: [uv](https://github.com/astral-sh/uv)
132
+
133
+ ```bash
134
+ make lint # Run ruff linter
135
+ make test # Run pytest tests
136
+ make check # Run lint and test
137
+ make generate # Regenerate patterns from AWS docs
138
+ make build # Build wheel and tarball
139
+ make publish # Build and upload to PyPI
140
+ ```
141
+
142
+ ## Versioning
143
+
144
+ [CalVer](https://calver.org/) format `YYYY.MM.MICRO` (e.g., `2026.2.0`).
@@ -0,0 +1,134 @@
1
+ # arnmatch
2
+
3
+ Parse AWS ARNs into structured data.
4
+
5
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
6
+ [![PyPI](https://img.shields.io/pypi/v/arnmatch)](https://pypi.org/project/arnmatch/)
7
+
8
+ ## Overview
9
+
10
+ Working with AWS at scale raises questions that are surprisingly hard to answer:
11
+
12
+ 1. **What resource does this ARN represent?** - ARN formats vary across services with no consistent parsing rules
13
+ 2. **What ARN formats exist?** - No single source documents all valid ARN patterns
14
+ 3. **What resource types exist on AWS?** - Scattered across 300+ service documentation pages
15
+ 4. **What CloudFormation type maps to this ARN?** - No direct ARN-to-CFN mapping exists
16
+
17
+ arnmatch answers these questions by:
18
+ - Parsing ARNs into structured components (service, region, account, resource type, resource ID)
19
+ - Providing a complete index of 2000+ resource types from 300+ AWS services
20
+ - Mapping ARNs to CloudFormation resource types (e.g., `arn:aws:lambda:...:function:X` → `AWS::Lambda::Function`)
21
+
22
+ Patterns are auto-generated from [AWS Service Authorization Reference](https://docs.aws.amazon.com/service-authorization/latest/reference/).
23
+
24
+ ## Features
25
+
26
+ - Zero runtime dependencies
27
+ - 300+ AWS services, 2000+ resource types
28
+ - Patterns auto-generated from AWS official documentation
29
+ - CLI and library interface
30
+ - CloudFormation resource type mapping
31
+ - Boto3 SDK service name mapping
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install arnmatch
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### CLI
42
+
43
+ ```bash
44
+ $ arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
45
+ aws_service: lambda
46
+ aws_sdk_service: lambda
47
+ aws_sdk_services: lambda
48
+ aws_region: us-east-1
49
+ aws_account: 123456789012
50
+ resource_type: function
51
+ resource_id: my-function
52
+ resource_name: my-function
53
+ cloudformation_resource: AWS::Lambda::Function
54
+ ```
55
+
56
+ ### Library
57
+
58
+ ```python
59
+ from arnmatch import arnmatch
60
+
61
+ result = arnmatch("arn:aws:lambda:us-east-1:123456789012:function:my-function")
62
+
63
+ result.aws_service # "lambda"
64
+ result.aws_region # "us-east-1"
65
+ result.aws_account # "123456789012"
66
+ result.resource_type # "function"
67
+ result.resource_id # "my-function"
68
+ result.resource_name # "my-function"
69
+ result.cloudformation_resource # "AWS::Lambda::Function"
70
+ result.aws_sdk_service # "lambda"
71
+
72
+ # Get a boto3 client for the service
73
+ client = result.client()
74
+ client.get_function(FunctionName=result.resource_name)
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### `arnmatch(arn: str) -> ARN`
80
+
81
+ Parse an ARN string and return structured data.
82
+
83
+ Raises `ARNError` if the ARN format is invalid or no pattern matches.
84
+
85
+ ### `ARN`
86
+
87
+ Dataclass with parsed ARN components:
88
+
89
+ | Field | Type | Description |
90
+ |-------|------|-------------|
91
+ | `aws_partition` | `str` | AWS partition (aws, aws-cn, aws-us-gov) |
92
+ | `aws_service` | `str` | AWS service name |
93
+ | `aws_region` | `str` | AWS region (may be empty for global resources) |
94
+ | `aws_account` | `str` | AWS account ID |
95
+ | `resource_type` | `str` | Canonical resource type from AWS docs |
96
+ | `resource_types` | `list[str]` | All known names for this resource type |
97
+ | `attributes` | `dict[str, str]` | All captured attributes from the ARN |
98
+ | `aws_sdk_service` | `str \| None` | Primary boto3 client name |
99
+ | `cloudformation_resource` | `str \| None` | CloudFormation resource type |
100
+
101
+ Properties:
102
+
103
+ | Property | Description |
104
+ |----------|-------------|
105
+ | `resource_id` | Resource identifier (prefers attributes ending in `Id`, then `Name`, then last attribute) |
106
+ | `resource_name` | Resource name (prefers attributes ending in `Name`, falls back to `resource_id`) |
107
+ | `aws_sdk_services` | List of boto3 client names (e.g., `['elb', 'elbv2']` for elasticloadbalancing) |
108
+
109
+ Methods:
110
+
111
+ | Method | Description |
112
+ |--------|-------------|
113
+ | `client(session=None)` | Returns a boto3 client for the resource's service. Pass an optional `boto3.Session` or uses default. Raises `ValueError` if no SDK mapping exists. |
114
+
115
+ ### `ARNError`
116
+
117
+ Exception raised when ARN parsing fails. Inherits from `ValueError`.
118
+
119
+ ## Development
120
+
121
+ Prerequisites: [uv](https://github.com/astral-sh/uv)
122
+
123
+ ```bash
124
+ make lint # Run ruff linter
125
+ make test # Run pytest tests
126
+ make check # Run lint and test
127
+ make generate # Regenerate patterns from AWS docs
128
+ make build # Build wheel and tarball
129
+ make publish # Build and upload to PyPI
130
+ ```
131
+
132
+ ## Versioning
133
+
134
+ [CalVer](https://calver.org/) format `YYYY.MM.MICRO` (e.g., `2026.2.0`).
@@ -7,6 +7,7 @@ authors = [
7
7
  { name = "Andrey Gubarev", email = "andrey@andreygubarev.com" }
8
8
  ]
9
9
  requires-python = ">=3.10"
10
+ license = "Apache-2.0"
10
11
  dependencies = []
11
12
 
12
13
  [project.scripts]
@@ -21,6 +22,7 @@ path = "src/arnmatch/__init__.py"
21
22
 
22
23
  [dependency-groups]
23
24
  dev = [
25
+ "boto3>=1.42.41",
24
26
  "pytest>=9.0.2",
25
27
  "ruff>=0.14.14",
26
28
  ]
@@ -1,6 +1,6 @@
1
1
  """ARN pattern matching using regex patterns."""
2
2
 
3
- __version__ = "2026.02.0"
3
+ __version__ = "2026.2.2"
4
4
 
5
5
  import sys
6
6
  from dataclasses import dataclass
@@ -90,6 +90,28 @@ class ARN:
90
90
  """
91
91
  return AWS_SDK_SERVICES.get(self.aws_service, [])
92
92
 
93
+ def client(self, session: "boto3.Session | None" = None): # noqa: F821
94
+ """Return a boto3 client for this resource's service.
95
+
96
+ Args:
97
+ session: Optional boto3 Session. If None, creates a new session.
98
+
99
+ Returns:
100
+ boto3 client for the service.
101
+
102
+ Raises:
103
+ ValueError: If no SDK service mapping exists for this ARN's service.
104
+ """
105
+ if self.aws_sdk_service is None:
106
+ raise ValueError(f"No SDK service mapping for service '{self.aws_service}'")
107
+
108
+ import boto3
109
+
110
+ if session is None:
111
+ session = boto3.Session()
112
+
113
+ return session.client(self.aws_sdk_service)
114
+
93
115
 
94
116
  def arnmatch(arn: str) -> ARN:
95
117
  """Match ARN against patterns.
@@ -1,5 +1,8 @@
1
1
  """Tests for ARN pattern matching."""
2
2
 
3
+ import boto3
4
+ import pytest
5
+
3
6
  from arnmatch import arnmatch
4
7
 
5
8
 
@@ -452,3 +455,30 @@ def test_wafv2():
452
455
  assert result.attributes["Scope"] == "regional"
453
456
  assert result.attributes["Name"] == "webacl-production-acl"
454
457
  assert result.attributes["Id"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
458
+
459
+
460
+ class TestClient:
461
+ """Tests for ARN.client() method."""
462
+
463
+ def test_client_returns_correct_service(self):
464
+ """Test client() returns a client for the correct service."""
465
+ result = arnmatch(
466
+ "arn:aws:lambda:us-east-1:012345678901:function:my-function"
467
+ )
468
+ client = result.client()
469
+ assert client.meta.service_model.service_name == "lambda"
470
+
471
+ def test_client_uses_provided_session(self):
472
+ """Test client() uses the provided session."""
473
+ session = boto3.Session()
474
+ result = arnmatch("arn:aws:s3:::my-bucket")
475
+ client = result.client(session=session)
476
+ assert client.meta.service_model.service_name == "s3"
477
+
478
+ def test_client_raises_when_no_sdk_mapping(self):
479
+ """Test client() raises ValueError when no SDK service mapping exists."""
480
+ # 'a4b' (Alexa for Business) has no SDK mapping
481
+ result = arnmatch("arn:aws:a4b:us-east-1:012345678901:address-book/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
482
+
483
+ with pytest.raises(ValueError, match="No SDK service mapping"):
484
+ result.client()