better-aws-tags 0.3.1__tar.gz → 0.5.0__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 (38) hide show
  1. better_aws_tags-0.5.0/.claude/rules/python.md +12 -0
  2. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/.gitignore +3 -2
  3. better_aws_tags-0.5.0/CHANGELOG.md +15 -0
  4. better_aws_tags-0.5.0/CLAUDE.md +101 -0
  5. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/Makefile +4 -0
  6. better_aws_tags-0.5.0/PKG-INFO +47 -0
  7. better_aws_tags-0.5.0/README.md +37 -0
  8. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/pyproject.toml +2 -0
  9. better_aws_tags-0.5.0/scratchpad/.gitignore +1 -0
  10. better_aws_tags-0.5.0/scratchpad/get_supported_resources.py +105 -0
  11. better_aws_tags-0.5.0/scratchpad/supported_resources.py +1146 -0
  12. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/src/better_aws_tags/__init__.py +1 -1
  13. better_aws_tags-0.5.0/src/better_aws_tags/handlers/__init__.py +45 -0
  14. better_aws_tags-0.5.0/src/better_aws_tags/handlers/asg.py +54 -0
  15. better_aws_tags-0.5.0/src/better_aws_tags/handlers/base.py +13 -0
  16. better_aws_tags-0.5.0/src/better_aws_tags/handlers/cloudfront.py +27 -0
  17. better_aws_tags-0.5.0/src/better_aws_tags/handlers/glue.py +25 -0
  18. better_aws_tags-0.5.0/src/better_aws_tags/handlers/iam.py +79 -0
  19. better_aws_tags-0.5.0/src/better_aws_tags/handlers/inspector.py +26 -0
  20. better_aws_tags-0.5.0/src/better_aws_tags/handlers/rds.py +26 -0
  21. better_aws_tags-0.5.0/src/better_aws_tags/handlers/resource_explorer.py +25 -0
  22. better_aws_tags-0.5.0/src/better_aws_tags/handlers/ses.py +26 -0
  23. better_aws_tags-0.5.0/src/better_aws_tags/handlers/tagging_api.py +50 -0
  24. better_aws_tags-0.5.0/src/better_aws_tags/handlers/wafv2.py +27 -0
  25. better_aws_tags-0.5.0/src/better_aws_tags/supported_resources.py +1146 -0
  26. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/src/better_aws_tags/tags.py +18 -9
  27. better_aws_tags-0.5.0/src/better_aws_tags/unsupported_resources.py +29 -0
  28. better_aws_tags-0.5.0/tests/integration/check_coverage.py +80 -0
  29. better_aws_tags-0.5.0/tests/test_dispatch.py +63 -0
  30. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/uv.lock +14 -1
  31. better_aws_tags-0.3.1/PKG-INFO +0 -20
  32. better_aws_tags-0.3.1/README.md +0 -11
  33. better_aws_tags-0.3.1/src/better_aws_tags/arnparse.py +0 -139
  34. better_aws_tags-0.3.1/src/better_aws_tags/handlers.py +0 -134
  35. better_aws_tags-0.3.1/tests/test_arnparse.py +0 -363
  36. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/.github/workflows/release.yml +0 -0
  37. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/.python-version +0 -0
  38. {better_aws_tags-0.3.1 → better_aws_tags-0.5.0}/tests/test_import.py +0 -0
@@ -0,0 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Python Rules
7
+
8
+ ## Naming
9
+
10
+ - Never use type suffixes in variable names (`_list`, `_dict`, `_map`, `_str`, `_int`, etc.)
11
+ - Bad: `users_list`, `config_dict`, `name_str`, `handler_map`, `count_int`
12
+ - Good: `users`, `config`, `name`, `handlers`, `count`
@@ -1,4 +1,5 @@
1
1
  debug_*.py
2
+ *.xlsx
2
3
 
3
4
  ### Python
4
5
 
@@ -199,9 +200,9 @@ cython_debug/
199
200
  .abstra/
200
201
 
201
202
  # Visual Studio Code
202
- # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
203
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
203
204
  # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
204
- # and can be added to the global gitignore or merged into this file. However, if you prefer,
205
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
205
206
  # you could uncomment the following to ignore the entire vscode folder
206
207
  # .vscode/
207
208
 
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ ### Added
6
+
7
+ - New `arnmatch` module for ARN pattern matching using regex patterns from AWS documentation
8
+ - `arnmatch(arn)` function returns `ARNMatch` with parsed ARN components
9
+ - `ARNMatch.resource_id` and `ARNMatch.resource_name` heuristics for extracting resource identifiers
10
+ - Support for Resource Explorer type aliases (handles naming differences between AWS docs and Resource Explorer)
11
+ - 2080 ARN patterns covering 355 AWS services
12
+
13
+ ## 0.3.1
14
+
15
+ - Initial release with `get_tags` and `set_tags` functions
@@ -0,0 +1,101 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## EXTREMELY IMPORTANT
6
+
7
+ - NEVER use bare `try...except` or `except Exception`. ALWAYS catch specific exceptions.
8
+ - NEVER do defensive programming. Let code FAIL if there is an unhandled exception.
9
+ - ALWAYS validate results of API calls (boto3, HTTP, etc.). Check the response to confirm the operation succeeded.
10
+
11
+ ## Build and Development Commands
12
+
13
+ ```bash
14
+ make dev # Run in development mode (uv run python -m better_aws_tags)
15
+ make run # Run debug script (uv run python -i debug_bats.py)
16
+ make test # Run tests on Python 3.10-3.13
17
+ make lint # Run ruff linter
18
+ make fmt # Format code with ruff
19
+ make check # Run lint and tests
20
+ make build # Build package
21
+ make publish # Build and publish to PyPI
22
+ ```
23
+
24
+ Run a single test:
25
+ ```bash
26
+ uv run pytest tests/test_dispatch.py::test_dispatch_supported_resource -v
27
+ ```
28
+
29
+ ## Architecture
30
+
31
+ This library provides a unified API for AWS resource tagging that abstracts away the differences between AWS's various tagging APIs.
32
+
33
+ ### Core Flow
34
+
35
+ 1. **Entry points** (`src/better_aws_tags/tags.py`): `get_tags()` and `set_tags()` accept ARNs and optional boto3 session
36
+ 2. **Dispatch** (`tags.py:dispatch()`): Routes ARNs to appropriate handlers based on resource type
37
+ 3. **Handlers** (`src/better_aws_tags/handlers.py`): Execute AWS API calls
38
+
39
+ ### Handlers
40
+
41
+ - `TaggingAPIHandler`: Uses Resource Groups Tagging API for resources in `SUPPORTED_RESOURCES`
42
+ - `IAMRoleHandler`: IAM roles (`arn:aws:iam::*:role/*`)
43
+ - `IAMUserHandler`: IAM users (`arn:aws:iam::*:user/*`)
44
+ - `IAMPolicyHandler`: IAM policies (`arn:aws:iam::*:policy/*`)
45
+
46
+ The `HANDLERS` dict maps `(service, resource_type)` tuples to handler classes for resources not covered by the Tagging API.
47
+
48
+ ### Handler Routing Logic
49
+
50
+ The `dispatch()` function determines which handler to use:
51
+ 1. If CloudFormation resource type is in `SUPPORTED_RESOURCES` → `TaggingAPIHandler`
52
+ 2. If `(service, resource_type)` is in `HANDLERS` → specific handler
53
+ 3. Otherwise → raises `NotImplementedError`
54
+
55
+ ### Dependencies
56
+
57
+ - `arnmatch`: Parses ARNs and extracts service, resource type, and CloudFormation resource type
58
+ - `boto3`: AWS SDK for Python
59
+
60
+ ## Adding Support for New Resources
61
+
62
+ 1. **Run integration tests** to identify unsupported resources:
63
+ ```bash
64
+ uv run python tests/integration/check_coverage.py
65
+ ```
66
+
67
+ 2. **Get sample ARN** and parse with arnmatch:
68
+ ```python
69
+ from arnmatch import arnmatch
70
+ data = arnmatch(arn)
71
+ print('aws_service:', data.aws_service)
72
+ print('resource_type:', data.resource_type)
73
+ print('tagging_resource:', data.tagging_resource)
74
+ ```
75
+ If `tagging_resource` is not `None` and is in `SUPPORTED_RESOURCES` → TaggingAPIHandler works.
76
+ If `tagging_resource` is `None` → need custom handler or mark unsupported.
77
+
78
+ 3. **Check if boto client exists**:
79
+ ```python
80
+ client = data.client()
81
+ ```
82
+ If no client exists → resource is not taggable.
83
+
84
+ 4. **Check if tagging methods exist**:
85
+ ```python
86
+ tag_methods = [m for m in dir(client) if 'tag' in m.lower()]
87
+ ```
88
+ If no tag methods → add to `unsupported_resources.py`.
89
+
90
+ 5. **Test if tagging works for this resource type**:
91
+ ```python
92
+ client.list_tags_for_resource(resourceArn=arn) # or similar
93
+ ```
94
+ If fails with "invalid resource" → add to `unsupported_resources.py`.
95
+
96
+ 6. **If taggable, write a handler**:
97
+ - Create `src/better_aws_tags/handlers/{service}.py`
98
+ - Implement `get_tags` and `set_tags` methods
99
+ - Register in `handlers/__init__.py` with `(service, resource_type)` key
100
+
101
+ 7. **Re-run integration tests** to verify coverage.
@@ -19,6 +19,10 @@ test: ## Run tests on all Python versions
19
19
  uv run --python 3.12 pytest -v
20
20
  uv run --python 3.13 pytest -v
21
21
 
22
+ .PHONY: test-integration
23
+ test-integration: ## Run integration tests (requires AWS credentials)
24
+ uv run python tests/integration/check_coverage.py
25
+
22
26
  .PHONY: lint
23
27
  lint: ## Run linter
24
28
  uv run ruff check .
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: better-aws-tags
3
+ Version: 0.5.0
4
+ Summary: A unified Python interface for managing AWS resource tags
5
+ Author-email: Andrey Gubarev <andrey@andreygubarev.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: arnmatch>=2026.2.2
8
+ Requires-Dist: boto3~=1.0
9
+ Description-Content-Type: text/markdown
10
+
11
+ # better-aws-tags
12
+
13
+ A unified Python interface for managing AWS resource tags.
14
+
15
+ AWS provides multiple tagging APIs across services, including the Resource Groups Tagging API and service-specific implementations such as EC2, S3, and IAM. This library abstracts these differences behind a single API that accepts any valid ARN and handles service routing automatically.
16
+
17
+ ## Getting Started
18
+
19
+ ```python
20
+ import better_aws_tags as bats
21
+
22
+ # Get tags for resources
23
+ tags = bats.get_tags(["arn:aws:s3:::my-bucket", "arn:aws:iam::123456789012:role/my-role"])
24
+
25
+ # Set tags on resources
26
+ bats.set_tags(["arn:aws:s3:::my-bucket"], {"Environment": "prod", "Team": "platform"})
27
+ ```
28
+
29
+ ## How It Works
30
+
31
+ The library routes ARNs to the appropriate handler:
32
+
33
+ 1. Most resources use the **Resource Groups Tagging API** (1000+ supported resource types)
34
+ 2. Some IAM resources (roles, users, policies) require **IAM-specific APIs**
35
+
36
+ This routing is transparent - just pass ARNs and the library handles the rest.
37
+
38
+ ## Reference
39
+
40
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/overview.html
41
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_TagResources.html
42
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/supported-services.html
43
+ - https://docs.aws.amazon.com/ARG/latest/userguide/supported-resources.html
44
+
45
+ AWS Resources and Resource Types:
46
+ - https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
47
+ - https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html
@@ -0,0 +1,37 @@
1
+ # better-aws-tags
2
+
3
+ A unified Python interface for managing AWS resource tags.
4
+
5
+ AWS provides multiple tagging APIs across services, including the Resource Groups Tagging API and service-specific implementations such as EC2, S3, and IAM. This library abstracts these differences behind a single API that accepts any valid ARN and handles service routing automatically.
6
+
7
+ ## Getting Started
8
+
9
+ ```python
10
+ import better_aws_tags as bats
11
+
12
+ # Get tags for resources
13
+ tags = bats.get_tags(["arn:aws:s3:::my-bucket", "arn:aws:iam::123456789012:role/my-role"])
14
+
15
+ # Set tags on resources
16
+ bats.set_tags(["arn:aws:s3:::my-bucket"], {"Environment": "prod", "Team": "platform"})
17
+ ```
18
+
19
+ ## How It Works
20
+
21
+ The library routes ARNs to the appropriate handler:
22
+
23
+ 1. Most resources use the **Resource Groups Tagging API** (1000+ supported resource types)
24
+ 2. Some IAM resources (roles, users, policies) require **IAM-specific APIs**
25
+
26
+ This routing is transparent - just pass ARNs and the library handles the rest.
27
+
28
+ ## Reference
29
+
30
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/overview.html
31
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_TagResources.html
32
+ - https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/supported-services.html
33
+ - https://docs.aws.amazon.com/ARG/latest/userguide/supported-resources.html
34
+
35
+ AWS Resources and Resource Types:
36
+ - https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
37
+ - https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html
@@ -8,6 +8,7 @@ authors = [
8
8
  ]
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
+ "arnmatch>=2026.2.2",
11
12
  "boto3~=1.0",
12
13
  ]
13
14
 
@@ -23,3 +24,4 @@ dev = [
23
24
  "pytest~=9.0",
24
25
  "ruff~=0.14",
25
26
  ]
27
+
@@ -0,0 +1 @@
1
+ *.json
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = [
5
+ # "boto3",
6
+ # "requests",
7
+ # ]
8
+ # ///
9
+ """
10
+ Fetch resource types from undocumented AWS Resource Groups Tagging API endpoint.
11
+
12
+ This endpoint returns the full list of resource types supported by the tagging API.
13
+ """
14
+
15
+ import json
16
+
17
+ import boto3
18
+ from botocore.auth import SigV4Auth
19
+ from botocore.awsrequest import AWSRequest
20
+ import requests
21
+
22
+
23
+ def fetch_resource_types_page(
24
+ query_type: str,
25
+ region: str,
26
+ credentials,
27
+ next_token: str | None = None,
28
+ ) -> dict:
29
+ """Fetch a single page of resource types."""
30
+ url = f"https://resource-groups.{region}.amazonaws.com/resource-types-list"
31
+
32
+ headers = {"Content-Type": "application/json"}
33
+ payload = {"QueryType": query_type, "MaxResults": 50}
34
+ if next_token:
35
+ payload["NextToken"] = next_token
36
+
37
+ body = json.dumps(payload)
38
+
39
+ request = AWSRequest(method="POST", url=url, headers=headers, data=body)
40
+ SigV4Auth(credentials, "resource-groups", region).add_auth(request)
41
+
42
+ response = requests.post(url, headers=dict(request.headers), data=body)
43
+ response.raise_for_status()
44
+
45
+ return response.json()
46
+
47
+
48
+ def fetch_resource_types(
49
+ query_type,
50
+ region="us-east-1",
51
+ ) -> list[str]:
52
+ """
53
+ Fetch all resource types with pagination.
54
+
55
+ Args:
56
+ query_type: CLOUDFORMATION_STACK_1_0 or TAG_FILTERS_1_0
57
+ region: AWS region to query
58
+
59
+ Returns:
60
+ List of resource type strings
61
+ """
62
+ session = boto3.Session()
63
+ credentials = session.get_credentials()
64
+
65
+ resource_types = []
66
+ next_token = None
67
+
68
+ while True:
69
+ data = fetch_resource_types_page(query_type, region, credentials, next_token)
70
+ resource_types.extend(data.get("ResourceTypes", []))
71
+
72
+ next_token = data.get("NextToken")
73
+ if not next_token:
74
+ break
75
+
76
+ return resource_types
77
+
78
+
79
+ def generate_python(resource_types: list[str]) -> str:
80
+ """Generate Python module with SUPPORTED_RESOURCES constant."""
81
+ lines = [
82
+ '"""Resource types supported by AWS Resource Groups Tagging API."""',
83
+ "",
84
+ "SUPPORTED_RESOURCES: list[str] = [",
85
+ ]
86
+ for rt in resource_types:
87
+ lines.append(f' "{rt}",')
88
+ lines.append("]")
89
+ lines.append("")
90
+ return "\n".join(lines)
91
+
92
+
93
+ def main():
94
+ resource_types = fetch_resource_types(query_type="TAG_FILTERS_1_0")
95
+ resource_types = sorted(set(resource_types))
96
+
97
+ output = generate_python(resource_types)
98
+ with open("supported_resources.py", "w") as f:
99
+ f.write(output)
100
+
101
+ print(f"Generated supported_resources.py with {len(resource_types)} resource types")
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()