haiway 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.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.
haiway/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from haiway.context import (
2
- ContextPresets,
2
+ ContextPreset,
3
3
  Disposable,
4
4
  Disposables,
5
5
  MissingContext,
@@ -25,7 +25,6 @@ from haiway.helpers import (
25
25
  stream_concurrently,
26
26
  throttle,
27
27
  timeout,
28
- traced,
29
28
  )
30
29
  from haiway.state import AttributePath, AttributeRequirement, Immutable, State
31
30
  from haiway.types import (
@@ -65,7 +64,7 @@ __all__ = (
65
64
  "AsyncStream",
66
65
  "AttributePath",
67
66
  "AttributeRequirement",
68
- "ContextPresets",
67
+ "ContextPreset",
69
68
  "Default",
70
69
  "DefaultValue",
71
70
  "Disposable",
@@ -112,7 +111,6 @@ __all__ = (
112
111
  "stream_concurrently",
113
112
  "throttle",
114
113
  "timeout",
115
- "traced",
116
114
  "unwrap_missing",
117
115
  "without_missing",
118
116
  )
@@ -14,13 +14,13 @@ from haiway.context.observability import (
14
14
  ObservabilityScopeExiting,
15
15
  ObservabilityTraceIdentifying,
16
16
  )
17
- from haiway.context.presets import ContextPresets
17
+ from haiway.context.presets import ContextPreset
18
18
  from haiway.context.state import StateContext
19
19
  from haiway.context.types import MissingContext, MissingState
20
20
  from haiway.state import Immutable
21
21
 
22
22
  __all__ = (
23
- "ContextPresets",
23
+ "ContextPreset",
24
24
  "Disposable",
25
25
  "Disposables",
26
26
  "Immutable",
haiway/context/access.py CHANGED
@@ -13,6 +13,7 @@ from collections.abc import (
13
13
  Iterable,
14
14
  Mapping,
15
15
  )
16
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
16
17
  from logging import Logger
17
18
  from types import TracebackType
18
19
  from typing import Any, final, overload
@@ -25,38 +26,28 @@ from haiway.context.observability import (
25
26
  ObservabilityContext,
26
27
  ObservabilityLevel,
27
28
  )
29
+
30
+ # Import after other imports to avoid circular dependencies
28
31
  from haiway.context.presets import (
29
- ContextPresets,
30
- ContextPresetsRegistry,
31
- ContextPresetsRegistryContext,
32
+ ContextPreset,
33
+ ContextPresetRegistryContext,
32
34
  )
33
35
  from haiway.context.state import ScopeState, StateContext
34
36
  from haiway.context.tasks import TaskGroupContext
35
37
  from haiway.state import Immutable, State
38
+ from haiway.utils.collections import as_list
36
39
  from haiway.utils.stream import AsyncStream
37
40
 
38
41
  __all__ = ("ctx",)
39
42
 
40
43
 
41
44
  class ScopeContext(Immutable):
42
- """
43
- Context manager for executing code within a defined scope.
44
-
45
- ScopeContext manages scope-related data and behavior including identity, state,
46
- observability, and task coordination. It enforces immutability and provides both
47
- synchronous and asynchronous context management interfaces.
48
-
49
- This class should not be instantiated directly; use the ctx.scope() factory method
50
- to create scope contexts.
51
- """
52
-
53
45
  _identifier: ScopeIdentifier
54
46
  _state: Collection[State]
55
- _captured_state: Collection[State]
56
- _resolved_state_context: StateContext | None
47
+ _state_context: StateContext | None
57
48
  _disposables: Disposables | None
58
- _presets: ContextPresets | None
59
- _presets_disposables: Disposables | None
49
+ _preset: ContextPreset | None
50
+ _preset_disposables: Disposables | None
60
51
  _observability_context: ObservabilityContext
61
52
  _task_group_context: TaskGroupContext | None
62
53
 
@@ -65,6 +56,7 @@ class ScopeContext(Immutable):
65
56
  name: str,
66
57
  task_group: TaskGroup | None,
67
58
  state: tuple[State, ...],
59
+ preset: ContextPreset | None,
68
60
  disposables: Disposables | None,
69
61
  observability: Observability | Logger | None,
70
62
  ) -> None:
@@ -79,16 +71,10 @@ class ScopeContext(Immutable):
79
71
  "_state",
80
72
  state,
81
73
  )
82
- # capture current contextual state (without new additions)
83
- object.__setattr__(
84
- self,
85
- "_captured_state",
86
- StateContext.current_state(),
87
- )
88
74
  # placeholder for temporary, resolved state context
89
75
  object.__setattr__(
90
76
  self,
91
- "_resolved_state_context",
77
+ "_state_context",
92
78
  None,
93
79
  )
94
80
  object.__setattr__(
@@ -98,12 +84,12 @@ class ScopeContext(Immutable):
98
84
  )
99
85
  object.__setattr__(
100
86
  self,
101
- "_presets",
102
- ContextPresetsRegistryContext.select(name),
87
+ "_preset",
88
+ preset if preset is not None else ContextPresetRegistryContext.select(name),
103
89
  )
104
90
  object.__setattr__(
105
91
  self,
106
- "_presets_disposables",
92
+ "_preset_disposables",
107
93
  None,
108
94
  )
109
95
  object.__setattr__(
@@ -123,80 +109,28 @@ class ScopeContext(Immutable):
123
109
  else None,
124
110
  )
125
111
 
126
- def __enter__(self) -> str:
127
- assert ( # nosec: B101
128
- self._task_group_context is None or self._identifier.is_root
129
- ), "Can't enter synchronous context with task group"
130
- assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
131
- assert self._resolved_state_context is None # nosec: B101
132
- assert self._presets is None, "Can't enter synchronous context with presets" # nosec: B101
133
- self._identifier.__enter__()
134
- self._observability_context.__enter__()
135
- # For sync context, only use explicit state (no presets or disposables allowed)
136
- resolved_state_context: StateContext = StateContext(
137
- _state=ScopeState((*self._captured_state, *self._state))
138
- )
139
- object.__setattr__(
140
- self,
141
- "_resolved_state_context",
142
- resolved_state_context,
143
- )
144
- resolved_state_context.__enter__()
145
-
146
- return str(self._observability_context.observability.trace_identifying(self._identifier))
147
-
148
- def __exit__(
149
- self,
150
- exc_type: type[BaseException] | None,
151
- exc_val: BaseException | None,
152
- exc_tb: TracebackType | None,
153
- ) -> None:
154
- assert self._resolved_state_context is not None # nosec: B101
155
- self._resolved_state_context.__exit__(
156
- exc_type=exc_type,
157
- exc_val=exc_val,
158
- exc_tb=exc_tb,
159
- )
160
- object.__setattr__(
161
- self,
162
- "_resolved_state_context",
163
- None,
164
- )
165
- self._observability_context.__exit__(
166
- exc_type=exc_type,
167
- exc_val=exc_val,
168
- exc_tb=exc_tb,
169
- )
170
- self._identifier.__exit__(
171
- exc_type=exc_type,
172
- exc_val=exc_val,
173
- exc_tb=exc_tb,
174
- )
175
-
176
112
  async def __aenter__(self) -> str:
177
- assert self._presets_disposables is None # nosec: B101
178
- assert self._resolved_state_context is None # nosec: B101
113
+ assert self._preset_disposables is None # nosec: B101
114
+ assert self._state_context is None # nosec: B101
179
115
  self._identifier.__enter__()
180
116
  self._observability_context.__enter__()
181
117
 
182
- if task_group := self._task_group_context:
183
- await task_group.__aenter__()
118
+ if self._task_group_context is not None:
119
+ await self._task_group_context.__aenter__()
184
120
 
185
121
  # Collect all state sources in priority order (lowest to highest priority)
186
- collected_state: list[State] = []
187
-
188
122
  # 1. Add contextual state first (lowest priority)
189
- collected_state.extend(self._captured_state)
123
+ collected_state: list[State] = as_list(StateContext.current_state())
190
124
 
191
125
  # 2. Add preset state (low priority, overrides contextual)
192
- if self._presets is not None:
193
- presets_disposables: Disposables = await self._presets.prepare()
126
+ if self._preset is not None:
127
+ preset_disposables: Disposables = await self._preset.prepare()
194
128
  object.__setattr__(
195
129
  self,
196
- "_presets_disposables",
197
- presets_disposables,
130
+ "_preset_disposables",
131
+ preset_disposables,
198
132
  )
199
- collected_state.extend(await presets_disposables.prepare())
133
+ collected_state.extend(await preset_disposables.prepare())
200
134
 
201
135
  # 3. Add explicit disposables state (medium priority)
202
136
  if self._disposables is not None:
@@ -212,7 +146,7 @@ class ScopeContext(Immutable):
212
146
  resolved_state_context.__enter__()
213
147
  object.__setattr__(
214
148
  self,
215
- "_resolved_state_context",
149
+ "_state_context",
216
150
  resolved_state_context,
217
151
  )
218
152
 
@@ -224,7 +158,7 @@ class ScopeContext(Immutable):
224
158
  exc_val: BaseException | None,
225
159
  exc_tb: TracebackType | None,
226
160
  ) -> None:
227
- assert self._resolved_state_context is not None # nosec: B101
161
+ assert self._state_context is not None # nosec: B101
228
162
  if self._disposables is not None:
229
163
  await self._disposables.dispose(
230
164
  exc_type=exc_type,
@@ -232,42 +166,40 @@ class ScopeContext(Immutable):
232
166
  exc_tb=exc_tb,
233
167
  )
234
168
 
235
- if self._presets_disposables is not None:
236
- await self._presets_disposables.dispose(
169
+ if self._preset_disposables is not None:
170
+ await self._preset_disposables.dispose(
237
171
  exc_type=exc_type,
238
172
  exc_val=exc_val,
239
173
  exc_tb=exc_tb,
240
174
  )
241
175
  object.__setattr__(
242
176
  self,
243
- "_presets_disposables",
177
+ "_preset_disposables",
244
178
  None,
245
179
  )
246
180
 
247
- if task_group := self._task_group_context:
248
- await task_group.__aexit__(
181
+ if self._task_group_context is not None:
182
+ await self._task_group_context.__aexit__(
249
183
  exc_type=exc_type,
250
184
  exc_val=exc_val,
251
185
  exc_tb=exc_tb,
252
186
  )
253
187
 
254
- self._resolved_state_context.__exit__(
188
+ self._state_context.__exit__(
255
189
  exc_type=exc_type,
256
190
  exc_val=exc_val,
257
191
  exc_tb=exc_tb,
258
192
  )
259
193
  object.__setattr__(
260
194
  self,
261
- "_resolved_state_context",
195
+ "_state_context",
262
196
  None,
263
197
  )
264
-
265
198
  self._observability_context.__exit__(
266
199
  exc_type=exc_type,
267
200
  exc_val=exc_val,
268
201
  exc_tb=exc_tb,
269
202
  )
270
-
271
203
  self._identifier.__exit__(
272
204
  exc_type=exc_type,
273
205
  exc_val=exc_val,
@@ -275,6 +207,59 @@ class ScopeContext(Immutable):
275
207
  )
276
208
 
277
209
 
210
+ class DisposablesContext(Immutable):
211
+ _disposables: Disposables
212
+ _state_context: StateContext | None
213
+
214
+ def __init__(
215
+ self,
216
+ disposables: Disposables,
217
+ ) -> None:
218
+ object.__setattr__(
219
+ self,
220
+ "_disposables",
221
+ disposables,
222
+ )
223
+ object.__setattr__(
224
+ self,
225
+ "_state_context",
226
+ None,
227
+ )
228
+
229
+ async def __aenter__(self) -> None:
230
+ assert self._state_context is None # nosec: B101
231
+ state_context: StateContext = StateContext.updated(await self._disposables.prepare())
232
+ state_context.__enter__()
233
+ object.__setattr__(
234
+ self,
235
+ "_state_context",
236
+ state_context,
237
+ )
238
+
239
+ async def __aexit__(
240
+ self,
241
+ exc_type: type[BaseException] | None,
242
+ exc_val: BaseException | None,
243
+ exc_tb: TracebackType | None,
244
+ ) -> None:
245
+ assert self._state_context is not None # nosec: B101
246
+ await self._disposables.dispose(
247
+ exc_type=exc_type,
248
+ exc_val=exc_val,
249
+ exc_tb=exc_tb,
250
+ )
251
+ self._state_context.__exit__(
252
+ exc_type=exc_type,
253
+ exc_val=exc_val,
254
+ exc_tb=exc_tb,
255
+ )
256
+ object.__setattr__(
257
+ self,
258
+ "_state_context",
259
+ None,
260
+ )
261
+
262
+
278
263
  @final
279
264
  class ctx:
280
265
  """
@@ -318,8 +303,8 @@ class ctx:
318
303
 
319
304
  @staticmethod
320
305
  def presets(
321
- *presets: ContextPresets,
322
- ) -> ContextPresetsRegistryContext:
306
+ *presets: ContextPreset,
307
+ ) -> AbstractContextManager[None]:
323
308
  """
324
309
  Create a context manager for a preset registry.
325
310
 
@@ -331,15 +316,19 @@ class ctx:
331
316
  for use with ctx.scope(). The presets are looked up by their name when
332
317
  creating scopes.
333
318
 
319
+ Note: For single preset usage, consider passing the preset directly to
320
+ ctx.scope() using the preset parameter instead of using this registry.
321
+ Presets only work with async contexts.
322
+
334
323
  Parameters
335
324
  ----------
336
- *presets: ContextPresets
325
+ *presets: ContextPreset
337
326
  Variable number of preset configurations to register. Each preset
338
327
  must have a unique name within the registry.
339
328
 
340
329
  Returns
341
330
  -------
342
- ContextPresetsRegistryContext
331
+ AbstractContextManager[None]
343
332
  A context manager that makes the presets available in nested scopes
344
333
 
345
334
  Examples
@@ -347,19 +336,19 @@ class ctx:
347
336
  Basic preset usage:
348
337
 
349
338
  >>> from haiway import ctx, State
350
- >>> from haiway.context import ContextPresets
339
+ >>> from haiway.context import ContextPreset
351
340
  >>>
352
341
  >>> class ApiConfig(State):
353
342
  ... base_url: str
354
343
  ... timeout: int = 30
355
344
  >>>
356
345
  >>> # Define presets
357
- >>> dev_preset = ContextPresets(
346
+ >>> dev_preset = ContextPreset(
358
347
  ... name="development",
359
348
  ... _state=[ApiConfig(base_url="https://dev-api.example.com")]
360
349
  ... )
361
350
  >>>
362
- >>> prod_preset = ContextPresets(
351
+ >>> prod_preset = ContextPreset(
363
352
  ... name="production",
364
353
  ... _state=[ApiConfig(base_url="https://api.example.com", timeout=60)]
365
354
  ... )
@@ -373,7 +362,7 @@ class ctx:
373
362
  Nested preset registries:
374
363
 
375
364
  >>> base_presets = [dev_preset, prod_preset]
376
- >>> override_preset = ContextPresets(
365
+ >>> override_preset = ContextPreset(
377
366
  ... name="development",
378
367
  ... _state=[ApiConfig(base_url="https://staging.example.com")]
379
368
  ... )
@@ -388,27 +377,29 @@ class ctx:
388
377
 
389
378
  See Also
390
379
  --------
391
- ContextPresets : For creating individual preset configurations
380
+ ContextPreset : For creating individual preset configurations
392
381
  ctx.scope : For creating scopes that can use presets
393
382
  """
394
- return ContextPresetsRegistryContext(
395
- registry=ContextPresetsRegistry(
396
- presets=presets,
397
- ),
398
- )
383
+ return ContextPresetRegistryContext(presets=presets)
399
384
 
400
385
  @staticmethod
401
386
  def scope(
402
387
  name: str,
403
388
  /,
404
389
  *state: State | None,
390
+ preset: ContextPreset | None = None,
405
391
  disposables: Disposables | Iterable[Disposable] | None = None,
406
392
  task_group: TaskGroup | None = None,
407
393
  observability: Observability | Logger | None = None,
408
- ) -> ScopeContext:
394
+ ) -> AbstractAsyncContextManager[str]:
409
395
  """
410
- Prepare scope context with given parameters. When called within an existing context\
411
- it becomes nested with current context as its parent.
396
+ Prepare scope context with given parameters.
397
+
398
+ When called within an existing context, it becomes nested with current context
399
+ as its parent.
400
+
401
+ Note: Presets can only be used with async contexts. Synchronous contexts
402
+ do not support preset functionality.
412
403
 
413
404
  State Priority System
414
405
  ---------------------
@@ -428,28 +419,71 @@ class ctx:
428
419
  name of the scope context, can be associated with state presets
429
420
 
430
421
  *state: State | None
431
- state propagated within the scope context, will be merged with current state by\
432
- replacing current with provided on conflict.
422
+ state propagated within the scope context, will be merged with current state by
423
+ replacing current with provided on conflict.
424
+
425
+ preset: ContextPreset | None = None
426
+ context preset to be used within the scope context. The preset's state and
427
+ disposables will be applied to the scope with lower priority than explicit state.
428
+ Only works with async contexts.
433
429
 
434
430
  disposables: Disposables | Iterable[Disposable] | None
435
- disposables consumed within the context when entered. Produced state will automatically\
436
- be added to the scope state. Using asynchronous context is required if any disposables\
437
- were provided.
431
+ disposables consumed within the context when entered. Produced state will automatically
432
+ be added to the scope state. Using asynchronous context is required if any disposables
433
+ were provided.
438
434
 
439
435
  task_group: TaskGroup | None
440
436
  task group used for spawning and joining tasks within the context. Root scope will
441
- always have task group created even when not set.
437
+ always have task group created even when not set.
442
438
 
443
439
  observability: Observability | Logger | None = None
444
- observability solution responsible for recording and storing metrics, logs and events.\
445
- Assigning observability within existing context will result in an error.
440
+ observability solution responsible for recording and storing metrics, logs and events.
441
+ Assigning observability within existing context will result in an error.
446
442
  When not provided, logger with the scope name will be requested and used.
447
443
 
448
444
  Returns
449
445
  -------
450
- ScopeContext
451
- context object intended to enter context manager with.\
452
- context manager will provide trace_id of current context.
446
+ AbstractAsyncContextManager[str]
447
+ context manager object intended to enter the scope with.
448
+ context manager will provide trace_id of current scope.
449
+
450
+ Examples
451
+ --------
452
+ Using a preset directly:
453
+
454
+ >>> from haiway import ctx, State
455
+ >>> from haiway.context import ContextPreset
456
+ >>>
457
+ >>> class ApiConfig(State):
458
+ ... base_url: str
459
+ ... timeout: int = 30
460
+ >>>
461
+ >>> api_preset = ContextPreset(
462
+ ... name="api",
463
+ ... state=[ApiConfig(base_url="https://api.example.com")]
464
+ ... )
465
+ >>>
466
+ >>> # Direct preset usage
467
+ >>> async with ctx.scope("main", preset=api_preset):
468
+ ... config = ctx.state(ApiConfig)
469
+ ... # Uses preset configuration
470
+ >>>
471
+ >>> # Override preset state with explicit state
472
+ >>> async with ctx.scope("main", ApiConfig(timeout=60), preset=api_preset):
473
+ ... config = ctx.state(ApiConfig)
474
+ ... # base_url from preset, timeout overridden to 60
475
+
476
+ Using preset registry (original approach):
477
+
478
+ >>> # Multiple presets registered
479
+ >>> with ctx.presets(dev_preset, prod_preset):
480
+ ... async with ctx.scope("development"): # Matches dev_preset by name
481
+ ... config = ctx.state(ApiConfig)
482
+
483
+ See Also
484
+ --------
485
+ ctx.presets : For registering multiple presets by name
486
+ ContextPreset : For creating preset configurations
453
487
  """
454
488
 
455
489
  resolved_disposables: Disposables | None
@@ -467,6 +501,7 @@ class ctx:
467
501
  name=name,
468
502
  task_group=task_group,
469
503
  state=tuple(element for element in state if element is not None),
504
+ preset=preset,
470
505
  disposables=resolved_disposables,
471
506
  observability=observability,
472
507
  )
@@ -474,21 +509,23 @@ class ctx:
474
509
  @staticmethod
475
510
  def updated(
476
511
  *state: State | None,
477
- ) -> StateContext:
512
+ ) -> AbstractContextManager[None]:
478
513
  """
479
- Update scope context with given state. When called within an existing context\
480
- it becomes nested with current context as its predecessor.
514
+ Update scope context with given state.
515
+
516
+ When called within an existing context, it becomes nested with current
517
+ context as its predecessor.
481
518
 
482
519
  Parameters
483
520
  ----------
484
521
  *state: State | None
485
- state propagated within the updated scope context, will be merged with current if any\
486
- by replacing current with provided on conflict
522
+ state propagated within the updated scope context, will be merged with current if any
523
+ by replacing current with provided on conflict
487
524
 
488
525
  Returns
489
526
  -------
490
- StateContext
491
- state part of context object intended to enter context manager with it
527
+ AbstractContextManager[None]
528
+ context manager object intended to enter updated state context with it
492
529
  """
493
530
 
494
531
  return StateContext.updated(element for element in state if element is not None)
@@ -496,7 +533,7 @@ class ctx:
496
533
  @staticmethod
497
534
  def disposables(
498
535
  *disposables: Disposable | None,
499
- ) -> Disposables:
536
+ ) -> AbstractAsyncContextManager[None]:
500
537
  """
501
538
  Create a container for managing multiple disposable resources.
502
539
 
@@ -513,9 +550,9 @@ class ctx:
513
550
 
514
551
  Returns
515
552
  -------
516
- Disposables
517
- A container that manages the lifecycle of all provided disposables
518
- and propagates their state to the context when used with ctx.scope()
553
+ AbstractAsyncContextManager[None]
554
+ A context manager that manages the lifecycle of all provided disposables
555
+ and propagates their state to the context, similar to ctx.scope()
519
556
 
520
557
  Examples
521
558
  --------
@@ -524,16 +561,19 @@ class ctx:
524
561
  >>> from haiway import ctx
525
562
  >>> async def main():
526
563
  ...
527
- ... async with ctx.scope(
528
- ... "database_work",
529
- ... disposables=(database_connection(),)
564
+ ... async with ctx.disposables(
565
+ ... database_connection(),
530
566
  ... ):
531
567
  ... # ConnectionState is now available in context
532
568
  ... conn_state = ctx.state(ConnectionState)
533
569
  ... await conn_state.connection.execute("SELECT 1")
534
570
  """
535
571
 
536
- return Disposables(*(disposable for disposable in disposables if disposable is not None))
572
+ return DisposablesContext(
573
+ disposables=Disposables(
574
+ *(disposable for disposable in disposables if disposable is not None)
575
+ )
576
+ )
537
577
 
538
578
  @staticmethod
539
579
  def spawn[Result, **Arguments](
@@ -543,8 +583,9 @@ class ctx:
543
583
  **kwargs: Arguments.kwargs,
544
584
  ) -> Task[Result]:
545
585
  """
546
- Spawn an async task within current scope context task group. When called outside of context\
547
- it will spawn detached task instead.
586
+ Spawn an async task within current scope context task group.
587
+
588
+ When called outside of context, it will spawn detached task instead.
548
589
 
549
590
  Parameters
550
591
  ----------
@@ -593,7 +634,7 @@ class ctx:
593
634
  """
594
635
 
595
636
  output_stream = AsyncStream[Element]()
596
- stream_scope: ScopeContext = ctx.scope("stream")
637
+ stream_scope: AbstractAsyncContextManager[str] = ctx.scope("stream")
597
638
 
598
639
  async def stream() -> None:
599
640
  async with stream_scope:
@@ -754,8 +795,9 @@ class ctx:
754
795
  **extra: Any,
755
796
  ) -> None:
756
797
  """
757
- Log using ERROR level within current scope context. When there is no current scope\
758
- root logger will be used without additional details.
798
+ Log using ERROR level within current scope context.
799
+
800
+ When there is no current scope, root logger will be used without additional details.
759
801
 
760
802
  Parameters
761
803
  ----------
@@ -790,8 +832,9 @@ class ctx:
790
832
  **extra: Any,
791
833
  ) -> None:
792
834
  """
793
- Log using WARNING level within current scope context. When there is no current scope\
794
- root logger will be used without additional details.
835
+ Log using WARNING level within current scope context.
836
+
837
+ When there is no current scope, root logger will be used without additional details.
795
838
 
796
839
  Parameters
797
840
  ----------
@@ -825,8 +868,9 @@ class ctx:
825
868
  **extra: Any,
826
869
  ) -> None:
827
870
  """
828
- Log using INFO level within current scope context. When there is no current scope\
829
- root logger will be used without additional details.
871
+ Log using INFO level within current scope context.
872
+
873
+ When there is no current scope, root logger will be used without additional details.
830
874
 
831
875
  Parameters
832
876
  ----------
@@ -858,8 +902,9 @@ class ctx:
858
902
  **extra: Any,
859
903
  ) -> None:
860
904
  """
861
- Log using DEBUG level within current scope context. When there is no current scope\
862
- root logger will be used without additional details.
905
+ Log using DEBUG level within current scope context.
906
+
907
+ When there is no current scope, root logger will be used without additional details.
863
908
 
864
909
  Parameters
865
910
  ----------