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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: interloper-docker
3
- Version: 0.72.0
3
+ Version: 0.74.0
4
4
  Summary: Interloper Docker integration
5
5
  Author: Guillaume Onfroy
6
6
  Author-email: Guillaume Onfroy <guillaume@digitlcloud.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interloper-docker"
3
- version = "0.72.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.72.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
- extend-select = ["E", "I", "UP", "ANN001", "ANN201", "ANN202"]
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)