capo-lambda-core 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.
Files changed (86) hide show
  1. capo_lambda_core/__init__.py +71 -0
  2. capo_lambda_core/_async.py +25 -0
  3. capo_lambda_core/_auth/_identity.py +16 -0
  4. capo_lambda_core/_auth/_providers.py +886 -0
  5. capo_lambda_core/_auth/_signers.py +121 -0
  6. capo_lambda_core/_auth/_sigv4.py +579 -0
  7. capo_lambda_core/_auth/_zapros_handler.py +80 -0
  8. capo_lambda_core/_body.py +221 -0
  9. capo_lambda_core/_iter.py +113 -0
  10. capo_lambda_core/_operations/lambda_core_api_service/create_network_connector.py +166 -0
  11. capo_lambda_core/_operations/lambda_core_api_service/delete_network_connector.py +163 -0
  12. capo_lambda_core/_operations/lambda_core_api_service/get_network_connector.py +162 -0
  13. capo_lambda_core/_operations/lambda_core_api_service/list_network_connectors.py +166 -0
  14. capo_lambda_core/_operations/lambda_core_api_service/update_network_connector.py +169 -0
  15. capo_lambda_core/_pagination.py +21 -0
  16. capo_lambda_core/_protocol/__init__.py +1 -0
  17. capo_lambda_core/_protocol/errors.py +93 -0
  18. capo_lambda_core/_protocol/eventstream.py +240 -0
  19. capo_lambda_core/_protocol/serialize.py +47 -0
  20. capo_lambda_core/_protocol/xml.py +33 -0
  21. capo_lambda_core/_resources/lambda_core_api_service/network_connector.py +616 -0
  22. capo_lambda_core/_rule_engine/__init__.py +0 -0
  23. capo_lambda_core/_rule_engine/_aws_partition.py +160 -0
  24. capo_lambda_core/_rule_engine/_endpoint_rule_set.py +135 -0
  25. capo_lambda_core/_rule_engine/_endpoint_runtime.py +389 -0
  26. capo_lambda_core/_services/_aws_config.py +160 -0
  27. capo_lambda_core/_services/_pipeline.py +247 -0
  28. capo_lambda_core/_services/async_lambda_core.py +468 -0
  29. capo_lambda_core/_services/lambda_core.py +463 -0
  30. capo_lambda_core/errors/__init__.py +39 -0
  31. capo_lambda_core/errors/_base.py +94 -0
  32. capo_lambda_core/errors/invalid_parameter_value_exception.py +59 -0
  33. capo_lambda_core/errors/network_connector_limit_exceeded_exception.py +60 -0
  34. capo_lambda_core/errors/resource_conflict_exception.py +57 -0
  35. capo_lambda_core/errors/resource_not_found_exception.py +57 -0
  36. capo_lambda_core/errors/service_exception.py +55 -0
  37. capo_lambda_core/errors/too_many_requests_exception.py +74 -0
  38. capo_lambda_core/py.typed +0 -0
  39. capo_lambda_core/types/_prelude/blob.py +12 -0
  40. capo_lambda_core/types/_prelude/timestamp.py +17 -0
  41. capo_lambda_core/types/associated_compute_resource_types_list.py +31 -0
  42. capo_lambda_core/types/client_token_string.py +5 -0
  43. capo_lambda_core/types/compute_resource_type.py +14 -0
  44. capo_lambda_core/types/core_timestamp.py +20 -0
  45. capo_lambda_core/types/create_network_connector_request.py +88 -0
  46. capo_lambda_core/types/create_network_connector_response.py +93 -0
  47. capo_lambda_core/types/delete_network_connector_request.py +25 -0
  48. capo_lambda_core/types/delete_network_connector_response.py +93 -0
  49. capo_lambda_core/types/get_network_connector_request.py +25 -0
  50. capo_lambda_core/types/get_network_connector_response.py +196 -0
  51. capo_lambda_core/types/list_network_connectors_request.py +34 -0
  52. capo_lambda_core/types/list_network_connectors_response.py +54 -0
  53. capo_lambda_core/types/max_hundred_list_items.py +5 -0
  54. capo_lambda_core/types/network_connector_arn.py +5 -0
  55. capo_lambda_core/types/network_connector_configuration.py +48 -0
  56. capo_lambda_core/types/network_connector_id.py +6 -0
  57. capo_lambda_core/types/network_connector_identifier.py +6 -0
  58. capo_lambda_core/types/network_connector_last_update_status.py +18 -0
  59. capo_lambda_core/types/network_connector_last_update_status_reason.py +5 -0
  60. capo_lambda_core/types/network_connector_last_update_status_reason_code.py +22 -0
  61. capo_lambda_core/types/network_connector_name.py +5 -0
  62. capo_lambda_core/types/network_connector_role_arn.py +5 -0
  63. capo_lambda_core/types/network_connector_security_group_id.py +5 -0
  64. capo_lambda_core/types/network_connector_security_group_ids.py +19 -0
  65. capo_lambda_core/types/network_connector_state.py +21 -0
  66. capo_lambda_core/types/network_connector_state_reason_code.py +22 -0
  67. capo_lambda_core/types/network_connector_subnet_id.py +5 -0
  68. capo_lambda_core/types/network_connector_subnet_ids.py +19 -0
  69. capo_lambda_core/types/network_connector_summary.py +94 -0
  70. capo_lambda_core/types/network_connector_tag_key.py +5 -0
  71. capo_lambda_core/types/network_connector_tag_value.py +5 -0
  72. capo_lambda_core/types/network_connector_tags.py +29 -0
  73. capo_lambda_core/types/network_connector_type.py +14 -0
  74. capo_lambda_core/types/network_connector_version.py +5 -0
  75. capo_lambda_core/types/network_connector_vpc_egress_configuration.py +103 -0
  76. capo_lambda_core/types/network_connectors_list.py +35 -0
  77. capo_lambda_core/types/network_protocol.py +17 -0
  78. capo_lambda_core/types/string.py +5 -0
  79. capo_lambda_core/types/throttle_reason.py +21 -0
  80. capo_lambda_core/types/update_network_connector_request.py +64 -0
  81. capo_lambda_core/types/update_network_connector_response.py +138 -0
  82. capo_lambda_core-0.1.0.dist-info/METADATA +103 -0
  83. capo_lambda_core-0.1.0.dist-info/RECORD +86 -0
  84. capo_lambda_core-0.1.0.dist-info/WHEEL +5 -0
  85. capo_lambda_core-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. capo_lambda_core-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ import configparser
4
+ import functools
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import TypeVar
9
+
10
+ from capo_lambda_core._services._pipeline import (
11
+ AsyncInterceptor,
12
+ AsyncNextFn,
13
+ AsyncOperationRequest,
14
+ AsyncOperationResponse,
15
+ Interceptor,
16
+ NextFn,
17
+ OperationRequest,
18
+ OperationResponse,
19
+ )
20
+
21
+ ENDPOINT_ENV_VAR = "AWS_ENDPOINT_URL_LAMBDA_CORE"
22
+ SERVICE_ID = "lambda_core"
23
+ TInput = TypeVar("TInput")
24
+ TOutput = TypeVar("TOutput")
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class AwsSettings:
29
+ region: str | None
30
+ endpoint: str | None
31
+ use_fips: bool
32
+ use_dual_stack: bool
33
+ max_attempts: int
34
+
35
+
36
+ def _env_bool(name: str) -> bool | None:
37
+ val = os.environ.get(name)
38
+ if val is None:
39
+ return None
40
+ return val.strip().lower() in ("true", "1")
41
+
42
+
43
+ def _profile_bool(section: dict[str, str], key: str) -> bool | None:
44
+ val = section.get(key)
45
+ if val is None:
46
+ return None
47
+ return val.strip().lower() in ("true", "1")
48
+
49
+
50
+ def config_file() -> Path:
51
+ raw_cfg = os.environ.get("AWS_CONFIG_FILE")
52
+ return Path(raw_cfg).expanduser() if raw_cfg else Path.home() / ".aws" / "config"
53
+
54
+
55
+ def active_profile() -> str:
56
+ return (
57
+ os.environ.get("AWS_PROFILE")
58
+ or os.environ.get("AWS_DEFAULT_PROFILE")
59
+ or "default"
60
+ )
61
+
62
+
63
+ def _load_profile(
64
+ profile: str | None = None,
65
+ ) -> tuple[dict[str, str], dict[str, dict[str, str]]]:
66
+ profile = profile or active_profile()
67
+ cfg_file = config_file()
68
+ merged: dict[str, str] = {}
69
+ services: dict[str, dict[str, str]] = {}
70
+ if cfg_file.is_file():
71
+ cfg = configparser.ConfigParser(interpolation=None)
72
+ cfg.read(cfg_file)
73
+ key = "default" if profile == "default" else f"profile {profile}"
74
+ if cfg.has_section(key):
75
+ merged.update(dict(cfg.items(key)))
76
+ services_name = merged.get("services")
77
+ if services_name and cfg.has_section(f"services {services_name}"):
78
+ raw = dict(cfg.items(f"services {services_name}"))
79
+ for svc, block in raw.items():
80
+ parsed: dict[str, str] = {}
81
+ for line in block.splitlines():
82
+ stripped = line.strip()
83
+ if "=" in stripped:
84
+ k, _, val = stripped.partition("=")
85
+ parsed[k.strip()] = val.strip()
86
+ services[svc] = parsed
87
+ return merged, services
88
+
89
+
90
+ @functools.cache
91
+ def load_aws_settings() -> AwsSettings:
92
+ profile, services = _load_profile()
93
+ region = os.environ.get("AWS_REGION") or profile.get("region")
94
+ endpoint = (
95
+ os.environ.get(ENDPOINT_ENV_VAR)
96
+ or os.environ.get("AWS_ENDPOINT_URL")
97
+ or services.get(SERVICE_ID, {}).get("endpoint_url")
98
+ or profile.get("endpoint_url")
99
+ )
100
+ use_fips: bool | None = _env_bool("AWS_USE_FIPS_ENDPOINT")
101
+ if use_fips is None:
102
+ use_fips = _profile_bool(profile, "use_fips_endpoint")
103
+ if use_fips is None:
104
+ use_fips = False
105
+ use_dual_stack: bool | None = _env_bool("AWS_USE_DUALSTACK_ENDPOINT")
106
+ if use_dual_stack is None:
107
+ use_dual_stack = _profile_bool(profile, "use_dualstack_endpoint")
108
+ if use_dual_stack is None:
109
+ use_dual_stack = False
110
+ max_attempts_raw = os.environ.get("AWS_MAX_ATTEMPTS") or profile.get("max_attempts")
111
+ max_attempts = int(max_attempts_raw) if max_attempts_raw else 3
112
+ return AwsSettings(
113
+ region=region,
114
+ endpoint=endpoint,
115
+ use_fips=use_fips,
116
+ use_dual_stack=use_dual_stack,
117
+ max_attempts=max_attempts,
118
+ )
119
+
120
+
121
+ def aws_config() -> Interceptor[TInput, TOutput]:
122
+ def interceptor(
123
+ request: OperationRequest[TInput], next: NextFn[TInput, TOutput]
124
+ ) -> OperationResponse[TOutput]:
125
+ settings = load_aws_settings()
126
+ options = request.options
127
+ if options.region is None:
128
+ options.region = settings.region
129
+ if options.endpoint is None:
130
+ options.endpoint = settings.endpoint
131
+ if options.use_fips is None:
132
+ options.use_fips = settings.use_fips
133
+ if options.use_dual_stack is None:
134
+ options.use_dual_stack = settings.use_dual_stack
135
+ if options.retry_max_attempts is None:
136
+ options.retry_max_attempts = settings.max_attempts
137
+ return next(request)
138
+
139
+ return interceptor
140
+
141
+
142
+ def aaws_config() -> AsyncInterceptor[TInput, TOutput]:
143
+ async def interceptor(
144
+ request: AsyncOperationRequest[TInput], next: AsyncNextFn[TInput, TOutput]
145
+ ) -> AsyncOperationResponse[TOutput]:
146
+ settings = load_aws_settings()
147
+ options = request.options
148
+ if options.region is None:
149
+ options.region = settings.region
150
+ if options.endpoint is None:
151
+ options.endpoint = settings.endpoint
152
+ if options.use_fips is None:
153
+ options.use_fips = settings.use_fips
154
+ if options.use_dual_stack is None:
155
+ options.use_dual_stack = settings.use_dual_stack
156
+ if options.retry_max_attempts is None:
157
+ options.retry_max_attempts = settings.max_attempts
158
+ return await next(request)
159
+
160
+ return interceptor
@@ -0,0 +1,247 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ from collections.abc import AsyncIterator, Iterator, Sequence
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Awaitable, Callable, Generic, TypeVar, cast
8
+
9
+ from zapros import (
10
+ AsyncClient,
11
+ Client,
12
+ ConnectionError,
13
+ ConnectTimeoutError,
14
+ PoolTimeoutError,
15
+ Response,
16
+ SSLError,
17
+ TimeoutError,
18
+ )
19
+
20
+ from capo_lambda_core._async import anysleep
21
+ from capo_lambda_core._body import Body
22
+ from capo_lambda_core._iter import StaticAnyIterator
23
+ from capo_lambda_core.errors import ServiceError
24
+
25
+ if TYPE_CHECKING:
26
+ from capo_lambda_core._auth._identity import Credentials
27
+ from capo_lambda_core._auth._providers import IdentityProvider
28
+
29
+ TInput = TypeVar("TInput")
30
+ TOutput = TypeVar("TOutput")
31
+
32
+
33
+ @dataclass
34
+ class OperationOptions:
35
+ client: Client
36
+ use_dual_stack: bool | None = None
37
+ use_fips: bool | None = None
38
+ region: str | None = None
39
+ endpoint: str | None = None
40
+ retry_max_attempts: int | None = None
41
+ credentials_provider: IdentityProvider[Credentials] | None = None
42
+
43
+
44
+ @dataclass
45
+ class AsyncOperationOptions:
46
+ client: AsyncClient
47
+ use_dual_stack: bool | None = None
48
+ use_fips: bool | None = None
49
+ region: str | None = None
50
+ endpoint: str | None = None
51
+ retry_max_attempts: int | None = None
52
+ credentials_provider: IdentityProvider[Credentials] | None = None
53
+
54
+
55
+ @dataclass
56
+ class OperationRequest(Generic[TInput]):
57
+ input: TInput
58
+ options: OperationOptions
59
+
60
+
61
+ @dataclass
62
+ class AsyncOperationRequest(Generic[TInput]):
63
+ input: TInput
64
+ options: AsyncOperationOptions
65
+
66
+
67
+ @dataclass
68
+ class OperationResponse(Generic[TOutput]):
69
+ output: TOutput
70
+ response: Response
71
+
72
+
73
+ @dataclass
74
+ class AsyncOperationResponse(Generic[TOutput]):
75
+ output: TOutput
76
+ response: Response
77
+
78
+
79
+ NextFn = Callable[
80
+ [OperationRequest[TInput]],
81
+ OperationResponse[TOutput],
82
+ ]
83
+
84
+ AsyncNextFn = Callable[
85
+ [AsyncOperationRequest[TInput]],
86
+ Awaitable[AsyncOperationResponse[TOutput]],
87
+ ]
88
+
89
+ Interceptor = Callable[
90
+ [OperationRequest[TInput], NextFn[TInput, TOutput]],
91
+ OperationResponse[TOutput],
92
+ ]
93
+
94
+ AsyncInterceptor = Callable[
95
+ [AsyncOperationRequest[TInput], AsyncNextFn[TInput, TOutput]],
96
+ Awaitable[AsyncOperationResponse[TOutput]],
97
+ ]
98
+
99
+
100
+ def execute_pipeline(
101
+ request: OperationRequest[TInput],
102
+ handler: NextFn[TInput, TOutput],
103
+ interceptors: Sequence[Interceptor[TInput, TOutput]],
104
+ ) -> OperationResponse[TOutput]:
105
+ def make_chain(index: int) -> NextFn[TInput, TOutput]:
106
+ if index < len(interceptors):
107
+ interceptor = interceptors[index]
108
+
109
+ def next_fn(req: OperationRequest[TInput]) -> OperationResponse[TOutput]:
110
+ return interceptor(req, make_chain(index + 1))
111
+
112
+ return next_fn
113
+ return handler
114
+
115
+ return make_chain(0)(request)
116
+
117
+
118
+ async def aexecute_pipeline(
119
+ request: AsyncOperationRequest[TInput],
120
+ handler: AsyncNextFn[TInput, TOutput],
121
+ interceptors: Sequence[AsyncInterceptor[TInput, TOutput]],
122
+ ) -> AsyncOperationResponse[TOutput]:
123
+ def make_chain(index: int) -> AsyncNextFn[TInput, TOutput]:
124
+ if index < len(interceptors):
125
+ interceptor = interceptors[index]
126
+
127
+ async def next_fn(
128
+ req: AsyncOperationRequest[TInput],
129
+ ) -> AsyncOperationResponse[TOutput]:
130
+ return await interceptor(req, make_chain(index + 1))
131
+
132
+ return next_fn
133
+ return handler
134
+
135
+ return await make_chain(0)(request)
136
+
137
+
138
+ def _is_retryable(exc: Exception) -> bool:
139
+ if isinstance(exc, ServiceError):
140
+ return exc.is_retryable
141
+ if isinstance(exc, SSLError):
142
+ return False
143
+ if isinstance(exc, (ConnectionError, TimeoutError)):
144
+ return True
145
+ return False
146
+
147
+
148
+ def _is_pre_transmission(exc: Exception) -> bool:
149
+ # True when the request body was certainly not written before the error,
150
+ # so the same stream can be reused as-is. A bare ConnectionError is
151
+ # ambiguous ("cannot be established or is lost"), so it does not qualify.
152
+ return isinstance(exc, (ConnectTimeoutError, PoolTimeoutError))
153
+
154
+
155
+ def _prepare_retry(inp: object) -> bool:
156
+ if not isinstance(inp, dict):
157
+ return True
158
+ for value in inp.values():
159
+ if isinstance(value, Body):
160
+ value = cast(Body[Iterator[bytes]], value)
161
+ if value.rebuild() is None:
162
+ return False
163
+ elif isinstance(value, StaticAnyIterator):
164
+ continue
165
+ elif isinstance(value, (Iterator, AsyncIterator)):
166
+ return False
167
+ return True
168
+
169
+
170
+ async def _aprepare_retry(inp: object) -> bool:
171
+ if not isinstance(inp, dict):
172
+ return True
173
+ for value in inp.values():
174
+ if isinstance(value, Body):
175
+ value = cast(Body[AsyncIterator[bytes]], value)
176
+ if await value.arebuild() is None:
177
+ return False
178
+ elif isinstance(value, StaticAnyIterator):
179
+ continue
180
+ elif isinstance(value, (Iterator, AsyncIterator)):
181
+ return False
182
+ return True
183
+
184
+
185
+ def _retry_delay(attempt: int, is_throttling: bool) -> float:
186
+ base = 1.0 if is_throttling else 0.5
187
+ delay = base * (2.0 ** min(attempt - 1, 10))
188
+ delay = min(20.0, delay)
189
+ return random.uniform(0.0, delay)
190
+
191
+
192
+ def retry() -> Interceptor[TInput, TOutput]:
193
+ def interceptor(
194
+ request: OperationRequest[TInput], next: NextFn[TInput, TOutput]
195
+ ) -> OperationResponse[TOutput]:
196
+ max_attempts = request.options.retry_max_attempts or 3
197
+ last_exc: Exception | None = None
198
+ for attempt in range(1, max_attempts + 1):
199
+ try:
200
+ return next(request)
201
+ except Exception as exc:
202
+ if not _is_retryable(exc):
203
+ raise
204
+ last_exc = exc
205
+ if attempt < max_attempts:
206
+ if not _is_pre_transmission(exc) and not _prepare_retry(
207
+ request.input
208
+ ):
209
+ raise
210
+ is_throttling = (
211
+ isinstance(exc, ServiceError) and exc.is_throttling_error
212
+ )
213
+ time.sleep(_retry_delay(attempt, is_throttling))
214
+
215
+ assert last_exc
216
+ raise last_exc
217
+
218
+ return interceptor
219
+
220
+
221
+ def aretry() -> AsyncInterceptor[TInput, TOutput]:
222
+ async def interceptor(
223
+ request: AsyncOperationRequest[TInput], next: AsyncNextFn[TInput, TOutput]
224
+ ) -> AsyncOperationResponse[TOutput]:
225
+ max_attempts = request.options.retry_max_attempts or 3
226
+ last_exc: Exception | None = None
227
+ for attempt in range(1, max_attempts + 1):
228
+ try:
229
+ return await next(request)
230
+ except Exception as exc:
231
+ if not _is_retryable(exc):
232
+ raise
233
+ last_exc = exc
234
+ if attempt < max_attempts:
235
+ if not _is_pre_transmission(exc) and not await _aprepare_retry(
236
+ request.input
237
+ ):
238
+ raise
239
+ is_throttling = (
240
+ isinstance(exc, ServiceError) and exc.is_throttling_error
241
+ )
242
+ await anysleep(_retry_delay(attempt, is_throttling))
243
+
244
+ assert last_exc
245
+ raise last_exc
246
+
247
+ return interceptor