1313from __future__ import annotations
1414
1515import time
16+ from datetime import datetime , timezone
1617from pathlib import Path
1718from typing import TYPE_CHECKING , Any
1819
2324from airbyte .exceptions import PyAirbyteInputError
2425
2526
27+ NAMESPACE_PREFIX = "zz_deleteme"
28+ """Prefix for auto-generated smoke test namespaces.
29+
30+ The ``zz_`` prefix sorts last alphabetically; ``deleteme`` signals the
31+ namespace is safe for automated cleanup.
32+ """
33+
34+ DEFAULT_NAMESPACE_SUFFIX = "smoke_test"
35+ """Default suffix appended when no explicit suffix is provided."""
36+
37+
2638if TYPE_CHECKING :
2739 from airbyte .destinations .base import Destination
2840 from airbyte .sources .base import Source
2941
3042
43+ def generate_namespace (
44+ * ,
45+ namespace_suffix : str | None = None ,
46+ ) -> str :
47+ """Generate a smoke-test namespace.
48+
49+ Format: ``zz_deleteme_yyyymmdd_hhmm_<suffix>``.
50+ The ``zz_`` prefix sorts last alphabetically and the ``deleteme``
51+ token acts as a guard for automated cleanup scripts.
52+
53+ If *namespace_suffix* is not provided, ``smoke_test`` is used as the
54+ default suffix.
55+ """
56+ suffix = namespace_suffix or DEFAULT_NAMESPACE_SUFFIX
57+ now = datetime .now (tz = timezone .utc )
58+ ts = now .strftime ("%Y%m%d_%H%M" )
59+ return f"{ NAMESPACE_PREFIX } _{ ts } _{ suffix } "
60+
61+
3162class DestinationSmokeTestResult (BaseModel ):
3263 """Result of a destination smoke test run."""
3364
@@ -37,6 +68,9 @@ class DestinationSmokeTestResult(BaseModel):
3768 destination : str
3869 """The destination connector name."""
3970
71+ namespace : str
72+ """The namespace used for this smoke test run."""
73+
4074 records_delivered : int
4175 """Total number of records delivered to the destination."""
4276
@@ -53,6 +87,7 @@ class DestinationSmokeTestResult(BaseModel):
5387def get_smoke_test_source (
5488 * ,
5589 scenarios : str | list [str ] = "fast" ,
90+ namespace : str | None = None ,
5691 custom_scenarios : list [dict [str , Any ]] | None = None ,
5792 custom_scenarios_file : str | None = None ,
5893) -> Source :
@@ -70,6 +105,9 @@ def get_smoke_test_source(
70105
71106 `custom_scenarios` is an optional list of scenario dicts to inject directly.
72107
108+ `namespace` is an optional namespace to set on all streams. When provided,
109+ the destination will write data into this namespace (schema, database, etc.).
110+
73111 `custom_scenarios_file` is an optional path to a JSON or YAML file containing
74112 additional scenario definitions. Each scenario should have `name`, `json_schema`,
75113 and optionally `records` and `primary_key`.
@@ -134,6 +172,9 @@ def get_smoke_test_source(
134172 existing = source_config .get ("custom_scenarios" , [])
135173 source_config ["custom_scenarios" ] = existing + file_scenarios
136174
175+ if namespace :
176+ source_config ["namespace" ] = namespace
177+
137178 return get_source (
138179 name = "source-smoke-test" ,
139180 config = source_config ,
@@ -157,6 +198,8 @@ def run_destination_smoke_test(
157198 * ,
158199 destination : Destination ,
159200 scenarios : str | list [str ] = "fast" ,
201+ namespace_suffix : str | None = None ,
202+ reuse_namespace : str | None = None ,
160203 custom_scenarios : list [dict [str , Any ]] | None = None ,
161204 custom_scenarios_file : str | None = None ,
162205) -> DestinationSmokeTestResult :
@@ -177,13 +220,26 @@ def run_destination_smoke_test(
177220 - `'all'`: runs every scenario including `large_batch_stream`.
178221 - A comma-separated string or list of specific scenario names.
179222
223+ `namespace_suffix` is an optional suffix appended to the auto-generated
224+ namespace. Defaults to ``smoke_test`` when not provided
225+ (e.g. ``zz_deleteme_20260318_2256_smoke_test``).
226+
227+ `reuse_namespace` is an exact namespace string to reuse from a previous
228+ run. When set, no new namespace is generated.
229+
180230 `custom_scenarios` is an optional list of scenario dicts to inject.
181231
182232 `custom_scenarios_file` is an optional path to a JSON/YAML file with
183233 additional scenario definitions.
184234 """
235+ # Determine namespace
236+ namespace = reuse_namespace or generate_namespace (
237+ namespace_suffix = namespace_suffix ,
238+ )
239+
185240 source_obj = get_smoke_test_source (
186241 scenarios = scenarios ,
242+ namespace = namespace ,
187243 custom_scenarios = custom_scenarios ,
188244 custom_scenarios_file = custom_scenarios_file ,
189245 )
@@ -214,6 +270,7 @@ def run_destination_smoke_test(
214270 return DestinationSmokeTestResult (
215271 success = success ,
216272 destination = destination .name ,
273+ namespace = namespace ,
217274 records_delivered = records_delivered ,
218275 scenarios_requested = scenarios_str ,
219276 elapsed_seconds = round (elapsed , 2 ),
0 commit comments