local-web-services-python-sdk 0.1.1__tar.gz → 0.1.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.
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/PKG-INFO +1 -1
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/pyproject.toml +1 -1
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/session.py +62 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/unit/test_session_lifecycle.py +44 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/.github/workflows/ci.yml +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/.github/workflows/publish.yml +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/.gitignore +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/Makefile +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/README.md +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_builders/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_builders/chaos.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_builders/iam.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_builders/mock.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_discovery/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_discovery/cdk.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_discovery/hcl.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_logs.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_resources/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_resources/dynamodb.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_resources/s3.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_resources/sqs.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_transport/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_transport/inprocess.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/fixtures.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/unit/__init__.py +0 -0
- {local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/unit/test_session_imports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: local-web-services-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python testing SDK for local-web-services — in-process pytest fixtures and boto3 helpers
|
|
5
5
|
Project-URL: Homepage, https://local-web-services.github.io/www
|
|
6
6
|
Project-URL: Repository, https://github.com/local-web-services/local-web-services-sdk-python
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import os
|
|
6
7
|
import shutil
|
|
7
8
|
import socket
|
|
8
9
|
import tempfile
|
|
@@ -12,6 +13,26 @@ from contextlib import contextmanager
|
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
16
|
+
# Maps boto3 service name → AWS SDK endpoint URL env var.
|
|
17
|
+
# Setting these redirects *any* boto3 client created in the process to the
|
|
18
|
+
# local LWS service — no production-code changes required.
|
|
19
|
+
_SERVICE_ENV_VARS: dict[str, str] = {
|
|
20
|
+
"dynamodb": "AWS_ENDPOINT_URL_DYNAMODB",
|
|
21
|
+
"sqs": "AWS_ENDPOINT_URL_SQS",
|
|
22
|
+
"s3": "AWS_ENDPOINT_URL_S3",
|
|
23
|
+
"sns": "AWS_ENDPOINT_URL_SNS",
|
|
24
|
+
"stepfunctions": "AWS_ENDPOINT_URL_STEPFUNCTIONS",
|
|
25
|
+
"ssm": "AWS_ENDPOINT_URL_SSM",
|
|
26
|
+
"secretsmanager": "AWS_ENDPOINT_URL_SECRETSMANAGER",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Credential / region overrides so boto3 never tries to contact IAM or STS.
|
|
30
|
+
_TEST_CREDENTIALS: dict[str, str] = {
|
|
31
|
+
"AWS_ACCESS_KEY_ID": "test",
|
|
32
|
+
"AWS_SECRET_ACCESS_KEY": "test",
|
|
33
|
+
"AWS_DEFAULT_REGION": "us-east-1",
|
|
34
|
+
}
|
|
35
|
+
|
|
15
36
|
|
|
16
37
|
def _free_port() -> int:
|
|
17
38
|
"""Return a free ephemeral TCP port on localhost."""
|
|
@@ -70,6 +91,7 @@ class LwsSession:
|
|
|
70
91
|
self._thread: threading.Thread | None = None
|
|
71
92
|
self._data_dir: Path | None = None
|
|
72
93
|
self._log_handler: Any = None
|
|
94
|
+
self._saved_env: dict[str, str | None] = {}
|
|
73
95
|
|
|
74
96
|
# ── Constructors ──────────────────────────────────────────────────────────
|
|
75
97
|
|
|
@@ -139,6 +161,8 @@ class LwsSession:
|
|
|
139
161
|
exc = error_holder[0]
|
|
140
162
|
raise RuntimeError(f"LwsSession failed to start: {exc}") from exc
|
|
141
163
|
|
|
164
|
+
self._patch_env()
|
|
165
|
+
|
|
142
166
|
async def _async_start(self, ready: threading.Event) -> None:
|
|
143
167
|
"""Create providers, build apps, start servers."""
|
|
144
168
|
from lws_testing._transport.inprocess import start_services
|
|
@@ -150,6 +174,8 @@ class LwsSession:
|
|
|
150
174
|
|
|
151
175
|
def _stop(self) -> None:
|
|
152
176
|
"""Stop all servers and clean up temporary state."""
|
|
177
|
+
self._restore_env()
|
|
178
|
+
|
|
153
179
|
if self._loop and self._loop.is_running():
|
|
154
180
|
future = asyncio.run_coroutine_threadsafe(self._async_stop(), self._loop)
|
|
155
181
|
future.result(timeout=10)
|
|
@@ -167,6 +193,33 @@ class LwsSession:
|
|
|
167
193
|
|
|
168
194
|
await stop_services(self._servers)
|
|
169
195
|
|
|
196
|
+
# ── Environment patching ──────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
def _patch_env(self) -> None:
|
|
199
|
+
"""Set AWS SDK endpoint env vars so any boto3 client hits local LWS.
|
|
200
|
+
|
|
201
|
+
This is the drop-in mechanism: production code that creates boto3
|
|
202
|
+
clients the normal way (``boto3.client("dynamodb")``) is automatically
|
|
203
|
+
redirected to the local services without any code changes.
|
|
204
|
+
"""
|
|
205
|
+
for service, env_var in _SERVICE_ENV_VARS.items():
|
|
206
|
+
port = self._ports.get(service)
|
|
207
|
+
if port:
|
|
208
|
+
self._saved_env[env_var] = os.environ.get(env_var)
|
|
209
|
+
os.environ[env_var] = f"http://127.0.0.1:{port}"
|
|
210
|
+
for key, val in _TEST_CREDENTIALS.items():
|
|
211
|
+
self._saved_env[key] = os.environ.get(key)
|
|
212
|
+
os.environ[key] = val
|
|
213
|
+
|
|
214
|
+
def _restore_env(self) -> None:
|
|
215
|
+
"""Restore all env vars that were overridden by :meth:`_patch_env`."""
|
|
216
|
+
for key, saved in self._saved_env.items():
|
|
217
|
+
if saved is None:
|
|
218
|
+
os.environ.pop(key, None)
|
|
219
|
+
else:
|
|
220
|
+
os.environ[key] = saved
|
|
221
|
+
self._saved_env = {}
|
|
222
|
+
|
|
170
223
|
# ── boto3 client factory ──────────────────────────────────────────────────
|
|
171
224
|
|
|
172
225
|
def client(self, service: str) -> Any:
|
|
@@ -279,6 +332,15 @@ class LwsSession:
|
|
|
279
332
|
|
|
280
333
|
return S3Helper(bucket_name, self.client("s3"))
|
|
281
334
|
|
|
335
|
+
def queue_url(self, queue_name: str) -> str:
|
|
336
|
+
"""Return the local SQS URL for *queue_name*.
|
|
337
|
+
|
|
338
|
+
Use this to set the queue URL env var that production code reads::
|
|
339
|
+
|
|
340
|
+
os.environ["ORDER_QUEUE_URL"] = session.queue_url("OrderQueue")
|
|
341
|
+
"""
|
|
342
|
+
return f"http://127.0.0.1:{self._ports['sqs']}/000000000000/{queue_name}"
|
|
343
|
+
|
|
282
344
|
# ── Mock / chaos / IAM builders ───────────────────────────────────────────
|
|
283
345
|
|
|
284
346
|
def mock(self, service: str) -> Any:
|
|
@@ -103,3 +103,47 @@ def test_all_services_available(session):
|
|
|
103
103
|
for svc in expected_services:
|
|
104
104
|
client = session.client(svc)
|
|
105
105
|
assert client is not None, f"Expected service {svc!r} to be available"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_vanilla_boto3_client_is_redirected_to_lws(session):
|
|
109
|
+
# Arrange — env vars are set by the session; a plain boto3 client should
|
|
110
|
+
# hit LWS without any explicit endpoint_url
|
|
111
|
+
import boto3
|
|
112
|
+
|
|
113
|
+
# Act
|
|
114
|
+
vanilla_client = boto3.client("dynamodb", region_name="us-east-1")
|
|
115
|
+
response = vanilla_client.list_tables()
|
|
116
|
+
|
|
117
|
+
# Assert — LWS responded (real AWS would also return this key, but if the
|
|
118
|
+
# env var wasn't set the call would fail with NoCredentialsError or timeout)
|
|
119
|
+
assert "TableNames" in response
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_env_vars_are_restored_after_session_closes():
|
|
123
|
+
# Arrange — capture state before any session starts
|
|
124
|
+
import os
|
|
125
|
+
|
|
126
|
+
expected_key = "AWS_ENDPOINT_URL_DYNAMODB"
|
|
127
|
+
value_before = os.environ.get(expected_key)
|
|
128
|
+
|
|
129
|
+
# Act
|
|
130
|
+
with LwsSession(tables=[{"name": "Tmp", "partition_key": "id"}]):
|
|
131
|
+
actual_during = os.environ.get(expected_key)
|
|
132
|
+
|
|
133
|
+
actual_after = os.environ.get(expected_key)
|
|
134
|
+
|
|
135
|
+
# Assert
|
|
136
|
+
assert actual_during is not None, "Expected env var to be set during session"
|
|
137
|
+
assert actual_after == value_before, "Expected env var to be restored after session"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_queue_url_returns_local_url(session):
|
|
141
|
+
# Arrange
|
|
142
|
+
expected_queue_name = "OrderQueue"
|
|
143
|
+
|
|
144
|
+
# Act
|
|
145
|
+
actual_url = session.queue_url(expected_queue_name)
|
|
146
|
+
|
|
147
|
+
# Assert
|
|
148
|
+
assert "127.0.0.1" in actual_url
|
|
149
|
+
assert expected_queue_name in actual_url
|
{local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/src/lws_testing/_logs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/__init__.py
RENAMED
|
File without changes
|
{local_web_services_python_sdk-0.1.1 → local_web_services_python_sdk-0.1.2}/tests/unit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|