interloper-docker 0.72.0__tar.gz → 0.74.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.
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/PKG-INFO +1 -1
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/pyproject.toml +11 -1
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/pyproject.toml.orig +7 -3
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/src/interloper_docker/launcher.py +17 -3
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/src/interloper_docker/runner.py +73 -9
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/README.md +0 -0
- {interloper_docker-0.72.0 → interloper_docker-0.74.0}/src/interloper_docker/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "interloper-docker"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.74.0"
|
|
4
4
|
description = "Interloper Docker integration"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -34,6 +34,7 @@ workspace = true
|
|
|
34
34
|
line-length = 120
|
|
35
35
|
|
|
36
36
|
[tool.ruff.lint]
|
|
37
|
+
preview = true
|
|
37
38
|
extend-select = [
|
|
38
39
|
"E",
|
|
39
40
|
"I",
|
|
@@ -41,8 +42,13 @@ extend-select = [
|
|
|
41
42
|
"ANN001",
|
|
42
43
|
"ANN201",
|
|
43
44
|
"ANN202",
|
|
45
|
+
"DOC",
|
|
46
|
+
"D",
|
|
44
47
|
]
|
|
45
48
|
|
|
49
|
+
[tool.ruff.lint.pydocstyle]
|
|
50
|
+
convention = "google"
|
|
51
|
+
|
|
46
52
|
[tool.ruff.lint.per-file-ignores]
|
|
47
53
|
"__init__.py" = [
|
|
48
54
|
"F401",
|
|
@@ -51,4 +57,8 @@ extend-select = [
|
|
|
51
57
|
"tests/**" = [
|
|
52
58
|
"ANN",
|
|
53
59
|
"F811",
|
|
60
|
+
"D101",
|
|
61
|
+
"D102",
|
|
62
|
+
"D103",
|
|
63
|
+
"D104",
|
|
54
64
|
]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ###############
|
|
4
4
|
[project]
|
|
5
5
|
name = "interloper-docker"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.74.0"
|
|
7
7
|
description = "Interloper Docker integration"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
|
|
@@ -31,8 +31,12 @@ interloper-scheduler = { workspace = true }
|
|
|
31
31
|
line-length = 120
|
|
32
32
|
|
|
33
33
|
[tool.ruff.lint]
|
|
34
|
-
|
|
34
|
+
preview = true
|
|
35
|
+
extend-select = ["E", "I", "UP", "ANN001", "ANN201", "ANN202", "DOC", "D"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint.pydocstyle]
|
|
38
|
+
convention = "google"
|
|
35
39
|
|
|
36
40
|
[tool.ruff.lint.per-file-ignores]
|
|
37
41
|
"__init__.py" = ["F401", "F403"]
|
|
38
|
-
"tests/**" = ["ANN", "F811"]
|
|
42
|
+
"tests/**" = ["ANN", "F811", "D101", "D102", "D103", "D104"]
|
|
@@ -53,6 +53,15 @@ class DockerLauncher(Launcher):
|
|
|
53
53
|
environment; ``settings.config`` supplies the launcher-specific
|
|
54
54
|
keyword arguments.
|
|
55
55
|
|
|
56
|
+
Args:
|
|
57
|
+
settings: The launcher settings block (image, network, volumes).
|
|
58
|
+
postgres: Postgres credentials forwarded into the spawned container.
|
|
59
|
+
runner: Runner settings the spawned process builds its runner from.
|
|
60
|
+
catalog: Catalog forwarded as import paths; required, since a container
|
|
61
|
+
cannot see the caller's.
|
|
62
|
+
store: Accepted for the registry's uniform hook and unused — a
|
|
63
|
+
container opens its own connection, not the caller's.
|
|
64
|
+
|
|
56
65
|
Returns:
|
|
57
66
|
The configured launcher.
|
|
58
67
|
|
|
@@ -165,12 +174,12 @@ class DockerLauncher(Launcher):
|
|
|
165
174
|
container_name = f"interloper_run_{str(run_id)[:8]}"
|
|
166
175
|
try:
|
|
167
176
|
container = self._client.containers.get(container_name)
|
|
168
|
-
except Exception:
|
|
177
|
+
except Exception: # noqa: BLE001 — any client error means the run is no longer observable
|
|
169
178
|
return RunState(status=RunStatus.NOT_FOUND)
|
|
170
179
|
|
|
171
180
|
try:
|
|
172
181
|
container.reload()
|
|
173
|
-
except Exception:
|
|
182
|
+
except Exception: # noqa: BLE001 — any client error means the run is no longer observable
|
|
174
183
|
return RunState(status=RunStatus.NOT_FOUND)
|
|
175
184
|
|
|
176
185
|
state = container.attrs.get("State", {}) if container.attrs else {}
|
|
@@ -197,7 +206,12 @@ class DockerLauncher(Launcher):
|
|
|
197
206
|
return RunState(status=RunStatus.FAILED, error=" ".join(parts))
|
|
198
207
|
|
|
199
208
|
def _build_environment(self) -> dict[str, str]:
|
|
200
|
-
"""Build environment variables for the container.
|
|
209
|
+
"""Build environment variables for the container.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The environment the run container is started with.
|
|
213
|
+
|
|
214
|
+
"""
|
|
201
215
|
environment: dict[str, str] = {
|
|
202
216
|
"INTERLOPER_POSTGRES_HOST": self._postgres_host,
|
|
203
217
|
"INTERLOPER_POSTGRES_PORT": str(self._postgres_port),
|
|
@@ -82,7 +82,12 @@ class DockerRunner(SyncRunner):
|
|
|
82
82
|
_container_map: dict[Future[Any], Container] = PrivateAttr(default_factory=dict)
|
|
83
83
|
|
|
84
84
|
def model_post_init(self, context: Any) -> None:
|
|
85
|
-
"""Initialize Docker client after model initialization.
|
|
85
|
+
"""Initialize Docker client after model initialization.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
context: Pydantic's post-init context, unused.
|
|
89
|
+
|
|
90
|
+
"""
|
|
86
91
|
super().model_post_init(context)
|
|
87
92
|
self._docker = docker.from_env()
|
|
88
93
|
|
|
@@ -90,7 +95,12 @@ class DockerRunner(SyncRunner):
|
|
|
90
95
|
|
|
91
96
|
@property
|
|
92
97
|
def _capacity(self) -> int:
|
|
93
|
-
"""Maximum number of concurrent containers.
|
|
98
|
+
"""Maximum number of concurrent containers.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The configured worker count.
|
|
102
|
+
|
|
103
|
+
"""
|
|
94
104
|
return self.max_containers
|
|
95
105
|
|
|
96
106
|
def _on_start(self) -> None:
|
|
@@ -116,8 +126,16 @@ class DockerRunner(SyncRunner):
|
|
|
116
126
|
) -> Future[Any]:
|
|
117
127
|
"""Launch an asset in a Docker container and return a polling Future.
|
|
118
128
|
|
|
129
|
+
Args:
|
|
130
|
+
operation: The operation to run in its own container.
|
|
131
|
+
partition_or_window: The partition or window to materialize, when scoped.
|
|
132
|
+
|
|
119
133
|
Returns:
|
|
120
134
|
A Future that raises :class:`RunnerError` on container failure.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
RunnerError: If the container cannot be created.
|
|
138
|
+
|
|
121
139
|
"""
|
|
122
140
|
if self._poll_pool is None:
|
|
123
141
|
raise RunnerError("Poll pool not initialized")
|
|
@@ -146,7 +164,7 @@ class DockerRunner(SyncRunner):
|
|
|
146
164
|
stdout=True,
|
|
147
165
|
stderr=True,
|
|
148
166
|
)
|
|
149
|
-
except Exception as e:
|
|
167
|
+
except Exception as e: # noqa: BLE001 — one operation's failure must not abort the run
|
|
150
168
|
# Container never started — emit from the host
|
|
151
169
|
self.state.mark_failed(operation, format_exception(e))
|
|
152
170
|
done: Future[None] = Future()
|
|
@@ -170,6 +188,11 @@ class DockerRunner(SyncRunner):
|
|
|
170
188
|
reporting one, the host's event is the only terminal, so the asset no
|
|
171
189
|
longer orphans as ``running``. Assets already terminal (e.g. marked
|
|
172
190
|
failed during ``_submit_operation``) are skipped.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
future: The polling future that has completed.
|
|
194
|
+
operation: The operation the container ran.
|
|
195
|
+
|
|
173
196
|
"""
|
|
174
197
|
container = self._container_map.pop(future, None)
|
|
175
198
|
if container is not None:
|
|
@@ -179,7 +202,7 @@ class DockerRunner(SyncRunner):
|
|
|
179
202
|
if not (info and info.is_terminal):
|
|
180
203
|
try:
|
|
181
204
|
future.result()
|
|
182
|
-
except Exception as e:
|
|
205
|
+
except Exception as e: # noqa: BLE001 — one operation's failure must not abort the run
|
|
183
206
|
self.state.mark_failed(operation, format_exception(e), emit=True, exception=e)
|
|
184
207
|
else:
|
|
185
208
|
self.state.mark_completed(operation, emit=True)
|
|
@@ -196,6 +219,11 @@ class DockerRunner(SyncRunner):
|
|
|
196
219
|
Same host-authored-terminal contract as :meth:`_handle_completed`
|
|
197
220
|
(``emit=True``, idempotent via deterministic ids) so an asset still
|
|
198
221
|
in flight when the run aborts is not left orphaned as ``running``.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
future: The polling future being flushed.
|
|
225
|
+
operation: The operation the container ran.
|
|
226
|
+
|
|
199
227
|
"""
|
|
200
228
|
container = self._container_map.pop(future, None)
|
|
201
229
|
if container is not None:
|
|
@@ -221,6 +249,9 @@ class DockerRunner(SyncRunner):
|
|
|
221
249
|
def _poll_container(self, container: Container) -> None:
|
|
222
250
|
"""Block until the container exits; raise on failure.
|
|
223
251
|
|
|
252
|
+
Args:
|
|
253
|
+
container: The container to wait on.
|
|
254
|
+
|
|
224
255
|
Raises:
|
|
225
256
|
RunnerError: If the container exits with a non-zero code.
|
|
226
257
|
"""
|
|
@@ -238,7 +269,17 @@ class DockerRunner(SyncRunner):
|
|
|
238
269
|
partition_or_window: Partition | PartitionWindow | None,
|
|
239
270
|
run_id: str,
|
|
240
271
|
) -> list[str]:
|
|
241
|
-
"""Build the CLI command for asset execution in a container.
|
|
272
|
+
"""Build the CLI command for asset execution in a container.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
dag_spec: The serialized DAG the container reconstructs.
|
|
276
|
+
partition_or_window: The partition or window to materialize, when scoped.
|
|
277
|
+
run_id: The run the container reports against.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
The argv the container entrypoint is started with.
|
|
281
|
+
|
|
282
|
+
"""
|
|
242
283
|
cmd = [
|
|
243
284
|
"interloper",
|
|
244
285
|
"run",
|
|
@@ -263,14 +304,24 @@ class DockerRunner(SyncRunner):
|
|
|
263
304
|
return cmd
|
|
264
305
|
|
|
265
306
|
def _build_env(self) -> dict[str, str]:
|
|
266
|
-
"""Build the environment variables for the container.
|
|
307
|
+
"""Build the environment variables for the container.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
The environment each operation container receives.
|
|
311
|
+
|
|
312
|
+
"""
|
|
267
313
|
env = dict(self.env_vars)
|
|
268
314
|
env["INTERLOPER_EVENTS_TO_STDERR"] = "true"
|
|
269
315
|
env.update(child_process_env())
|
|
270
316
|
return env
|
|
271
317
|
|
|
272
318
|
def _build_volumes(self) -> dict[str, dict[str, str]]:
|
|
273
|
-
"""Build the volume mounts for the container.
|
|
319
|
+
"""Build the volume mounts for the container.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
The mounts keyed by host path, in docker-py's shape.
|
|
323
|
+
|
|
324
|
+
"""
|
|
274
325
|
volumes: dict[str, dict[str, str]] = {}
|
|
275
326
|
if isinstance(self.volumes, dict):
|
|
276
327
|
volumes.update(self.volumes)
|
|
@@ -281,7 +332,15 @@ class DockerRunner(SyncRunner):
|
|
|
281
332
|
return volumes
|
|
282
333
|
|
|
283
334
|
def _build_name(self, operation: Operation) -> str:
|
|
284
|
-
"""Build the name for the container.
|
|
335
|
+
"""Build the name for the container.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
operation: The operation the container runs.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
A container name unique to this operation and run.
|
|
342
|
+
|
|
343
|
+
"""
|
|
285
344
|
return f"interloper_run_{self.state.run_id[:8]}_{operation.id[:8]}"
|
|
286
345
|
|
|
287
346
|
# -- Real-time event streaming (stderr) ------------------------------------
|
|
@@ -336,7 +395,12 @@ class DockerRunner(SyncRunner):
|
|
|
336
395
|
self._log_threads[container.id] = thread
|
|
337
396
|
|
|
338
397
|
def _stop_container_log_streaming(self, container: Container) -> None:
|
|
339
|
-
"""Stop and clean up the log streaming thread for a container.
|
|
398
|
+
"""Stop and clean up the log streaming thread for a container.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
container: The container whose log thread is stopped.
|
|
402
|
+
|
|
403
|
+
"""
|
|
340
404
|
if container.id is None:
|
|
341
405
|
return
|
|
342
406
|
thread = self._log_threads.pop(container.id, None)
|
|
File without changes
|
|
File without changes
|