fraiseql-confiture 0.3.4__cp311-cp311-win_amd64.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.
- confiture/__init__.py +48 -0
- confiture/_core.cp311-win_amd64.pyd +0 -0
- confiture/cli/__init__.py +0 -0
- confiture/cli/dry_run.py +116 -0
- confiture/cli/lint_formatter.py +193 -0
- confiture/cli/main.py +1656 -0
- confiture/config/__init__.py +0 -0
- confiture/config/environment.py +263 -0
- confiture/core/__init__.py +51 -0
- confiture/core/anonymization/__init__.py +0 -0
- confiture/core/anonymization/audit.py +485 -0
- confiture/core/anonymization/benchmarking.py +372 -0
- confiture/core/anonymization/breach_notification.py +652 -0
- confiture/core/anonymization/compliance.py +617 -0
- confiture/core/anonymization/composer.py +298 -0
- confiture/core/anonymization/data_subject_rights.py +669 -0
- confiture/core/anonymization/factory.py +319 -0
- confiture/core/anonymization/governance.py +737 -0
- confiture/core/anonymization/performance.py +1092 -0
- confiture/core/anonymization/profile.py +284 -0
- confiture/core/anonymization/registry.py +195 -0
- confiture/core/anonymization/security/kms_manager.py +547 -0
- confiture/core/anonymization/security/lineage.py +888 -0
- confiture/core/anonymization/security/token_store.py +686 -0
- confiture/core/anonymization/strategies/__init__.py +41 -0
- confiture/core/anonymization/strategies/address.py +359 -0
- confiture/core/anonymization/strategies/credit_card.py +374 -0
- confiture/core/anonymization/strategies/custom.py +161 -0
- confiture/core/anonymization/strategies/date.py +218 -0
- confiture/core/anonymization/strategies/differential_privacy.py +398 -0
- confiture/core/anonymization/strategies/email.py +141 -0
- confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
- confiture/core/anonymization/strategies/hash.py +150 -0
- confiture/core/anonymization/strategies/ip_address.py +235 -0
- confiture/core/anonymization/strategies/masking_retention.py +252 -0
- confiture/core/anonymization/strategies/name.py +298 -0
- confiture/core/anonymization/strategies/phone.py +119 -0
- confiture/core/anonymization/strategies/preserve.py +85 -0
- confiture/core/anonymization/strategies/redact.py +101 -0
- confiture/core/anonymization/strategies/salted_hashing.py +322 -0
- confiture/core/anonymization/strategies/text_redaction.py +183 -0
- confiture/core/anonymization/strategies/tokenization.py +334 -0
- confiture/core/anonymization/strategy.py +241 -0
- confiture/core/anonymization/syncer_audit.py +357 -0
- confiture/core/blue_green.py +683 -0
- confiture/core/builder.py +500 -0
- confiture/core/checksum.py +358 -0
- confiture/core/connection.py +132 -0
- confiture/core/differ.py +522 -0
- confiture/core/drift.py +564 -0
- confiture/core/dry_run.py +182 -0
- confiture/core/health.py +313 -0
- confiture/core/hooks/__init__.py +87 -0
- confiture/core/hooks/base.py +232 -0
- confiture/core/hooks/context.py +146 -0
- confiture/core/hooks/execution_strategies.py +57 -0
- confiture/core/hooks/observability.py +220 -0
- confiture/core/hooks/phases.py +53 -0
- confiture/core/hooks/registry.py +295 -0
- confiture/core/large_tables.py +775 -0
- confiture/core/linting/__init__.py +70 -0
- confiture/core/linting/composer.py +192 -0
- confiture/core/linting/libraries/__init__.py +17 -0
- confiture/core/linting/libraries/gdpr.py +168 -0
- confiture/core/linting/libraries/general.py +184 -0
- confiture/core/linting/libraries/hipaa.py +144 -0
- confiture/core/linting/libraries/pci_dss.py +104 -0
- confiture/core/linting/libraries/sox.py +120 -0
- confiture/core/linting/schema_linter.py +491 -0
- confiture/core/linting/versioning.py +151 -0
- confiture/core/locking.py +389 -0
- confiture/core/migration_generator.py +298 -0
- confiture/core/migrator.py +793 -0
- confiture/core/observability/__init__.py +44 -0
- confiture/core/observability/audit.py +323 -0
- confiture/core/observability/logging.py +187 -0
- confiture/core/observability/metrics.py +174 -0
- confiture/core/observability/tracing.py +192 -0
- confiture/core/pg_version.py +418 -0
- confiture/core/pool.py +406 -0
- confiture/core/risk/__init__.py +39 -0
- confiture/core/risk/predictor.py +188 -0
- confiture/core/risk/scoring.py +248 -0
- confiture/core/rollback_generator.py +388 -0
- confiture/core/schema_analyzer.py +769 -0
- confiture/core/schema_to_schema.py +590 -0
- confiture/core/security/__init__.py +32 -0
- confiture/core/security/logging.py +201 -0
- confiture/core/security/validation.py +416 -0
- confiture/core/signals.py +371 -0
- confiture/core/syncer.py +540 -0
- confiture/exceptions.py +192 -0
- confiture/integrations/__init__.py +0 -0
- confiture/models/__init__.py +0 -0
- confiture/models/lint.py +193 -0
- confiture/models/migration.py +180 -0
- confiture/models/schema.py +203 -0
- confiture/scenarios/__init__.py +36 -0
- confiture/scenarios/compliance.py +586 -0
- confiture/scenarios/ecommerce.py +199 -0
- confiture/scenarios/financial.py +253 -0
- confiture/scenarios/healthcare.py +315 -0
- confiture/scenarios/multi_tenant.py +340 -0
- confiture/scenarios/saas.py +295 -0
- confiture/testing/FRAMEWORK_API.md +722 -0
- confiture/testing/__init__.py +38 -0
- confiture/testing/fixtures/__init__.py +11 -0
- confiture/testing/fixtures/data_validator.py +229 -0
- confiture/testing/fixtures/migration_runner.py +167 -0
- confiture/testing/fixtures/schema_snapshotter.py +352 -0
- confiture/testing/frameworks/__init__.py +10 -0
- confiture/testing/frameworks/mutation.py +587 -0
- confiture/testing/frameworks/performance.py +479 -0
- confiture/testing/utils/__init__.py +0 -0
- fraiseql_confiture-0.3.4.dist-info/METADATA +438 -0
- fraiseql_confiture-0.3.4.dist-info/RECORD +119 -0
- fraiseql_confiture-0.3.4.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.3.4.dist-info/entry_points.txt +2 -0
- fraiseql_confiture-0.3.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""Signal handling for graceful shutdown.
|
|
2
|
+
|
|
3
|
+
Provides utilities for graceful shutdown in containerized environments,
|
|
4
|
+
particularly for Kubernetes which sends SIGTERM before killing pods.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import signal
|
|
9
|
+
import threading
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ShutdownState(Enum):
|
|
19
|
+
"""State of the shutdown handler."""
|
|
20
|
+
|
|
21
|
+
RUNNING = "running"
|
|
22
|
+
SHUTTING_DOWN = "shutting_down"
|
|
23
|
+
STOPPED = "stopped"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ShutdownContext:
|
|
28
|
+
"""Context passed to cleanup handlers."""
|
|
29
|
+
|
|
30
|
+
signal_received: str
|
|
31
|
+
current_operation: str | None = None
|
|
32
|
+
migration_in_progress: str | None = None
|
|
33
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GracefulShutdown:
|
|
37
|
+
"""Handle graceful shutdown signals.
|
|
38
|
+
|
|
39
|
+
Registers signal handlers for SIGTERM and SIGINT to enable
|
|
40
|
+
graceful shutdown in containerized environments.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> shutdown = GracefulShutdown()
|
|
44
|
+
>>> shutdown.register()
|
|
45
|
+
>>> shutdown.add_cleanup(lambda ctx: print("Cleaning up..."))
|
|
46
|
+
>>> while not shutdown.should_stop:
|
|
47
|
+
... # Do work
|
|
48
|
+
... pass
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
In Kubernetes, pods receive SIGTERM before being killed.
|
|
52
|
+
This handler allows migrations to complete the current
|
|
53
|
+
operation before exiting.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, timeout: float = 30.0):
|
|
57
|
+
"""Initialize graceful shutdown handler.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
timeout: Maximum seconds to wait for cleanup handlers
|
|
61
|
+
"""
|
|
62
|
+
self._should_stop = False
|
|
63
|
+
self._state = ShutdownState.RUNNING
|
|
64
|
+
self._cleanup_handlers: list[Callable[[ShutdownContext], None]] = []
|
|
65
|
+
self._current_operation: str | None = None
|
|
66
|
+
self._migration_in_progress: str | None = None
|
|
67
|
+
self._timeout = timeout
|
|
68
|
+
self._lock = threading.Lock()
|
|
69
|
+
self._registered = False
|
|
70
|
+
self._original_handlers: dict[int, Any] = {}
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def should_stop(self) -> bool:
|
|
74
|
+
"""Check if shutdown has been requested."""
|
|
75
|
+
return self._should_stop
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def state(self) -> ShutdownState:
|
|
79
|
+
"""Get current shutdown state."""
|
|
80
|
+
return self._state
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def is_running(self) -> bool:
|
|
84
|
+
"""Check if still in running state."""
|
|
85
|
+
return self._state == ShutdownState.RUNNING
|
|
86
|
+
|
|
87
|
+
def register(self) -> "GracefulShutdown":
|
|
88
|
+
"""Register signal handlers.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Self for chaining
|
|
92
|
+
"""
|
|
93
|
+
if self._registered:
|
|
94
|
+
logger.warning("Graceful shutdown handlers already registered")
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
# Store original handlers
|
|
98
|
+
self._original_handlers[signal.SIGTERM] = signal.signal(signal.SIGTERM, self._handle_signal)
|
|
99
|
+
self._original_handlers[signal.SIGINT] = signal.signal(signal.SIGINT, self._handle_signal)
|
|
100
|
+
|
|
101
|
+
self._registered = True
|
|
102
|
+
logger.info("Graceful shutdown handlers registered")
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def unregister(self) -> None:
|
|
106
|
+
"""Restore original signal handlers."""
|
|
107
|
+
if not self._registered:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
for sig, handler in self._original_handlers.items():
|
|
111
|
+
signal.signal(sig, handler)
|
|
112
|
+
|
|
113
|
+
self._original_handlers.clear()
|
|
114
|
+
self._registered = False
|
|
115
|
+
logger.info("Graceful shutdown handlers unregistered")
|
|
116
|
+
|
|
117
|
+
def add_cleanup(self, handler: Callable[[ShutdownContext], None], priority: int = 0) -> None:
|
|
118
|
+
"""Add cleanup handler to run on shutdown.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
handler: Callable that receives ShutdownContext
|
|
122
|
+
priority: Higher priority handlers run first (default: 0)
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> def release_lock(ctx):
|
|
126
|
+
... print(f"Releasing lock during {ctx.signal_received}")
|
|
127
|
+
>>> shutdown.add_cleanup(release_lock, priority=10)
|
|
128
|
+
"""
|
|
129
|
+
with self._lock:
|
|
130
|
+
# Insert maintaining priority order (highest first)
|
|
131
|
+
self._cleanup_handlers.append((priority, handler))
|
|
132
|
+
self._cleanup_handlers.sort(key=lambda x: -x[0])
|
|
133
|
+
|
|
134
|
+
def remove_cleanup(self, handler: Callable[[ShutdownContext], None]) -> bool:
|
|
135
|
+
"""Remove a cleanup handler.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
handler: The handler to remove
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
True if handler was found and removed
|
|
142
|
+
"""
|
|
143
|
+
with self._lock:
|
|
144
|
+
for i, (_, h) in enumerate(self._cleanup_handlers):
|
|
145
|
+
if h == handler:
|
|
146
|
+
self._cleanup_handlers.pop(i)
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def set_current_operation(self, operation: str | None) -> None:
|
|
151
|
+
"""Set the current operation being performed.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
operation: Description of current operation, or None if idle
|
|
155
|
+
"""
|
|
156
|
+
self._current_operation = operation
|
|
157
|
+
|
|
158
|
+
def set_migration_in_progress(self, migration: str | None) -> None:
|
|
159
|
+
"""Set the current migration being executed.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
migration: Migration name/version, or None if not migrating
|
|
163
|
+
"""
|
|
164
|
+
self._migration_in_progress = migration
|
|
165
|
+
|
|
166
|
+
def _handle_signal(self, signum: int, frame: Any) -> None: # noqa: ARG002
|
|
167
|
+
"""Handle shutdown signal.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
signum: Signal number
|
|
171
|
+
frame: Current stack frame
|
|
172
|
+
"""
|
|
173
|
+
sig_name = signal.Signals(signum).name
|
|
174
|
+
logger.warning(f"Received {sig_name}, initiating graceful shutdown...")
|
|
175
|
+
|
|
176
|
+
with self._lock:
|
|
177
|
+
if self._state != ShutdownState.RUNNING:
|
|
178
|
+
logger.warning(f"Already in {self._state.value} state, ignoring signal")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
self._should_stop = True
|
|
182
|
+
self._state = ShutdownState.SHUTTING_DOWN
|
|
183
|
+
|
|
184
|
+
# Create context for cleanup handlers
|
|
185
|
+
context = ShutdownContext(
|
|
186
|
+
signal_received=sig_name,
|
|
187
|
+
current_operation=self._current_operation,
|
|
188
|
+
migration_in_progress=self._migration_in_progress,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Run cleanup handlers
|
|
192
|
+
self._run_cleanup_handlers(context)
|
|
193
|
+
|
|
194
|
+
self._state = ShutdownState.STOPPED
|
|
195
|
+
logger.info("Graceful shutdown complete")
|
|
196
|
+
|
|
197
|
+
def _run_cleanup_handlers(self, context: ShutdownContext) -> None:
|
|
198
|
+
"""Run all registered cleanup handlers.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
context: Shutdown context to pass to handlers
|
|
202
|
+
"""
|
|
203
|
+
with self._lock:
|
|
204
|
+
handlers = list(self._cleanup_handlers)
|
|
205
|
+
|
|
206
|
+
for priority, handler in handlers:
|
|
207
|
+
try:
|
|
208
|
+
logger.debug(f"Running cleanup handler (priority={priority})")
|
|
209
|
+
handler(context)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Cleanup handler failed: {e}")
|
|
212
|
+
|
|
213
|
+
def request_shutdown(self) -> None:
|
|
214
|
+
"""Programmatically request shutdown.
|
|
215
|
+
|
|
216
|
+
Useful for testing or triggering shutdown from code.
|
|
217
|
+
"""
|
|
218
|
+
self._handle_signal(signal.SIGTERM, None)
|
|
219
|
+
|
|
220
|
+
def __enter__(self) -> "GracefulShutdown":
|
|
221
|
+
"""Context manager entry."""
|
|
222
|
+
return self.register()
|
|
223
|
+
|
|
224
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
225
|
+
"""Context manager exit."""
|
|
226
|
+
self.unregister()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Global instance for convenience
|
|
230
|
+
_shutdown: GracefulShutdown | None = None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def get_shutdown_handler() -> GracefulShutdown:
|
|
234
|
+
"""Get or create the global shutdown handler.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Global GracefulShutdown instance
|
|
238
|
+
"""
|
|
239
|
+
global _shutdown
|
|
240
|
+
if _shutdown is None:
|
|
241
|
+
_shutdown = GracefulShutdown()
|
|
242
|
+
return _shutdown
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def register_shutdown() -> GracefulShutdown:
|
|
246
|
+
"""Register and return global shutdown handler.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Registered GracefulShutdown instance
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> shutdown = register_shutdown()
|
|
253
|
+
>>> shutdown.add_cleanup(my_cleanup_func)
|
|
254
|
+
"""
|
|
255
|
+
return get_shutdown_handler().register()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def add_cleanup(handler: Callable[[ShutdownContext], None], priority: int = 0) -> None:
|
|
259
|
+
"""Add cleanup handler to global shutdown handler.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
handler: Cleanup function receiving ShutdownContext
|
|
263
|
+
priority: Higher priority handlers run first
|
|
264
|
+
"""
|
|
265
|
+
get_shutdown_handler().add_cleanup(handler, priority)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def should_stop() -> bool:
|
|
269
|
+
"""Check if shutdown has been requested.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
True if shutdown is in progress
|
|
273
|
+
"""
|
|
274
|
+
return get_shutdown_handler().should_stop
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def set_current_operation(operation: str | None) -> None:
|
|
278
|
+
"""Set current operation on global handler.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
operation: Description of current operation
|
|
282
|
+
"""
|
|
283
|
+
get_shutdown_handler().set_current_operation(operation)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def set_migration_in_progress(migration: str | None) -> None:
|
|
287
|
+
"""Set current migration on global handler.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
migration: Migration name/version
|
|
291
|
+
"""
|
|
292
|
+
get_shutdown_handler().set_migration_in_progress(migration)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class MigrationShutdownGuard:
|
|
296
|
+
"""Context manager for safe migration execution.
|
|
297
|
+
|
|
298
|
+
Tracks migration state and ensures clean shutdown if
|
|
299
|
+
interrupted mid-migration.
|
|
300
|
+
|
|
301
|
+
Example:
|
|
302
|
+
>>> with MigrationShutdownGuard("001_create_users", lock, connection):
|
|
303
|
+
... migration.up()
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
def __init__(
|
|
307
|
+
self,
|
|
308
|
+
migration_name: str,
|
|
309
|
+
lock: Any | None = None,
|
|
310
|
+
connection: Any | None = None,
|
|
311
|
+
):
|
|
312
|
+
"""Initialize migration guard.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
migration_name: Name of the migration being run
|
|
316
|
+
lock: Optional lock to release on shutdown
|
|
317
|
+
connection: Optional connection to rollback on shutdown
|
|
318
|
+
"""
|
|
319
|
+
self.migration_name = migration_name
|
|
320
|
+
self.lock = lock
|
|
321
|
+
self.connection = connection
|
|
322
|
+
self._shutdown = get_shutdown_handler()
|
|
323
|
+
self._cleanup_registered = False
|
|
324
|
+
|
|
325
|
+
def __enter__(self) -> "MigrationShutdownGuard":
|
|
326
|
+
"""Enter migration context."""
|
|
327
|
+
self._shutdown.set_migration_in_progress(self.migration_name)
|
|
328
|
+
|
|
329
|
+
# Register cleanup handler
|
|
330
|
+
def cleanup(ctx: ShutdownContext) -> None: # noqa: ARG001
|
|
331
|
+
logger.warning(f"Shutdown during migration {self.migration_name}, cleaning up...")
|
|
332
|
+
|
|
333
|
+
# Release lock if held
|
|
334
|
+
if self.lock is not None:
|
|
335
|
+
try:
|
|
336
|
+
if hasattr(self.lock, "release"):
|
|
337
|
+
self.lock.release()
|
|
338
|
+
elif hasattr(self.lock, "_release_lock"):
|
|
339
|
+
self.lock._release_lock()
|
|
340
|
+
logger.info("Lock released during shutdown")
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.error(f"Failed to release lock: {e}")
|
|
343
|
+
|
|
344
|
+
# Rollback connection if in transaction
|
|
345
|
+
if self.connection is not None:
|
|
346
|
+
try:
|
|
347
|
+
self.connection.rollback()
|
|
348
|
+
logger.info("Transaction rolled back during shutdown")
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(f"Failed to rollback transaction: {e}")
|
|
351
|
+
|
|
352
|
+
self._shutdown.add_cleanup(cleanup, priority=100)
|
|
353
|
+
self._cleanup_registered = True
|
|
354
|
+
|
|
355
|
+
return self
|
|
356
|
+
|
|
357
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
358
|
+
"""Exit migration context."""
|
|
359
|
+
self._shutdown.set_migration_in_progress(None)
|
|
360
|
+
|
|
361
|
+
# Note: We don't remove the cleanup handler here because
|
|
362
|
+
# if shutdown is in progress, it may still need to run.
|
|
363
|
+
# Handlers are cleared when the process exits anyway.
|
|
364
|
+
|
|
365
|
+
def check_shutdown(self) -> bool:
|
|
366
|
+
"""Check if shutdown was requested.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
True if should stop
|
|
370
|
+
"""
|
|
371
|
+
return self._shutdown.should_stop
|