krons 0.1.1__py3-none-any.whl → 0.2.1__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.
Files changed (142) hide show
  1. krons/__init__.py +49 -0
  2. krons/agent/__init__.py +144 -0
  3. krons/agent/mcps/__init__.py +14 -0
  4. krons/agent/mcps/loader.py +287 -0
  5. krons/agent/mcps/wrapper.py +799 -0
  6. krons/agent/message/__init__.py +20 -0
  7. krons/agent/message/action.py +69 -0
  8. krons/agent/message/assistant.py +52 -0
  9. krons/agent/message/common.py +49 -0
  10. krons/agent/message/instruction.py +130 -0
  11. krons/agent/message/prepare_msg.py +187 -0
  12. krons/agent/message/role.py +53 -0
  13. krons/agent/message/system.py +53 -0
  14. krons/agent/operations/__init__.py +82 -0
  15. krons/agent/operations/act.py +100 -0
  16. krons/agent/operations/generate.py +145 -0
  17. krons/agent/operations/llm_reparse.py +89 -0
  18. krons/agent/operations/operate.py +247 -0
  19. krons/agent/operations/parse.py +243 -0
  20. krons/agent/operations/react.py +286 -0
  21. krons/agent/operations/specs.py +235 -0
  22. krons/agent/operations/structure.py +151 -0
  23. krons/agent/operations/utils.py +79 -0
  24. krons/agent/providers/__init__.py +17 -0
  25. krons/agent/providers/anthropic_messages.py +146 -0
  26. krons/agent/providers/claude_code.py +276 -0
  27. krons/agent/providers/gemini.py +268 -0
  28. krons/agent/providers/match.py +75 -0
  29. krons/agent/providers/oai_chat.py +174 -0
  30. krons/agent/third_party/__init__.py +2 -0
  31. krons/agent/third_party/anthropic_models.py +154 -0
  32. krons/agent/third_party/claude_code.py +682 -0
  33. krons/agent/third_party/gemini_models.py +508 -0
  34. krons/agent/third_party/openai_models.py +295 -0
  35. krons/agent/tool.py +291 -0
  36. krons/core/__init__.py +56 -74
  37. krons/core/base/__init__.py +121 -0
  38. krons/core/{broadcaster.py → base/broadcaster.py} +7 -3
  39. krons/core/{element.py → base/element.py} +13 -5
  40. krons/core/{event.py → base/event.py} +39 -6
  41. krons/core/{eventbus.py → base/eventbus.py} +3 -1
  42. krons/core/{flow.py → base/flow.py} +11 -4
  43. krons/core/{graph.py → base/graph.py} +24 -8
  44. krons/core/{node.py → base/node.py} +44 -19
  45. krons/core/{pile.py → base/pile.py} +22 -8
  46. krons/core/{processor.py → base/processor.py} +21 -7
  47. krons/core/{progression.py → base/progression.py} +3 -1
  48. krons/{specs → core/specs}/__init__.py +0 -5
  49. krons/{specs → core/specs}/adapters/dataclass_field.py +16 -8
  50. krons/{specs → core/specs}/adapters/pydantic_adapter.py +11 -5
  51. krons/{specs → core/specs}/adapters/sql_ddl.py +14 -8
  52. krons/{specs → core/specs}/catalog/__init__.py +2 -2
  53. krons/{specs → core/specs}/catalog/_audit.py +2 -2
  54. krons/{specs → core/specs}/catalog/_common.py +2 -2
  55. krons/{specs → core/specs}/catalog/_content.py +4 -4
  56. krons/{specs → core/specs}/catalog/_enforcement.py +3 -3
  57. krons/{specs → core/specs}/factory.py +5 -5
  58. krons/{specs → core/specs}/operable.py +8 -2
  59. krons/{specs → core/specs}/protocol.py +4 -2
  60. krons/{specs → core/specs}/spec.py +23 -11
  61. krons/{types → core/types}/base.py +4 -2
  62. krons/{types → core/types}/db_types.py +2 -2
  63. krons/errors.py +13 -13
  64. krons/protocols.py +9 -4
  65. krons/resource/__init__.py +89 -0
  66. krons/{services → resource}/backend.py +48 -22
  67. krons/{services → resource}/endpoint.py +28 -14
  68. krons/{services → resource}/hook.py +20 -7
  69. krons/{services → resource}/imodel.py +46 -28
  70. krons/{services → resource}/registry.py +26 -24
  71. krons/{services → resource}/utilities/rate_limited_executor.py +7 -3
  72. krons/{services → resource}/utilities/rate_limiter.py +3 -1
  73. krons/{services → resource}/utilities/resilience.py +15 -5
  74. krons/resource/utilities/token_calculator.py +185 -0
  75. krons/session/__init__.py +12 -17
  76. krons/session/constraints.py +70 -0
  77. krons/session/exchange.py +11 -3
  78. krons/session/message.py +3 -1
  79. krons/session/registry.py +35 -0
  80. krons/session/session.py +165 -174
  81. krons/utils/__init__.py +45 -0
  82. krons/utils/_function_arg_parser.py +99 -0
  83. krons/utils/_pythonic_function_call.py +249 -0
  84. krons/utils/_to_list.py +9 -3
  85. krons/utils/_utils.py +6 -2
  86. krons/utils/concurrency/_async_call.py +4 -2
  87. krons/utils/concurrency/_errors.py +3 -1
  88. krons/utils/concurrency/_patterns.py +3 -1
  89. krons/utils/concurrency/_resource_tracker.py +6 -2
  90. krons/utils/display.py +257 -0
  91. krons/utils/fuzzy/__init__.py +6 -1
  92. krons/utils/fuzzy/_fuzzy_match.py +14 -8
  93. krons/utils/fuzzy/_string_similarity.py +3 -1
  94. krons/utils/fuzzy/_to_dict.py +3 -1
  95. krons/utils/schemas/__init__.py +26 -0
  96. krons/utils/schemas/_breakdown_pydantic_annotation.py +131 -0
  97. krons/utils/schemas/_formatter.py +72 -0
  98. krons/utils/schemas/_minimal_yaml.py +151 -0
  99. krons/utils/schemas/_typescript.py +153 -0
  100. krons/utils/validators/__init__.py +3 -0
  101. krons/utils/validators/_validate_image_url.py +56 -0
  102. krons/work/__init__.py +115 -0
  103. krons/work/engine.py +333 -0
  104. krons/work/form.py +242 -0
  105. krons/{operations → work/operations}/__init__.py +7 -4
  106. krons/{operations → work/operations}/builder.py +1 -1
  107. krons/{enforcement → work/operations}/context.py +36 -5
  108. krons/{operations → work/operations}/flow.py +13 -5
  109. krons/{operations → work/operations}/node.py +45 -43
  110. krons/work/operations/registry.py +103 -0
  111. krons/work/report.py +268 -0
  112. krons/work/rules/__init__.py +47 -0
  113. krons/{enforcement → work/rules}/common/boolean.py +3 -1
  114. krons/{enforcement → work/rules}/common/choice.py +9 -3
  115. krons/{enforcement → work/rules}/common/number.py +3 -1
  116. krons/{enforcement → work/rules}/common/string.py +9 -3
  117. krons/{enforcement → work/rules}/rule.py +1 -1
  118. krons/{enforcement → work/rules}/validator.py +20 -5
  119. krons/work/worker.py +266 -0
  120. {krons-0.1.1.dist-info → krons-0.2.1.dist-info}/METADATA +15 -1
  121. krons-0.2.1.dist-info/RECORD +151 -0
  122. krons/enforcement/__init__.py +0 -57
  123. krons/enforcement/policy.py +0 -80
  124. krons/enforcement/service.py +0 -370
  125. krons/operations/registry.py +0 -92
  126. krons/services/__init__.py +0 -81
  127. krons/specs/phrase.py +0 -405
  128. krons-0.1.1.dist-info/RECORD +0 -101
  129. /krons/{specs → core/specs}/adapters/__init__.py +0 -0
  130. /krons/{specs → core/specs}/adapters/_utils.py +0 -0
  131. /krons/{specs → core/specs}/adapters/factory.py +0 -0
  132. /krons/{types → core/types}/__init__.py +0 -0
  133. /krons/{types → core/types}/_sentinel.py +0 -0
  134. /krons/{types → core/types}/identity.py +0 -0
  135. /krons/{services → resource}/utilities/__init__.py +0 -0
  136. /krons/{services → resource}/utilities/header_factory.py +0 -0
  137. /krons/{enforcement → work/rules}/common/__init__.py +0 -0
  138. /krons/{enforcement → work/rules}/common/mapping.py +0 -0
  139. /krons/{enforcement → work/rules}/common/model.py +0 -0
  140. /krons/{enforcement → work/rules}/registry.py +0 -0
  141. {krons-0.1.1.dist-info → krons-0.2.1.dist-info}/WHEEL +0 -0
  142. {krons-0.1.1.dist-info → krons-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -10,7 +10,7 @@ from pydantic import Field, PrivateAttr, field_validator
10
10
  from typing_extensions import TypedDict
11
11
 
12
12
  from krons.core import Broadcaster, Event, EventStatus
13
- from krons.types import Enum, Undefined
13
+ from krons.core.types import Enum, Undefined
14
14
  from krons.utils import concurrency
15
15
 
16
16
  SC = TypeVar("SC")
@@ -84,7 +84,11 @@ class HookEvent(Event):
84
84
  )
85
85
 
86
86
  # Unpack the result - hook_phase returns tuple of (inner_tuple, meta)
87
- if isinstance(result, tuple) and len(result) == 2 and isinstance(result[1], dict):
87
+ if (
88
+ isinstance(result, tuple)
89
+ and len(result) == 2
90
+ and isinstance(result[1], dict)
91
+ ):
88
92
  inner_tuple, meta = result
89
93
  res, se, _ = inner_tuple
90
94
  else:
@@ -93,7 +97,9 @@ class HookEvent(Event):
93
97
  meta = {}
94
98
 
95
99
  # Build associated event info from meta dict
96
- event_info: AssociatedEventInfo = {"kron_class": str(meta.get("kron_class", ""))}
100
+ event_info: AssociatedEventInfo = {
101
+ "kron_class": str(meta.get("kron_class", ""))
102
+ }
97
103
  if "event_id" in meta:
98
104
  event_info["event_id"] = str(meta["event_id"])
99
105
  if "event_created_at" in meta:
@@ -180,7 +186,9 @@ def validate_stream_handlers(kw: dict[Any, Any]) -> None:
180
186
 
181
187
  for k, v in kw.items():
182
188
  if not isinstance(k, str | type):
183
- raise ValueError(f"Stream handler key must be a string or type, got {type(k)}")
189
+ raise ValueError(
190
+ f"Stream handler key must be a string or type, got {type(k)}"
191
+ )
184
192
 
185
193
  if not callable(v):
186
194
  raise ValueError(f"Stream handler for {k} must be callable, got {type(v)}")
@@ -402,7 +410,8 @@ class HookRegistry:
402
410
  exit: bool = False,
403
411
  **kw: Any,
404
412
  ) -> (
405
- tuple[tuple[Any, bool, EventStatus], dict[str, Any]] | tuple[Any, bool, EventStatus | None]
413
+ tuple[tuple[Any, bool, EventStatus], dict[str, Any]]
414
+ | tuple[Any, bool, EventStatus | None]
406
415
  ):
407
416
  """Call a hook or stream handler.
408
417
 
@@ -437,7 +446,9 @@ class HookRegistry:
437
446
  await self.pre_invocation(event_like, exit=exit, **kw),
438
447
  meta,
439
448
  )
440
- raise TypeError("PreInvocation requires an Event instance, not a type")
449
+ raise TypeError(
450
+ "PreInvocation requires an Event instance, not a type"
451
+ )
441
452
  case HookPhase.PostInvocation | HookPhase.PostInvocation.value:
442
453
  # For post_invocation, event_like should be an instance
443
454
  if isinstance(event_like, Event):
@@ -447,7 +458,9 @@ class HookRegistry:
447
458
  await self.post_invocation(event_like, exit=exit, **kw),
448
459
  meta,
449
460
  )
450
- raise TypeError("PostInvocation requires an Event instance, not a type")
461
+ raise TypeError(
462
+ "PostInvocation requires an Event instance, not a type"
463
+ )
451
464
  return await self.handle_streaming_chunk(chunk_type, chunk, exit=exit, **kw)
452
465
 
453
466
  def _can_handle(
@@ -11,7 +11,7 @@ from krons.core import Element, Executor
11
11
  from krons.protocols import Invocable, implements
12
12
  from krons.utils.concurrency import sleep
13
13
 
14
- from .backend import ServiceBackend
14
+ from .backend import ResourceBackend
15
15
  from .endpoint import Endpoint
16
16
  from .hook import HookRegistry
17
17
  from .utilities.rate_limited_executor import RateLimitedExecutor
@@ -26,14 +26,14 @@ __all__ = ("iModel",)
26
26
 
27
27
  @implements(Invocable)
28
28
  class iModel(Element): # noqa: N801
29
- """Unified service interface wrapping ServiceBackend with rate limiting and hooks.
29
+ """Unified resource interface wrapping ResourceBackend with rate limiting and hooks.
30
30
 
31
- Combines ServiceBackend (API abstraction) with optional:
31
+ Combines ResourceBackend (API abstraction) with optional:
32
32
  - Rate limiting: TokenBucket (simple) or Executor (event-driven)
33
33
  - Hook registry: Lifecycle callbacks at PreEventCreate/PreInvocation/PostInvocation
34
34
 
35
35
  Attributes:
36
- backend: ServiceBackend instance (e.g., Endpoint for HTTP APIs).
36
+ backend: ResourceBackend instance (e.g., Endpoint for HTTP APIs).
37
37
  rate_limiter: Optional TokenBucket for simple blocking rate limits.
38
38
  executor: Optional Executor for event-driven processing with rate limiting.
39
39
  hook_registry: Optional HookRegistry for invocation lifecycle callbacks.
@@ -43,9 +43,9 @@ class iModel(Element): # noqa: N801
43
43
  _EXECUTOR_POLL_TIMEOUT_ITERATIONS = 100
44
44
  _EXECUTOR_POLL_SLEEP_INTERVAL = 0.1
45
45
 
46
- backend: ServiceBackend | None = Field(
46
+ backend: ResourceBackend | None = Field(
47
47
  None,
48
- description="ServiceBackend instance (e.g., Endpoint)",
48
+ description="ResourceBackend instance (e.g., Endpoint)",
49
49
  )
50
50
 
51
51
  rate_limiter: TokenBucket | None = Field(
@@ -70,7 +70,7 @@ class iModel(Element): # noqa: N801
70
70
 
71
71
  def __init__(
72
72
  self,
73
- backend: ServiceBackend,
73
+ backend: ResourceBackend,
74
74
  rate_limiter: TokenBucket | None = None,
75
75
  executor: Executor | None = None,
76
76
  hook_registry: HookRegistry | None = None,
@@ -78,10 +78,10 @@ class iModel(Element): # noqa: N801
78
78
  capacity_refresh_time: float = 60,
79
79
  limit_requests: int | None = None,
80
80
  ):
81
- """Initialize iModel with ServiceBackend.
81
+ """Initialize iModel with ResourceBackend.
82
82
 
83
83
  Args:
84
- backend: ServiceBackend instance (required).
84
+ backend: ResourceBackend instance (required).
85
85
  rate_limiter: TokenBucket for simple blocking rate limits.
86
86
  executor: Executor for event-driven processing.
87
87
  hook_registry: HookRegistry for lifecycle callbacks.
@@ -114,21 +114,21 @@ class iModel(Element): # noqa: N801
114
114
 
115
115
  @property
116
116
  def name(self) -> str:
117
- """Service name from backend."""
117
+ """Resource name from backend."""
118
118
  if self.backend is None:
119
119
  raise RuntimeError("Backend not configured")
120
120
  return self.backend.name
121
121
 
122
122
  @property
123
123
  def version(self) -> str:
124
- """Service version from backend."""
124
+ """Resource version from backend."""
125
125
  if self.backend is None:
126
126
  raise RuntimeError("Backend not configured")
127
127
  return self.backend.version or ""
128
128
 
129
129
  @property
130
130
  def tags(self) -> set[str]:
131
- """Service tags from backend."""
131
+ """Resource tags from backend."""
132
132
  if self.backend is None:
133
133
  raise RuntimeError("Backend not configured")
134
134
  return self.backend.tags
@@ -181,7 +181,11 @@ class iModel(Element): # noqa: N801
181
181
  hook_phase=HookPhase.PreEventCreate,
182
182
  event_like=calling_type,
183
183
  registry=self.hook_registry,
184
- exit=(create_event_exit_hook if create_event_exit_hook is not None else False),
184
+ exit=(
185
+ create_event_exit_hook
186
+ if create_event_exit_hook is not None
187
+ else False
188
+ ),
185
189
  timeout=create_event_hook_timeout,
186
190
  streaming=False,
187
191
  params=create_event_hook_params or {},
@@ -194,11 +198,12 @@ class iModel(Element): # noqa: N801
194
198
  )
195
199
 
196
200
  payload = self.backend.create_payload(request=arguments)
201
+ # Handle backends that return (payload, headers) tuple
202
+ if isinstance(payload, tuple):
203
+ payload, _ = payload
197
204
  calling: Calling = calling_type(
198
205
  backend=self.backend,
199
206
  payload=payload,
200
- timeout=timeout,
201
- streaming=streaming,
202
207
  )
203
208
 
204
209
  if self.hook_registry is not None and self.hook_registry._can_handle(
@@ -206,7 +211,9 @@ class iModel(Element): # noqa: N801
206
211
  ):
207
212
  calling.create_pre_invoke_hook(
208
213
  hook_registry=self.hook_registry,
209
- exit_hook=(pre_invoke_exit_hook if pre_invoke_exit_hook is not None else False),
214
+ exit_hook=(
215
+ pre_invoke_exit_hook if pre_invoke_exit_hook is not None else False
216
+ ),
210
217
  hook_timeout=pre_invoke_hook_timeout,
211
218
  hook_params=pre_invoke_hook_params or {},
212
219
  )
@@ -216,7 +223,11 @@ class iModel(Element): # noqa: N801
216
223
  ):
217
224
  calling.create_post_invoke_hook(
218
225
  hook_registry=self.hook_registry,
219
- exit_hook=(post_invoke_exit_hook if post_invoke_exit_hook is not None else False),
226
+ exit_hook=(
227
+ post_invoke_exit_hook
228
+ if post_invoke_exit_hook is not None
229
+ else False
230
+ ),
220
231
  hook_timeout=post_invoke_hook_timeout,
221
232
  hook_params=post_invoke_hook_params or {},
222
233
  )
@@ -281,7 +292,8 @@ class iModel(Element): # noqa: N801
281
292
  # Poll for completion (fast backends see ~100-200% overhead, slow backends <10%)
282
293
  interval = poll_interval or self._EXECUTOR_POLL_SLEEP_INTERVAL
283
294
  timeout_seconds = poll_timeout or (
284
- self._EXECUTOR_POLL_TIMEOUT_ITERATIONS * self._EXECUTOR_POLL_SLEEP_INTERVAL
295
+ self._EXECUTOR_POLL_TIMEOUT_ITERATIONS
296
+ * self._EXECUTOR_POLL_SLEEP_INTERVAL
285
297
  )
286
298
  max_iterations = int(timeout_seconds / interval)
287
299
  ctr = 0
@@ -300,7 +312,9 @@ class iModel(Element): # noqa: N801
300
312
  f"Event aborted after 3 permission denials (rate limited): {calling.id}"
301
313
  )
302
314
  elif calling.execution.status.value == "failed":
303
- raise calling.execution.error or RuntimeError(f"Event failed: {calling.id}")
315
+ raise calling.execution.error or RuntimeError(
316
+ f"Event failed: {calling.id}"
317
+ )
304
318
 
305
319
  self._store_claude_code_session_id(calling)
306
320
  return calling
@@ -317,7 +331,7 @@ class iModel(Element): # noqa: N801
317
331
 
318
332
  def _store_claude_code_session_id(self, calling: Calling) -> None:
319
333
  """Extract and store Claude Code session_id for context continuation."""
320
- from krons.types import is_sentinel
334
+ from krons.core.types import is_sentinel
321
335
 
322
336
  from .backend import NormalizedResponse
323
337
 
@@ -334,7 +348,7 @@ class iModel(Element): # noqa: N801
334
348
  self.provider_metadata["session_id"] = session_id
335
349
 
336
350
  @field_serializer("backend")
337
- def _serialize_backend(self, backend: ServiceBackend) -> dict[str, Any] | None:
351
+ def _serialize_backend(self, backend: ResourceBackend) -> dict[str, Any] | None:
338
352
  """Serialize backend to dict with kron_class for polymorphic restoration."""
339
353
  if backend is None:
340
354
  return None
@@ -384,24 +398,24 @@ class iModel(Element): # noqa: N801
384
398
 
385
399
  @field_validator("backend", mode="before")
386
400
  @classmethod
387
- def _deserialize_backend(cls, v: Any) -> ServiceBackend:
401
+ def _deserialize_backend(cls, v: Any) -> ResourceBackend:
388
402
  """Reconstruct backend via Element polymorphic deserialization."""
389
403
  if v is None:
390
404
  raise ValueError("backend is required")
391
405
 
392
- if isinstance(v, ServiceBackend):
406
+ if isinstance(v, ResourceBackend):
393
407
  return v
394
408
 
395
409
  if not isinstance(v, dict):
396
- raise ValueError("backend must be a dict or ServiceBackend instance")
410
+ raise ValueError("backend must be a dict or ResourceBackend instance")
397
411
 
398
412
  from krons.core import Element
399
413
 
400
414
  backend = Element.from_dict(v)
401
415
 
402
- if not isinstance(backend, ServiceBackend):
416
+ if not isinstance(backend, ResourceBackend):
403
417
  raise ValueError(
404
- f"Deserialized backend must be ServiceBackend subclass, got: {type(backend).__name__}"
418
+ f"Deserialized backend must be ResourceBackend subclass, got: {type(backend).__name__}"
405
419
  )
406
420
  return backend
407
421
 
@@ -420,9 +434,13 @@ class iModel(Element): # noqa: N801
420
434
 
421
435
  config = {**v}
422
436
  if "request_bucket" in config and isinstance(config["request_bucket"], dict):
423
- config["request_bucket"] = TokenBucket(RateLimitConfig(**config["request_bucket"]))
437
+ config["request_bucket"] = TokenBucket(
438
+ RateLimitConfig(**config["request_bucket"])
439
+ )
424
440
  if "token_bucket" in config and isinstance(config["token_bucket"], dict):
425
- config["token_bucket"] = TokenBucket(RateLimitConfig(**config["token_bucket"]))
441
+ config["token_bucket"] = TokenBucket(
442
+ RateLimitConfig(**config["token_bucket"])
443
+ )
426
444
 
427
445
  return RateLimitedExecutor(processor_config=config)
428
446
 
@@ -7,23 +7,23 @@ from typing import Any
7
7
  from uuid import UUID
8
8
 
9
9
  from krons.core import Pile
10
- from krons.types import Undefined, UndefinedType, is_sentinel
10
+ from krons.core.types import Undefined, UndefinedType, is_sentinel
11
11
 
12
12
  from .imodel import iModel
13
13
 
14
- __all__ = ("ServiceRegistry",)
14
+ __all__ = ("ResourceRegistry",)
15
15
 
16
16
 
17
- class ServiceRegistry:
18
- """Service registry managing iModel instances with O(1) name-based lookup.
17
+ class ResourceRegistry:
18
+ """Resource registry managing iModel instances with O(1) name-based lookup.
19
19
 
20
20
  Provides type-safe storage via Pile[iModel] with name-based indexing.
21
- Services must have unique names; duplicates raise ValueError unless update=True.
21
+ Resources must have unique names; duplicates raise ValueError unless update=True.
22
22
 
23
23
  Example:
24
- >>> registry = ServiceRegistry()
24
+ >>> registry = ResourceRegistry()
25
25
  >>> registry.register(iModel(backend=my_endpoint))
26
- >>> model = registry.get("my_service")
26
+ >>> model = registry.get("my_resource")
27
27
  >>> tagged = registry.list_by_tag("api")
28
28
  """
29
29
 
@@ -39,17 +39,17 @@ class ServiceRegistry:
39
39
 
40
40
  Args:
41
41
  model: iModel instance to register.
42
- update: If True, replaces existing service with same name.
42
+ update: If True, replaces existing resource with same name.
43
43
 
44
44
  Returns:
45
- UUID of registered service.
45
+ UUID of registered resource.
46
46
 
47
47
  Raises:
48
- ValueError: If service name exists and update=False.
48
+ ValueError: If resource name exists and update=False.
49
49
  """
50
50
  if model.name in self._name_index:
51
51
  if not update:
52
- raise ValueError(f"Service '{model.name}' already registered")
52
+ raise ValueError(f"Resource '{model.name}' already registered")
53
53
  # Update: remove old, add new
54
54
  old_uid = self._name_index[model.name]
55
55
  self._pile.remove(old_uid)
@@ -60,15 +60,17 @@ class ServiceRegistry:
60
60
  return model.id
61
61
 
62
62
  def unregister(self, name: str) -> iModel:
63
- """Remove and return service by name. Raises KeyError if not found."""
63
+ """Remove and return resource by name. Raises KeyError if not found."""
64
64
  if name not in self._name_index:
65
- raise KeyError(f"Service '{name}' not found")
65
+ raise KeyError(f"Resource '{name}' not found")
66
66
 
67
67
  uid = self._name_index.pop(name)
68
68
  return self._pile.remove(uid)
69
69
 
70
- def get(self, name: str | UUID | iModel, default: Any | UndefinedType = Undefined) -> iModel:
71
- """Get service by name, UUID, or return iModel passthrough. Raises KeyError if not found."""
70
+ def get(
71
+ self, name: str | UUID | iModel, default: Any | UndefinedType = Undefined
72
+ ) -> iModel:
73
+ """Get resource by name, UUID, or return iModel passthrough. Raises KeyError if not found."""
72
74
  if isinstance(name, UUID):
73
75
  return self._pile[name]
74
76
  if isinstance(name, iModel):
@@ -76,40 +78,40 @@ class ServiceRegistry:
76
78
  if name not in self._name_index:
77
79
  if not is_sentinel(default):
78
80
  return default
79
- raise KeyError(f"Service '{name}' not found")
81
+ raise KeyError(f"Resource '{name}' not found")
80
82
 
81
83
  uid = self._name_index[name]
82
84
  return self._pile[uid]
83
85
 
84
86
  def has(self, name: str) -> bool:
85
- """Check if service exists."""
87
+ """Check if resource exists."""
86
88
  return name in self._name_index
87
89
 
88
90
  def list_names(self) -> list[str]:
89
- """List all registered service names."""
91
+ """List all registered resource names."""
90
92
  return list(self._name_index.keys())
91
93
 
92
94
  def list_by_tag(self, tag: str) -> Pile[iModel]:
93
- """Filter services by tag, returns Pile of matching iModels."""
95
+ """Filter resources by tag, returns Pile of matching iModels."""
94
96
  return self._pile[lambda m: tag in m.tags]
95
97
 
96
98
  def count(self) -> int:
97
- """Count registered services."""
99
+ """Count registered resources."""
98
100
  return len(self._pile)
99
101
 
100
102
  def clear(self) -> None:
101
- """Remove all registered services."""
103
+ """Remove all registered resources."""
102
104
  self._pile.clear()
103
105
  self._name_index.clear()
104
106
 
105
107
  def __len__(self) -> int:
106
- """Return number of registered services."""
108
+ """Return number of registered resources."""
107
109
  return len(self._pile)
108
110
 
109
111
  def __contains__(self, name: str) -> bool:
110
- """Check if service exists (supports `name in registry`)."""
112
+ """Check if resource exists (supports `name in registry`)."""
111
113
  return name in self._name_index
112
114
 
113
115
  def __repr__(self) -> str:
114
116
  """String representation."""
115
- return f"ServiceRegistry(count={len(self)})"
117
+ return f"ResourceRegistry(count={len(self)})"
@@ -15,7 +15,7 @@ from typing import TYPE_CHECKING, Any, Self
15
15
  from typing_extensions import override
16
16
 
17
17
  from krons.core import Event, Executor, Processor
18
- from krons.services.endpoint import APICalling
18
+ from krons.resource.endpoint import APICalling
19
19
  from krons.utils.concurrency import get_cancelled_exc_class, sleep
20
20
 
21
21
  from .rate_limiter import TokenBucket
@@ -213,8 +213,12 @@ class RateLimitedProcessor(Processor):
213
213
  "concurrency_limit": self.concurrency_limit,
214
214
  "max_queue_size": self.max_queue_size,
215
215
  "max_denial_tracking": self.max_denial_tracking,
216
- "request_bucket": (self.request_bucket.to_dict() if self.request_bucket else None),
217
- "token_bucket": (self.token_bucket.to_dict() if self.token_bucket else None),
216
+ "request_bucket": (
217
+ self.request_bucket.to_dict() if self.request_bucket else None
218
+ ),
219
+ "token_bucket": (
220
+ self.token_bucket.to_dict() if self.token_bucket else None
221
+ ),
218
222
  }
219
223
 
220
224
 
@@ -105,7 +105,9 @@ class TokenBucket:
105
105
 
106
106
  if self.tokens >= tokens:
107
107
  self.tokens -= tokens
108
- logger.debug(f"Acquired {tokens} tokens, {self.tokens:.2f} remaining")
108
+ logger.debug(
109
+ f"Acquired {tokens} tokens, {self.tokens:.2f} remaining"
110
+ )
109
111
  return True
110
112
 
111
113
  deficit = tokens - self.tokens
@@ -199,7 +199,9 @@ class CircuitBreaker:
199
199
  if now - self.last_failure_time >= self.recovery_time:
200
200
  await self._change_state(CircuitState.HALF_OPEN)
201
201
  else:
202
- recovery_remaining = self.recovery_time - (now - self.last_failure_time)
202
+ recovery_remaining = self.recovery_time - (
203
+ now - self.last_failure_time
204
+ )
203
205
  self._metrics["rejected_count"] += 1
204
206
 
205
207
  logger.warning(
@@ -223,7 +225,9 @@ class CircuitBreaker:
223
225
 
224
226
  return True, 0.0
225
227
 
226
- async def execute(self, func: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any) -> T:
228
+ async def execute(
229
+ self, func: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any
230
+ ) -> T:
227
231
  """Execute async function with circuit breaker protection.
228
232
 
229
233
  Args:
@@ -259,7 +263,9 @@ class CircuitBreaker:
259
263
  return result
260
264
 
261
265
  except Exception as e:
262
- is_excluded = any(isinstance(e, exc_type) for exc_type in self.excluded_exceptions)
266
+ is_excluded = any(
267
+ isinstance(e, exc_type) for exc_type in self.excluded_exceptions
268
+ )
263
269
 
264
270
  if not is_excluded:
265
271
  async with self._lock:
@@ -322,7 +328,9 @@ class RetryConfig:
322
328
  Returns:
323
329
  Delay in seconds before next retry
324
330
  """
325
- delay = min(self.initial_delay * (self.exponential_base**attempt), self.max_delay)
331
+ delay = min(
332
+ self.initial_delay * (self.exponential_base**attempt), self.max_delay
333
+ )
326
334
  if self.jitter:
327
335
  delay = delay * (0.5 + random.random() * 0.5)
328
336
  return delay
@@ -394,7 +402,9 @@ async def retry_with_backoff(
394
402
  last_exception = e
395
403
 
396
404
  if attempt >= max_retries:
397
- logger.error(f"All {max_retries} retry attempts exhausted for {func.__name__}: {e}")
405
+ logger.error(
406
+ f"All {max_retries} retry attempts exhausted for {func.__name__}: {e}"
407
+ )
398
408
  raise
399
409
 
400
410
  delay = min(initial_delay * (exponential_base**attempt), max_delay)