krons 0.2.2__py3-none-any.whl → 0.2.3__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.
@@ -26,8 +26,6 @@ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
26
26
  "Edge": ("krons.core.base.graph", "Edge"),
27
27
  "EdgeCondition": ("krons.core.base.graph", "EdgeCondition"),
28
28
  "Graph": ("krons.core.base.graph", "Graph"),
29
- # log
30
- "DataLoggerConfig": ("krons.core.base.log", "DataLoggerConfig"),
31
29
  # node
32
30
  "NODE_REGISTRY": ("krons.core.base.node", "NODE_REGISTRY"),
33
31
  "PERSISTABLE_NODE_REGISTRY": ("krons.core.base.node", "PERSISTABLE_NODE_REGISTRY"),
@@ -79,7 +77,6 @@ if TYPE_CHECKING:
79
77
  from .eventbus import EventBus, Handler
80
78
  from .flow import Flow
81
79
  from .graph import Edge, EdgeCondition, Graph
82
- from .log import DataLoggerConfig
83
80
  from .node import (
84
81
  NODE_REGISTRY,
85
82
  PERSISTABLE_NODE_REGISTRY,
@@ -100,7 +97,6 @@ __all__ = [
100
97
  "PERSISTABLE_NODE_REGISTRY",
101
98
  # classes
102
99
  "Broadcaster",
103
- "DataLoggerConfig",
104
100
  "Edge",
105
101
  "EdgeCondition",
106
102
  "Element",
krons/core/base/flow.py CHANGED
@@ -321,6 +321,13 @@ class Flow(Element, Generic[E, P]):
321
321
 
322
322
  return self.items.remove(uid)
323
323
 
324
+ @synchronized
325
+ def clear(self) -> None:
326
+ """Clear all items and progressions."""
327
+ self.items.clear()
328
+ self.progressions.clear()
329
+ self._progression_names.clear()
330
+
324
331
  def __repr__(self) -> str:
325
332
  name_str = f", name='{self.name}'" if self.name else ""
326
333
  return f"Flow(items={len(self.items)}, progressions={len(self.progressions)}{name_str})"
krons/session/session.py CHANGED
@@ -14,11 +14,9 @@ import contextlib
14
14
  from collections.abc import AsyncGenerator, Iterable
15
15
  from pathlib import Path
16
16
  from typing import Any, Literal
17
-
18
- from krons.core.base.log import DataLoggerConfig
19
17
  from uuid import UUID
20
18
 
21
- from pydantic import Field, PrivateAttr, model_validator
19
+ from pydantic import Field, PrivateAttr, field_serializer, model_validator
22
20
 
23
21
  from krons.core import Element, Flow, Pile, Progression
24
22
  from krons.core.types import HashableModel, Unset, UnsetType, not_sentinel
@@ -63,25 +61,41 @@ class SessionConfig(HashableModel):
63
61
  default_gen_model: str | None = None
64
62
  default_parse_model: str | None = None
65
63
  auto_create_default_branch: bool = True
66
- log_config: DataLoggerConfig | None = Field(
64
+
65
+ # Logging configuration
66
+ log_persist_dir: str | Path | None = Field(
67
67
  default=None,
68
- description="DataLoggerConfig for auto-logging (None = disabled)",
68
+ description="Directory for session dumps. None disables logging.",
69
+ )
70
+ log_auto_save_on_exit: bool = Field(
71
+ default=True,
72
+ description="Register atexit handler on Session creation.",
69
73
  )
70
74
 
75
+ @property
76
+ def logging_enabled(self) -> bool:
77
+ """True if logging is configured (log_persist_dir is set)."""
78
+ return self.log_persist_dir is not None
79
+
71
80
 
72
81
  class Session(Element):
73
82
  user: str | None = None
74
83
  communications: Flow[Message, Branch] = Field(
75
84
  default_factory=lambda: Flow(item_type=Message)
76
85
  )
77
- resources: ResourceRegistry = Field(default_factory=ResourceRegistry)
78
- operations: OperationRegistry = Field(default_factory=OperationRegistry)
86
+ resources: ResourceRegistry = Field(default_factory=ResourceRegistry, exclude=True)
87
+ operations: OperationRegistry = Field(default_factory=OperationRegistry, exclude=True)
79
88
  config: SessionConfig = Field(default_factory=SessionConfig)
80
89
  default_branch_id: UUID | None = None
81
90
 
82
91
  _registered_atexit: bool = PrivateAttr(default=False)
83
92
  _dump_count: int = PrivateAttr(default=0)
84
93
 
94
+ @field_serializer("communications")
95
+ def _serialize_communications(self, flow: Flow) -> dict:
96
+ """Use Flow's custom to_dict for proper nested serialization."""
97
+ return flow.to_dict(mode="json")
98
+
85
99
  @model_validator(mode="after")
86
100
  def _validate_default_branch(self) -> Session:
87
101
  """Auto-create default branch and register built-in operations."""
@@ -96,8 +110,8 @@ class Session(Element):
96
110
 
97
111
  # Register atexit handler if configured
98
112
  if (
99
- self.config.log_config is not None
100
- and self.config.log_config.auto_save_on_exit
113
+ self.config.logging_enabled
114
+ and self.config.log_auto_save_on_exit
101
115
  and not self._registered_atexit
102
116
  ):
103
117
  atexit.register(self._save_at_exit)
@@ -406,96 +420,87 @@ class Session(Element):
406
420
  async for result in handler(params, ctx):
407
421
  yield result
408
422
 
409
- def dump_messages(self, clear: bool = False) -> Path | None:
410
- """Sync dump all session messages to file.
423
+ def dump(self, clear: bool = False) -> Path | None:
424
+ """Sync dump entire session state for replay.
425
+
426
+ Serializes session (messages, branches, config) to JSON.
427
+ Resources and operations are excluded (re-register on restore).
428
+ To restore: Session.from_dict(data), then re-register resources.
411
429
 
412
430
  Args:
413
- clear: Clear messages after dump (default False).
431
+ clear: Clear communications after dump (default False).
414
432
 
415
433
  Returns:
416
- Path to written file, or None if no log_config or no messages.
434
+ Path to session file, or None if logging disabled or empty.
417
435
  """
418
- from krons.utils import create_path, json_dumpb, json_lines_iter
436
+ from krons.utils import create_path, json_dumpb
419
437
  from krons.utils.concurrency import run_async
420
438
 
421
- if self.config.log_config is None or len(self.messages) == 0:
439
+ if not self.config.logging_enabled or len(self.messages) == 0:
422
440
  return None
423
441
 
424
- cfg = self.config.log_config
425
442
  self._dump_count += 1
426
443
 
427
444
  filepath = run_async(
428
445
  create_path(
429
- directory=cfg.persist_dir,
446
+ directory=self.config.log_persist_dir,
430
447
  filename=f"session_{str(self.id)[:8]}_{self._dump_count}",
431
- extension=cfg.extension,
448
+ extension=".json",
432
449
  timestamp=True,
433
450
  file_exist_ok=True,
434
451
  )
435
452
  )
436
453
 
437
- items = [msg.to_dict(mode="json") for msg in self.messages]
438
-
454
+ data = json_dumpb(self.to_dict(mode="json"), safe_fallback=True)
439
455
  std_path = Path(filepath)
440
- if cfg.extension == ".jsonl":
441
- with std_path.open("wb") as f:
442
- for chunk in json_lines_iter(items, safe_fallback=True):
443
- f.write(chunk)
444
- else:
445
- data = json_dumpb(items, safe_fallback=True)
446
- std_path.write_bytes(data)
456
+ std_path.write_bytes(data)
447
457
 
448
458
  if clear:
449
- self.communications.items.clear()
459
+ self.communications.clear()
450
460
 
451
461
  return std_path
452
462
 
453
- async def adump_messages(self, clear: bool = False) -> Path | None:
454
- """Async dump all session messages to file with lock protection.
463
+ async def adump(self, clear: bool = False) -> Path | None:
464
+ """Async dump entire session state for replay.
465
+
466
+ Serializes the full session (messages, branches, config) to JSON.
467
+ To restore: Session.from_dict(data), then re-register resources.
455
468
 
456
469
  Args:
457
- clear: Clear messages after dump (default False).
470
+ clear: Clear communications after dump (default False).
458
471
 
459
472
  Returns:
460
- Path to written file, or None if no log_config or no messages.
473
+ Path to session file, or None if logging disabled or empty.
461
474
  """
462
- from krons.utils import create_path, json_dumpb, json_lines_iter
475
+ from krons.utils import create_path, json_dumpb
463
476
 
464
- if self.config.log_config is None or len(self.messages) == 0:
477
+ if not self.config.logging_enabled or len(self.messages) == 0:
465
478
  return None
466
479
 
467
- cfg = self.config.log_config
468
-
469
480
  async with self.messages:
470
481
  self._dump_count += 1
471
482
 
472
483
  filepath = await create_path(
473
- directory=cfg.persist_dir,
484
+ directory=self.config.log_persist_dir,
474
485
  filename=f"session_{str(self.id)[:8]}_{self._dump_count}",
475
- extension=cfg.extension,
486
+ extension=".json",
476
487
  timestamp=True,
477
488
  file_exist_ok=True,
478
489
  )
479
490
 
480
- items = [msg.to_dict(mode="json") for msg in self.messages]
481
-
482
- if cfg.extension == ".jsonl":
483
- content = b"".join(json_lines_iter(items, safe_fallback=True))
484
- await filepath.write_bytes(content)
485
- else:
486
- data = json_dumpb(items, safe_fallback=True)
487
- await filepath.write_bytes(data)
491
+ data = json_dumpb(self.to_dict(mode="json"), safe_fallback=True)
492
+ await filepath.write_bytes(data)
488
493
 
489
494
  if clear:
490
- self.communications.items.clear()
495
+ self.communications.clear()
491
496
 
492
497
  return Path(filepath)
493
498
 
494
499
  def _save_at_exit(self) -> None:
495
- """atexit callback. Dumps messages synchronously. Errors are suppressed."""
500
+ """atexit callback. Dumps session synchronously. Errors are suppressed."""
496
501
  if len(self.messages) > 0:
497
502
  try:
498
- self.dump_messages(clear=False)
503
+ self.dump(clear=False)
499
504
  except Exception:
500
505
  pass # Silent failure during interpreter shutdown
501
506
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: krons
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Spec-based composable framework for building type-safe systems
5
5
  Project-URL: Homepage, https://github.com/khive-ai/krons
6
6
  Project-URL: Repository, https://github.com/khive-ai/krons
@@ -37,14 +37,13 @@ krons/agent/third_party/claude_code.py,sha256=NBYMsdPheb0PqGgTO2_aUAR2XoGDsPUwsB
37
37
  krons/agent/third_party/gemini_models.py,sha256=2dag2WbSTKLBd8t0F7t_TXCcl7aqsTFgDI6XLkhsZwM,17416
38
38
  krons/agent/third_party/openai_models.py,sha256=L-GkJ-bBW5zwPbgCWEYHMNqRiZfFTAUJE96AVZoDAkQ,8040
39
39
  krons/core/__init__.py,sha256=TKi3toVRYybyjUwUJlng--pfWzWihUWQBCNh0UTfTSQ,3611
40
- krons/core/base/__init__.py,sha256=emyypsuiFK0UzYsDeamD84ygXnO1TLx2A5mBxGTBidE,3755
40
+ krons/core/base/__init__.py,sha256=Edl_HH2F1l0SMHMzuEPU_IFjkigRR1vtqVXcPm-mr1o,3614
41
41
  krons/core/base/broadcaster.py,sha256=Es__WfL-j2h5NS6aU18fgaFkZsJIL-zDe86Qvlea-io,4013
42
42
  krons/core/base/element.py,sha256=LqBbmPttXoDAFy56JzWhSJ4sBw_LW7hMGxZUog85N8g,7642
43
43
  krons/core/base/event.py,sha256=iZQ65Xg3a8eaCEl4Hca-pxPNtckhrHkydEqYyppbuo0,11524
44
44
  krons/core/base/eventbus.py,sha256=z45ORtGey5A1N9hdyEPOhlX1whaTTdqdk7g3XEDL-_8,3761
45
- krons/core/base/flow.py,sha256=eqsYm8QO7bD5Y9VYnlpHFWpaUCY0rCeKGrDrFUyBVA4,12364
45
+ krons/core/base/flow.py,sha256=bFG5x7PEbWApsD24GTgpD5VsedLO6364scaAxvYw6LM,12561
46
46
  krons/core/base/graph.py,sha256=FiTMArxuz6At3oYPEoamES3FtBM-AM6QnA7EQ8G18mo,15972
47
- krons/core/base/log.py,sha256=SMJlruIG4YHk6Ls45jQ9SrPxFCcnypUmTGaWd90wiwM,923
48
47
  krons/core/base/node.py,sha256=0_-R6uNe6-LF3adQRZowSIICdy6ep3cQIQRdF0oosoQ,34699
49
48
  krons/core/base/pile.py,sha256=ZKvnX3BjMsrH989TLNZII1dDWv4yPhSRzLKXx2yzyKQ,21108
50
49
  krons/core/base/processor.py,sha256=GrdNZa5YD0tXle847ttnxuWe4Ego1MhULzWHmSz5RqQ,18768
@@ -87,7 +86,7 @@ krons/session/constraints.py,sha256=DvmLKyf5eYUX2nxCDcco1-uDipGjiIJwoUxWVtX2w84,
87
86
  krons/session/exchange.py,sha256=QChEqK5NFZJpHWxPrXd8LkGBCFg6LNONd1ZEy0MufpI,9165
88
87
  krons/session/message.py,sha256=mtZ-PYCCoyWLy7BSB5TL5a0hIt0aQX2zdckOCIl6NZ0,2007
89
88
  krons/session/registry.py,sha256=13pPKmDZJaj9q27oUOdtH6-jrHP_HDFF8lCdcNF--EE,999
90
- krons/session/session.py,sha256=_y3-NEQxgBa51mRd6LiD7zTqJFikKCP4avLqMai6O08,16966
89
+ krons/session/session.py,sha256=2dykFkdK0DUu3kdv1v8RIevtuD3P50w_0vNIE3qs3D0,17204
91
90
  krons/utils/__init__.py,sha256=V-jTKULdofjJXxcEHygefKV_v6XE4H3poCdxPLjvf_4,1718
92
91
  krons/utils/_function_arg_parser.py,sha256=H5JVLBVY8W9ZNkZ4_YVtVVY1rFYvnIRhSrAhKEdj2Qc,3479
93
92
  krons/utils/_hash.py,sha256=W2Ma9v8-INPaGkur7GTtbF8KwuXSJNSwk8DCNPRvx8Q,6859
@@ -146,7 +145,7 @@ krons/work/rules/common/mapping.py,sha256=Loq54MNEtwpnHN0aypTjFOqwoOKLEysddHh-JE
146
145
  krons/work/rules/common/model.py,sha256=xmM6coEThf_fgIiqJiyDgvdfib_FpVeY6LgWPVcWSwU,3026
147
146
  krons/work/rules/common/number.py,sha256=cCukgMSpQu5RdYK5rXAUyop9qXgDRfLCioMvE8kIzHg,3162
148
147
  krons/work/rules/common/string.py,sha256=zHp_OLh0FL4PvmSlyDTEzb2I97-DBSEyI2zcMo10voA,5090
149
- krons-0.2.2.dist-info/METADATA,sha256=X1gYH9GGL-V2GhH3_iy96GzRZOCwO-HKt7NpNrzmgcg,2527
150
- krons-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
151
- krons-0.2.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
152
- krons-0.2.2.dist-info/RECORD,,
148
+ krons-0.2.3.dist-info/METADATA,sha256=5VYw8_rlpQ5Q9W8EGwy8XZ19F--f_URzcmVAiG9WIqY,2527
149
+ krons-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
150
+ krons-0.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
151
+ krons-0.2.3.dist-info/RECORD,,
krons/core/base/log.py DELETED
@@ -1,32 +0,0 @@
1
- # Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
2
- # SPDX-License-Identifier: Apache-2.0
3
-
4
- """Logging configuration for Session message persistence.
5
-
6
- Provides DataLoggerConfig for configuring automatic message dumps.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from pathlib import Path
12
- from typing import Literal
13
-
14
- from pydantic import Field
15
-
16
- from krons.core.types import HashableModel
17
-
18
- __all__ = ("DataLoggerConfig",)
19
-
20
-
21
- class DataLoggerConfig(HashableModel):
22
- """Configuration for Session message persistence.
23
-
24
- Attributes:
25
- persist_dir: Directory for dump files.
26
- extension: Output format (.json array or .jsonl newline-delimited).
27
- auto_save_on_exit: Register atexit handler on Session creation.
28
- """
29
-
30
- persist_dir: str | Path = Field(default="./logs")
31
- extension: Literal[".json", ".jsonl"] = Field(default=".jsonl")
32
- auto_save_on_exit: bool = Field(default=True)
File without changes