capo-cloudcontrol 0.5.0__tar.gz → 0.7.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 (115) hide show
  1. {capo_cloudcontrol-0.5.0/src/capo_cloudcontrol.egg-info → capo_cloudcontrol-0.7.0}/PKG-INFO +2 -2
  2. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/pyproject.toml +2 -2
  3. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_auth/_sigv4.py +9 -9
  4. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/cancel_resource_request.py +5 -1
  5. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/create_resource.py +5 -1
  6. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/delete_resource.py +5 -1
  7. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/get_resource.py +5 -1
  8. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/get_resource_request_status.py +5 -1
  9. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/list_resource_requests.py +5 -1
  10. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/list_resources.py +5 -1
  11. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_operations/cloud_api_service/update_resource.py +5 -1
  12. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_services/_pipeline.py +2 -0
  13. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_services/async_cloud_control.py +12 -4
  14. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_services/cloud_control.py +12 -4
  15. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0/src/capo_cloudcontrol.egg-info}/PKG-INFO +2 -2
  16. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol.egg-info/requires.txt +1 -1
  17. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/tests/test_sigv4.py +21 -6
  18. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/LICENSE +0 -0
  19. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/README.md +0 -0
  20. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/setup.cfg +0 -0
  21. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/__init__.py +0 -0
  22. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_async.py +0 -0
  23. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_auth/_identity.py +0 -0
  24. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_auth/_providers.py +0 -0
  25. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_auth/_signers.py +0 -0
  26. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_auth/_zapros_handler.py +0 -0
  27. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_body.py +0 -0
  28. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_iter.py +0 -0
  29. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_pagination.py +0 -0
  30. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_protocol/__init__.py +0 -0
  31. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_protocol/errors.py +0 -0
  32. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_protocol/eventstream.py +0 -0
  33. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_protocol/serialize.py +0 -0
  34. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_protocol/xml.py +0 -0
  35. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_rule_engine/__init__.py +0 -0
  36. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_rule_engine/_aws_partition.py +0 -0
  37. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_rule_engine/_endpoint_rule_set.py +0 -0
  38. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_rule_engine/_endpoint_runtime.py +0 -0
  39. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/_services/_aws_config.py +0 -0
  40. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/__init__.py +0 -0
  41. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/_base.py +0 -0
  42. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/already_exists_exception.py +0 -0
  43. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/client_token_conflict_exception.py +0 -0
  44. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/concurrent_modification_exception.py +0 -0
  45. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/concurrent_operation_exception.py +0 -0
  46. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/general_service_exception.py +0 -0
  47. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/handler_failure_exception.py +0 -0
  48. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/handler_internal_failure_exception.py +0 -0
  49. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/invalid_credentials_exception.py +0 -0
  50. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/invalid_request_exception.py +0 -0
  51. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/network_failure_exception.py +0 -0
  52. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/not_stabilized_exception.py +0 -0
  53. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/not_updatable_exception.py +0 -0
  54. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/private_type_exception.py +0 -0
  55. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/request_token_not_found_exception.py +0 -0
  56. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/resource_conflict_exception.py +0 -0
  57. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/resource_not_found_exception.py +0 -0
  58. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/service_internal_error_exception.py +0 -0
  59. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/service_limit_exceeded_exception.py +0 -0
  60. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/throttling_exception.py +0 -0
  61. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/type_not_found_exception.py +0 -0
  62. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/errors/unsupported_action_exception.py +0 -0
  63. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/py.typed +0 -0
  64. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/_prelude/blob.py +0 -0
  65. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/_prelude/timestamp.py +0 -0
  66. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/cancel_resource_request_input.py +0 -0
  67. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/cancel_resource_request_output.py +0 -0
  68. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/client_token.py +0 -0
  69. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/create_resource_input.py +0 -0
  70. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/create_resource_output.py +0 -0
  71. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/delete_resource_input.py +0 -0
  72. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/delete_resource_output.py +0 -0
  73. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/error_message.py +0 -0
  74. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/get_resource_input.py +0 -0
  75. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/get_resource_output.py +0 -0
  76. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/get_resource_request_status_input.py +0 -0
  77. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/get_resource_request_status_output.py +0 -0
  78. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/handler_error_code.py +0 -0
  79. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/handler_next_token.py +0 -0
  80. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hook_failure_mode.py +0 -0
  81. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hook_invocation_point.py +0 -0
  82. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hook_progress_event.py +0 -0
  83. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hook_status.py +0 -0
  84. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hook_type_arn.py +0 -0
  85. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/hooks_progress_event.py +0 -0
  86. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/identifier.py +0 -0
  87. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/list_resource_requests_input.py +0 -0
  88. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/list_resource_requests_output.py +0 -0
  89. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/list_resources_input.py +0 -0
  90. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/list_resources_output.py +0 -0
  91. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/max_results.py +0 -0
  92. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/next_token.py +0 -0
  93. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/operation.py +0 -0
  94. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/operation_status.py +0 -0
  95. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/operation_statuses.py +0 -0
  96. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/operations.py +0 -0
  97. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/patch_document.py +0 -0
  98. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/progress_event.py +0 -0
  99. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/properties.py +0 -0
  100. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/request_token.py +0 -0
  101. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/resource_description.py +0 -0
  102. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/resource_descriptions.py +0 -0
  103. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/resource_request_status_filter.py +0 -0
  104. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/resource_request_status_summaries.py +0 -0
  105. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/role_arn.py +0 -0
  106. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/status_message.py +0 -0
  107. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/timestamp.py +0 -0
  108. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/type_name.py +0 -0
  109. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/type_version_id.py +0 -0
  110. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/update_resource_input.py +0 -0
  111. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol/types/update_resource_output.py +0 -0
  112. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol.egg-info/SOURCES.txt +0 -0
  113. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol.egg-info/dependency_links.txt +0 -0
  114. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/src/capo_cloudcontrol.egg-info/top_level.txt +0 -0
  115. {capo_cloudcontrol-0.5.0 → capo_cloudcontrol-0.7.0}/tests/test_body.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capo-cloudcontrol
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Python SDK for AWS Cloudcontrol.
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -18,7 +18,7 @@ Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: jmespath>=1.0
21
- Requires-Dist: zapros>=0.13.0
21
+ Requires-Dist: zapros>=0.21.0
22
22
  Provides-Extra: anyio
23
23
  Requires-Dist: anyio>=3.0; extra == "anyio"
24
24
  Provides-Extra: sso
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capo-cloudcontrol"
3
- version = "0.5.0"
3
+ version = "0.7.0"
4
4
  description = "Python SDK for AWS Cloudcontrol."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -20,7 +20,7 @@ classifiers = [
20
20
  ]
21
21
  dependencies = [
22
22
  "jmespath>=1.0",
23
- "zapros>=0.13.0",
23
+ "zapros>=0.21.0",
24
24
  ]
25
25
 
26
26
  [project.optional-dependencies]
@@ -17,7 +17,7 @@ import hmac
17
17
  import re
18
18
  from collections.abc import AsyncIterator, Iterator
19
19
  from typing import Any, Literal, TypedDict, cast
20
- from urllib.parse import quote, unquote
20
+ from urllib.parse import quote
21
21
 
22
22
  import zapros
23
23
  from pywhatwgurl import URLSearchParams
@@ -134,11 +134,11 @@ def _remove_dot_segments(path: str) -> str:
134
134
  def _canonical_path(path: str, *, double_encode: bool, normalize: bool) -> str:
135
135
  """Build CanonicalURI.
136
136
 
137
- Per the SigV4 spec, every segment is URI-encoded; standard services
138
- normalize dot segments and URI-encode each segment **twice**. The
139
- rules-engine ``disableNormalizePath`` / ``disableDoubleEncoding`` flags
140
- turn those steps off — S3-family services set both, so the path is
141
- signed exactly as provided.
137
+ ``path`` is the path as sent on the wire, so it is already URI-encoded
138
+ once. Standard services normalize dot segments and URI-encode each
139
+ segment a second time. The rules-engine ``disableNormalizePath`` /
140
+ ``disableDoubleEncoding`` flags turn those steps off — S3-family
141
+ services set both, so the path is signed exactly as provided.
142
142
  """
143
143
  if not path:
144
144
  return "/"
@@ -148,9 +148,9 @@ def _canonical_path(path: str, *, double_encode: bool, normalize: bool) -> str:
148
148
  path = _remove_dot_segments(path)
149
149
  if not double_encode:
150
150
  return path
151
- decoded = unquote(path)
152
- first = quote(decoded, safe="/~")
153
- return quote(first, safe="/~")
151
+ # Never decode first: that would turn a "%2F" inside a segment (an ARN in
152
+ # a path label) into a separator and leave it out of the second encoding.
153
+ return quote(path, safe="/~")
154
154
 
155
155
 
156
156
  def _canonical_query(query: str) -> str:
@@ -111,7 +111,11 @@ def build_request(
111
111
  allow_nan=False,
112
112
  ).encode()
113
113
  headers["content-type"] = "application/x-amz-json-1.0"
114
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
114
+ signer = (
115
+ None
116
+ if options.anonymous
117
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
118
+ )
115
119
  normalized_url = zapros.URL(url)
116
120
  for k, v in params:
117
121
  normalized_url.search_params.append(k, v)
@@ -198,7 +198,11 @@ def build_request(
198
198
  allow_nan=False,
199
199
  ).encode()
200
200
  headers["content-type"] = "application/x-amz-json-1.0"
201
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
201
+ signer = (
202
+ None
203
+ if options.anonymous
204
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
205
+ )
202
206
  normalized_url = zapros.URL(url)
203
207
  for k, v in params:
204
208
  normalized_url.search_params.append(k, v)
@@ -198,7 +198,11 @@ def build_request(
198
198
  allow_nan=False,
199
199
  ).encode()
200
200
  headers["content-type"] = "application/x-amz-json-1.0"
201
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
201
+ signer = (
202
+ None
203
+ if options.anonymous
204
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
205
+ )
202
206
  normalized_url = zapros.URL(url)
203
207
  for k, v in params:
204
208
  normalized_url.search_params.append(k, v)
@@ -188,7 +188,11 @@ def build_request(
188
188
  allow_nan=False,
189
189
  ).encode()
190
190
  headers["content-type"] = "application/x-amz-json-1.0"
191
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
191
+ signer = (
192
+ None
193
+ if options.anonymous
194
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
195
+ )
192
196
  normalized_url = zapros.URL(url)
193
197
  for k, v in params:
194
198
  normalized_url.search_params.append(k, v)
@@ -107,7 +107,11 @@ def build_request(
107
107
  allow_nan=False,
108
108
  ).encode()
109
109
  headers["content-type"] = "application/x-amz-json-1.0"
110
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
110
+ signer = (
111
+ None
112
+ if options.anonymous
113
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
114
+ )
111
115
  normalized_url = zapros.URL(url)
112
116
  for k, v in params:
113
117
  normalized_url.search_params.append(k, v)
@@ -102,7 +102,11 @@ def build_request(
102
102
  allow_nan=False,
103
103
  ).encode()
104
104
  headers["content-type"] = "application/x-amz-json-1.0"
105
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
105
+ signer = (
106
+ None
107
+ if options.anonymous
108
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
109
+ )
106
110
  normalized_url = zapros.URL(url)
107
111
  for k, v in params:
108
112
  normalized_url.search_params.append(k, v)
@@ -188,7 +188,11 @@ def build_request(
188
188
  allow_nan=False,
189
189
  ).encode()
190
190
  headers["content-type"] = "application/x-amz-json-1.0"
191
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
191
+ signer = (
192
+ None
193
+ if options.anonymous
194
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
195
+ )
192
196
  normalized_url = zapros.URL(url)
193
197
  for k, v in params:
194
198
  normalized_url.search_params.append(k, v)
@@ -198,7 +198,11 @@ def build_request(
198
198
  allow_nan=False,
199
199
  ).encode()
200
200
  headers["content-type"] = "application/x-amz-json-1.0"
201
- signer = get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
201
+ signer = (
202
+ None
203
+ if options.anonymous
204
+ else get_signer(options, auth_schemes=endpoint.properties.get("authSchemes"))
205
+ )
202
206
  normalized_url = zapros.URL(url)
203
207
  for k, v in params:
204
208
  normalized_url.search_params.append(k, v)
@@ -40,6 +40,7 @@ class OperationOptions:
40
40
  endpoint: str | None = None
41
41
  retry_max_attempts: int | None = None
42
42
  credentials_provider: IdentityProvider[Credentials] | None = None
43
+ anonymous: bool | None = None
43
44
 
44
45
 
45
46
  @dataclass
@@ -51,6 +52,7 @@ class AsyncOperationOptions:
51
52
  endpoint: str | None = None
52
53
  retry_max_attempts: int | None = None
53
54
  credentials_provider: IdentityProvider[Credentials] | None = None
55
+ anonymous: bool | None = None
54
56
 
55
57
 
56
58
  @dataclass
@@ -70,6 +70,7 @@ class AsyncCloudControlClientConfig(TypedDict, total=False, closed=True):
70
70
  use_fips: bool | None
71
71
  endpoint: str | None
72
72
  credentials_provider: IdentityProvider[Credentials] | None
73
+ anonymous: bool | None
73
74
 
74
75
 
75
76
  class AsyncCloudControlClient:
@@ -85,6 +86,7 @@ class AsyncCloudControlClient:
85
86
  endpoint: The value of the ``SDK::Endpoint`` endpoint parameter.
86
87
  credentials: AWS credentials for request signing.
87
88
  credentials_provider: Provider that resolves AWS credentials. Takes precedence over ``credentials``.
89
+ anonymous: Send requests unsigned, without resolving credentials, even for operations that require authentication.
88
90
  """
89
91
 
90
92
  def __init__(
@@ -98,10 +100,14 @@ class AsyncCloudControlClient:
98
100
  endpoint: str | None = None,
99
101
  credentials: Credentials | None = None,
100
102
  credentials_provider: CredentialsProvider | None = None,
103
+ anonymous: bool | None = None,
101
104
  ):
102
- self._client = AsyncClient(http_handler).wrap_with_middleware(
103
- lambda next: AuthMiddleware(next)
104
- )
105
+ client = AsyncClient(http_handler)
106
+ # The handler the user gave us, or the default one for this environment.
107
+ # The SDK middlewares wrap it below, and the credential resolvers borrow
108
+ # it for their one-off calls.
109
+ raw_handler = client.handler
110
+ self._client = client.wrap_with_middleware(lambda next: AuthMiddleware(next))
105
111
  if credentials is not None and credentials_provider is not None:
106
112
  warnings.warn(
107
113
  "Both credentials and credentials_provider given; provider takes precedence"
@@ -113,7 +119,7 @@ class AsyncCloudControlClient:
113
119
  resolved_credentials_provider = StaticAwsCredentialsProvider(credentials)
114
120
  if resolved_credentials_provider is None and credentials is None:
115
121
  resolved_credentials_provider = default_aws_credentials_chain(
116
- AsyncClient(http_handler)
122
+ AsyncClient(raw_handler)
117
123
  )
118
124
  self._config = AsyncCloudControlClientConfig(
119
125
  {
@@ -124,6 +130,7 @@ class AsyncCloudControlClient:
124
130
  "use_fips": use_fips,
125
131
  "endpoint": endpoint,
126
132
  "credentials_provider": resolved_credentials_provider,
133
+ "anonymous": anonymous,
127
134
  }
128
135
  )
129
136
 
@@ -152,6 +159,7 @@ class AsyncCloudControlClient:
152
159
  credentials_provider=overrides.get(
153
160
  "credentials_provider", self._config.get("credentials_provider")
154
161
  ),
162
+ anonymous=overrides.get("anonymous", self._config.get("anonymous")),
155
163
  )
156
164
  return interceptors_, options_
157
165
 
@@ -70,6 +70,7 @@ class CloudControlClientConfig(TypedDict, total=False, closed=True):
70
70
  use_fips: bool | None
71
71
  endpoint: str | None
72
72
  credentials_provider: IdentityProvider[Credentials] | None
73
+ anonymous: bool | None
73
74
 
74
75
 
75
76
  class CloudControlClient:
@@ -85,6 +86,7 @@ class CloudControlClient:
85
86
  endpoint: The value of the ``SDK::Endpoint`` endpoint parameter.
86
87
  credentials: AWS credentials for request signing.
87
88
  credentials_provider: Provider that resolves AWS credentials. Takes precedence over ``credentials``.
89
+ anonymous: Send requests unsigned, without resolving credentials, even for operations that require authentication.
88
90
  """
89
91
 
90
92
  def __init__(
@@ -98,10 +100,14 @@ class CloudControlClient:
98
100
  endpoint: str | None = None,
99
101
  credentials: Credentials | None = None,
100
102
  credentials_provider: CredentialsProvider | None = None,
103
+ anonymous: bool | None = None,
101
104
  ):
102
- self._client = Client(http_handler).wrap_with_middleware(
103
- lambda next: AuthMiddleware(next)
104
- )
105
+ client = Client(http_handler)
106
+ # The handler the user gave us, or the default one for this environment.
107
+ # The SDK middlewares wrap it below, and the credential resolvers borrow
108
+ # it for their one-off calls.
109
+ raw_handler = client.handler
110
+ self._client = client.wrap_with_middleware(lambda next: AuthMiddleware(next))
105
111
  if credentials is not None and credentials_provider is not None:
106
112
  warnings.warn(
107
113
  "Both credentials and credentials_provider given; provider takes precedence"
@@ -113,7 +119,7 @@ class CloudControlClient:
113
119
  resolved_credentials_provider = StaticAwsCredentialsProvider(credentials)
114
120
  if resolved_credentials_provider is None and credentials is None:
115
121
  resolved_credentials_provider = default_aws_credentials_chain(
116
- Client(http_handler)
122
+ Client(raw_handler)
117
123
  )
118
124
  self._config = CloudControlClientConfig(
119
125
  {
@@ -124,6 +130,7 @@ class CloudControlClient:
124
130
  "use_fips": use_fips,
125
131
  "endpoint": endpoint,
126
132
  "credentials_provider": resolved_credentials_provider,
133
+ "anonymous": anonymous,
127
134
  }
128
135
  )
129
136
 
@@ -152,6 +159,7 @@ class CloudControlClient:
152
159
  credentials_provider=overrides.get(
153
160
  "credentials_provider", self._config.get("credentials_provider")
154
161
  ),
162
+ anonymous=overrides.get("anonymous", self._config.get("anonymous")),
155
163
  )
156
164
  return interceptors_, options_
157
165
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capo-cloudcontrol
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Python SDK for AWS Cloudcontrol.
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -18,7 +18,7 @@ Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: jmespath>=1.0
21
- Requires-Dist: zapros>=0.13.0
21
+ Requires-Dist: zapros>=0.21.0
22
22
  Provides-Extra: anyio
23
23
  Requires-Dist: anyio>=3.0; extra == "anyio"
24
24
  Provides-Extra: sso
@@ -1,5 +1,5 @@
1
1
  jmespath>=1.0
2
- zapros>=0.13.0
2
+ zapros>=0.21.0
3
3
 
4
4
  [anyio]
5
5
  anyio>=3.0
@@ -91,13 +91,28 @@ class TestCanonicalPath:
91
91
  def test_s3_preserves_dot_segments(self):
92
92
  assert _s3_path("/a/../b/./c") == "/a/../b/./c"
93
93
 
94
- def test_non_s3_double_encodes(self):
95
- # "/foo bar" → first encode → "/foo%20bar" → second → "/foo%2520bar"
96
- assert _std_path("/foo bar") == "/foo%2520bar"
97
-
98
- def test_non_s3_already_encoded_double_encodes(self):
99
- # "/foo%20bar" gets unquoted to "/foo bar", then double-encoded.
94
+ def test_non_s3_encodes_wire_path_once_more(self):
95
+ # The path arrives URI-encoded once ("/foo bar" is sent as "/foo%20bar").
100
96
  assert _std_path("/foo%20bar") == "/foo%2520bar"
97
+ assert _std_path("/a%3Ab/caf%C3%A9~") == "/a%253Ab/caf%25C3%25A9~"
98
+
99
+ def test_non_s3_keeps_encoded_slash_inside_segment(self):
100
+ # An ARN in a path label: its "/" is sent as "%2F" and must stay part
101
+ # of the segment, not become a separator.
102
+ assert (
103
+ _std_path("/model/arn%3Aaws%3Abedrock%3A%3Afoundation-model%2Fx/converse")
104
+ == "/model/arn%253Aaws%253Abedrock%253A%253Afoundation-model%252Fx/converse"
105
+ )
106
+ assert _s3_path("/bucket/a%2Fb") == "/bucket/a%2Fb"
107
+
108
+ def test_non_s3_encodes_literal_reserved_characters_once(self):
109
+ # "@" and "$" appear literally in a few URI templates and are sent as is.
110
+ assert _std_path("/@connections/abc") == "/%40connections/abc"
111
+ assert _std_path("/things/t/jobs/$next") == "/things/t/jobs/%24next"
112
+
113
+ def test_non_s3_does_not_decode_the_wire_path(self):
114
+ assert _std_path("/a%2fb") == "/a%252fb"
115
+ assert _std_path("/a%25b") == "/a%2525b"
101
116
 
102
117
  def test_non_s3_removes_dot_segments(self):
103
118
  # Mirrors botocore's normalize_url_path: "." dropped, ".." pops,