capo-keyspacesstreams 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 (85) hide show
  1. capo_keyspacesstreams/__init__.py +74 -0
  2. capo_keyspacesstreams/_async.py +25 -0
  3. capo_keyspacesstreams/_auth/_identity.py +16 -0
  4. capo_keyspacesstreams/_auth/_providers.py +886 -0
  5. capo_keyspacesstreams/_auth/_signers.py +88 -0
  6. capo_keyspacesstreams/_auth/_sigv4.py +434 -0
  7. capo_keyspacesstreams/_auth/_zapros_handler.py +80 -0
  8. capo_keyspacesstreams/_iter.py +113 -0
  9. capo_keyspacesstreams/_operations/keyspaces_streams/get_records.py +171 -0
  10. capo_keyspacesstreams/_operations/keyspaces_streams/get_shard_iterator.py +170 -0
  11. capo_keyspacesstreams/_operations/keyspaces_streams/get_stream.py +174 -0
  12. capo_keyspacesstreams/_operations/keyspaces_streams/list_streams.py +170 -0
  13. capo_keyspacesstreams/_pagination.py +21 -0
  14. capo_keyspacesstreams/_protocol/__init__.py +1 -0
  15. capo_keyspacesstreams/_protocol/errors.py +93 -0
  16. capo_keyspacesstreams/_protocol/eventstream.py +238 -0
  17. capo_keyspacesstreams/_protocol/serialize.py +47 -0
  18. capo_keyspacesstreams/_protocol/xml.py +33 -0
  19. capo_keyspacesstreams/_rule_engine/__init__.py +0 -0
  20. capo_keyspacesstreams/_rule_engine/_aws_partition.py +160 -0
  21. capo_keyspacesstreams/_rule_engine/_endpoint_rule_set.py +65 -0
  22. capo_keyspacesstreams/_rule_engine/_endpoint_runtime.py +389 -0
  23. capo_keyspacesstreams/_services/_aws_config.py +156 -0
  24. capo_keyspacesstreams/_services/_pipeline.py +196 -0
  25. capo_keyspacesstreams/_services/async_keyspaces_streams.py +442 -0
  26. capo_keyspacesstreams/_services/keyspaces_streams.py +438 -0
  27. capo_keyspacesstreams/errors/__init__.py +32 -0
  28. capo_keyspacesstreams/errors/_base.py +94 -0
  29. capo_keyspacesstreams/errors/access_denied_exception.py +47 -0
  30. capo_keyspacesstreams/errors/internal_server_exception.py +47 -0
  31. capo_keyspacesstreams/errors/resource_not_found_exception.py +47 -0
  32. capo_keyspacesstreams/errors/throttling_exception.py +47 -0
  33. capo_keyspacesstreams/errors/validation_exception.py +72 -0
  34. capo_keyspacesstreams/py.typed +0 -0
  35. capo_keyspacesstreams/types/_prelude/blob.py +12 -0
  36. capo_keyspacesstreams/types/_prelude/timestamp.py +17 -0
  37. capo_keyspacesstreams/types/date.py +20 -0
  38. capo_keyspacesstreams/types/get_records_input.py +37 -0
  39. capo_keyspacesstreams/types/get_records_output.py +70 -0
  40. capo_keyspacesstreams/types/get_shard_iterator_input.py +70 -0
  41. capo_keyspacesstreams/types/get_shard_iterator_output.py +30 -0
  42. capo_keyspacesstreams/types/get_stream_input.py +63 -0
  43. capo_keyspacesstreams/types/get_stream_output.py +144 -0
  44. capo_keyspacesstreams/types/iterator_description.py +42 -0
  45. capo_keyspacesstreams/types/iterator_position.py +17 -0
  46. capo_keyspacesstreams/types/keyspace_name.py +5 -0
  47. capo_keyspacesstreams/types/keyspaces_cell.py +63 -0
  48. capo_keyspacesstreams/types/keyspaces_cell_list.py +35 -0
  49. capo_keyspacesstreams/types/keyspaces_cell_map.py +39 -0
  50. capo_keyspacesstreams/types/keyspaces_cell_map_definition.py +83 -0
  51. capo_keyspacesstreams/types/keyspaces_cell_value.py +333 -0
  52. capo_keyspacesstreams/types/keyspaces_cells.py +35 -0
  53. capo_keyspacesstreams/types/keyspaces_keys_map.py +39 -0
  54. capo_keyspacesstreams/types/keyspaces_metadata.py +29 -0
  55. capo_keyspacesstreams/types/keyspaces_row.py +83 -0
  56. capo_keyspacesstreams/types/keyspaces_udt_map.py +35 -0
  57. capo_keyspacesstreams/types/list_streams_input.py +50 -0
  58. capo_keyspacesstreams/types/list_streams_output.py +47 -0
  59. capo_keyspacesstreams/types/origin_type.py +18 -0
  60. capo_keyspacesstreams/types/record.py +146 -0
  61. capo_keyspacesstreams/types/record_list.py +29 -0
  62. capo_keyspacesstreams/types/sequence_number.py +5 -0
  63. capo_keyspacesstreams/types/sequence_number_range.py +38 -0
  64. capo_keyspacesstreams/types/shard.py +70 -0
  65. capo_keyspacesstreams/types/shard_description_list.py +29 -0
  66. capo_keyspacesstreams/types/shard_filter.py +47 -0
  67. capo_keyspacesstreams/types/shard_filter_type.py +14 -0
  68. capo_keyspacesstreams/types/shard_id.py +5 -0
  69. capo_keyspacesstreams/types/shard_id_list.py +17 -0
  70. capo_keyspacesstreams/types/shard_id_token.py +5 -0
  71. capo_keyspacesstreams/types/shard_iterator.py +5 -0
  72. capo_keyspacesstreams/types/shard_iterator_type.py +19 -0
  73. capo_keyspacesstreams/types/stream.py +54 -0
  74. capo_keyspacesstreams/types/stream_arn.py +5 -0
  75. capo_keyspacesstreams/types/stream_arn_token.py +5 -0
  76. capo_keyspacesstreams/types/stream_list.py +29 -0
  77. capo_keyspacesstreams/types/stream_status.py +19 -0
  78. capo_keyspacesstreams/types/stream_view_type.py +19 -0
  79. capo_keyspacesstreams/types/table_name.py +5 -0
  80. capo_keyspacesstreams/types/validation_exception_type.py +19 -0
  81. capo_keyspacesstreams-0.1.0.dist-info/METADATA +101 -0
  82. capo_keyspacesstreams-0.1.0.dist-info/RECORD +85 -0
  83. capo_keyspacesstreams-0.1.0.dist-info/WHEEL +5 -0
  84. capo_keyspacesstreams-0.1.0.dist-info/licenses/LICENSE +21 -0
  85. capo_keyspacesstreams-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,156 @@
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_keyspacesstreams._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_KEYSPACES_STREAMS"
22
+ SERVICE_ID = "keyspaces_streams"
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.retry_max_attempts is None:
134
+ options.retry_max_attempts = settings.max_attempts
135
+ return next(request)
136
+
137
+ return interceptor
138
+
139
+
140
+ def aaws_config() -> AsyncInterceptor[TInput, TOutput]:
141
+ async def interceptor(
142
+ request: AsyncOperationRequest[TInput], next: AsyncNextFn[TInput, TOutput]
143
+ ) -> AsyncOperationResponse[TOutput]:
144
+ settings = load_aws_settings()
145
+ options = request.options
146
+ if options.region is None:
147
+ options.region = settings.region
148
+ if options.endpoint is None:
149
+ options.endpoint = settings.endpoint
150
+ if options.use_fips is None:
151
+ options.use_fips = settings.use_fips
152
+ if options.retry_max_attempts is None:
153
+ options.retry_max_attempts = settings.max_attempts
154
+ return await next(request)
155
+
156
+ return interceptor
@@ -0,0 +1,196 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Awaitable, Callable, Generic, TypeVar
8
+
9
+ from zapros import (
10
+ AsyncClient,
11
+ Client,
12
+ ConnectionError,
13
+ Response,
14
+ SSLError,
15
+ TimeoutError,
16
+ )
17
+
18
+ from capo_keyspacesstreams._async import anysleep
19
+ from capo_keyspacesstreams.errors import ServiceError
20
+
21
+ if TYPE_CHECKING:
22
+ from capo_keyspacesstreams._auth._identity import Credentials
23
+ from capo_keyspacesstreams._auth._providers import IdentityProvider
24
+
25
+ TInput = TypeVar("TInput")
26
+ TOutput = TypeVar("TOutput")
27
+
28
+
29
+ @dataclass
30
+ class OperationOptions:
31
+ client: Client
32
+ use_fips: bool | None = None
33
+ endpoint: str | None = None
34
+ region: str | None = None
35
+ retry_max_attempts: int | None = None
36
+ credentials_provider: IdentityProvider[Credentials] | None = None
37
+
38
+
39
+ @dataclass
40
+ class AsyncOperationOptions:
41
+ client: AsyncClient
42
+ use_fips: bool | None = None
43
+ endpoint: str | None = None
44
+ region: str | None = None
45
+ retry_max_attempts: int | None = None
46
+ credentials_provider: IdentityProvider[Credentials] | None = None
47
+
48
+
49
+ @dataclass
50
+ class OperationRequest(Generic[TInput]):
51
+ input: TInput
52
+ options: OperationOptions
53
+
54
+
55
+ @dataclass
56
+ class AsyncOperationRequest(Generic[TInput]):
57
+ input: TInput
58
+ options: AsyncOperationOptions
59
+
60
+
61
+ @dataclass
62
+ class OperationResponse(Generic[TOutput]):
63
+ output: TOutput
64
+ response: Response
65
+
66
+
67
+ @dataclass
68
+ class AsyncOperationResponse(Generic[TOutput]):
69
+ output: TOutput
70
+ response: Response
71
+
72
+
73
+ NextFn = Callable[
74
+ [OperationRequest[TInput]],
75
+ OperationResponse[TOutput],
76
+ ]
77
+
78
+ AsyncNextFn = Callable[
79
+ [AsyncOperationRequest[TInput]],
80
+ Awaitable[AsyncOperationResponse[TOutput]],
81
+ ]
82
+
83
+ Interceptor = Callable[
84
+ [OperationRequest[TInput], NextFn[TInput, TOutput]],
85
+ OperationResponse[TOutput],
86
+ ]
87
+
88
+ AsyncInterceptor = Callable[
89
+ [AsyncOperationRequest[TInput], AsyncNextFn[TInput, TOutput]],
90
+ Awaitable[AsyncOperationResponse[TOutput]],
91
+ ]
92
+
93
+
94
+ def execute_pipeline(
95
+ request: OperationRequest[TInput],
96
+ handler: NextFn[TInput, TOutput],
97
+ interceptors: Sequence[Interceptor[TInput, TOutput]],
98
+ ) -> OperationResponse[TOutput]:
99
+ def make_chain(index: int) -> NextFn[TInput, TOutput]:
100
+ if index < len(interceptors):
101
+ interceptor = interceptors[index]
102
+
103
+ def next_fn(req: OperationRequest[TInput]) -> OperationResponse[TOutput]:
104
+ return interceptor(req, make_chain(index + 1))
105
+
106
+ return next_fn
107
+ return handler
108
+
109
+ return make_chain(0)(request)
110
+
111
+
112
+ async def aexecute_pipeline(
113
+ request: AsyncOperationRequest[TInput],
114
+ handler: AsyncNextFn[TInput, TOutput],
115
+ interceptors: Sequence[AsyncInterceptor[TInput, TOutput]],
116
+ ) -> AsyncOperationResponse[TOutput]:
117
+ def make_chain(index: int) -> AsyncNextFn[TInput, TOutput]:
118
+ if index < len(interceptors):
119
+ interceptor = interceptors[index]
120
+
121
+ async def next_fn(
122
+ req: AsyncOperationRequest[TInput],
123
+ ) -> AsyncOperationResponse[TOutput]:
124
+ return await interceptor(req, make_chain(index + 1))
125
+
126
+ return next_fn
127
+ return handler
128
+
129
+ return await make_chain(0)(request)
130
+
131
+
132
+ def _is_retryable(exc: Exception) -> bool:
133
+ if isinstance(exc, ServiceError):
134
+ return exc.is_retryable
135
+ if isinstance(exc, SSLError):
136
+ return False
137
+ if isinstance(exc, (ConnectionError, TimeoutError)):
138
+ return True
139
+ return False
140
+
141
+
142
+ def _retry_delay(attempt: int, is_throttling: bool) -> float:
143
+ base = 1.0 if is_throttling else 0.5
144
+ delay = base * (2.0 ** min(attempt - 1, 10))
145
+ delay = min(20.0, delay)
146
+ return random.uniform(0.0, delay)
147
+
148
+
149
+ def retry() -> Interceptor[TInput, TOutput]:
150
+ def interceptor(
151
+ request: OperationRequest[TInput], next: NextFn[TInput, TOutput]
152
+ ) -> OperationResponse[TOutput]:
153
+ max_attempts = request.options.retry_max_attempts or 3
154
+ last_exc: Exception | None = None
155
+ for attempt in range(1, max_attempts + 1):
156
+ try:
157
+ return next(request)
158
+ except Exception as exc:
159
+ if not _is_retryable(exc):
160
+ raise
161
+ last_exc = exc
162
+ if attempt < max_attempts:
163
+ is_throttling = (
164
+ isinstance(exc, ServiceError) and exc.is_throttling_error
165
+ )
166
+ time.sleep(_retry_delay(attempt, is_throttling))
167
+
168
+ assert last_exc
169
+ raise last_exc
170
+
171
+ return interceptor
172
+
173
+
174
+ def aretry() -> AsyncInterceptor[TInput, TOutput]:
175
+ async def interceptor(
176
+ request: AsyncOperationRequest[TInput], next: AsyncNextFn[TInput, TOutput]
177
+ ) -> AsyncOperationResponse[TOutput]:
178
+ max_attempts = request.options.retry_max_attempts or 3
179
+ last_exc: Exception | None = None
180
+ for attempt in range(1, max_attempts + 1):
181
+ try:
182
+ return await next(request)
183
+ except Exception as exc:
184
+ if not _is_retryable(exc):
185
+ raise
186
+ last_exc = exc
187
+ if attempt < max_attempts:
188
+ is_throttling = (
189
+ isinstance(exc, ServiceError) and exc.is_throttling_error
190
+ )
191
+ await anysleep(_retry_delay(attempt, is_throttling))
192
+
193
+ assert last_exc
194
+ raise last_exc
195
+
196
+ return interceptor