Skip to content

Commit 08bbe33

Browse files
authored
Refactor/Adapt ClassRegistry from kajson, remove sandbox manager (Pipelex#103)
- Adapt to new ClassRegistry from kajson, used with dependency injection - Remove unused sandbox component, adapt logging, pretty printing, error reporting
1 parent ea6e9eb commit 08bbe33

25 files changed

Lines changed: 237 additions & 174 deletions

File tree

docs/pages/configuration/config-practical/logging-config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ silenced_problem_ids = ["azure_openai_no_stream_options"]
7979
### Poor Loggers
8080

8181
```toml
82-
generic_poor_logger = "#sandbox"
82+
generic_poor_logger = "#poor-log"
8383
poor_loggers = [
8484
"kajson.decoder.sandbox",
8585
"kajson.encoder.sandbox",
@@ -150,7 +150,7 @@ is_caller_info_enabled = true
150150
caller_info_template = "file_line_func"
151151

152152
silenced_problem_ids = []
153-
generic_poor_logger = "#sandbox"
153+
generic_poor_logger = "#poor-log"
154154
poor_loggers = []
155155

156156
[pipelex.log_config.package_log_levels]

pipelex/cogt/content_generation/assignment_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Any, Dict, Optional, Type
22

3-
from kajson.class_registry import class_registry
43
from pydantic import BaseModel
54
from typing_extensions import override
65

@@ -16,6 +15,7 @@
1615
from pipelex.cogt.ocr.ocr_handle import OcrHandle
1716
from pipelex.cogt.ocr.ocr_input import OcrInput
1817
from pipelex.cogt.ocr.ocr_job_components import OcrJobConfig, OcrJobParams
18+
from pipelex.hub import get_class_registry
1919
from pipelex.pipeline.job_metadata import JobMetadata
2020
from pipelex.tools.templating.jinja2_template_category import Jinja2TemplateCategory
2121
from pipelex.tools.templating.templating_models import PromptingStyle
@@ -80,7 +80,7 @@ class ObjectAssignment(BaseModel):
8080

8181
def __init__(self, **kwargs: Any):
8282
super().__init__(**kwargs)
83-
if not class_registry.has_class(name=self.object_class_name):
83+
if not get_class_registry().has_class(name=self.object_class_name):
8484
error_msg = f"Could not create ObjectAssignment for class '{self.object_class_name}' because it is not in the class registry."
8585
raise LLMAssignmentError(error_msg)
8686

@@ -90,7 +90,7 @@ def make_for_class(
9090
llm_assignment: LLMAssignment,
9191
) -> "ObjectAssignment":
9292
object_class_name = object_class.__name__
93-
class_registry.register_class(
93+
get_class_registry().register_class(
9494
class_type=object_class,
9595
name=object_class_name,
9696
should_warn_if_already_registered=False,

pipelex/cogt/content_generation/llm_generate.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from typing import List
22

3-
from kajson.class_registry import class_registry
43
from pydantic import BaseModel
54

65
from pipelex import log
76
from pipelex.cogt.content_generation.assignment_models import LLMAssignment, ObjectAssignment
87
from pipelex.cogt.llm.llm_job_factory import LLMJobFactory
9-
from pipelex.hub import get_llm_worker
8+
from pipelex.hub import get_class_registry, get_llm_worker
109

1110

1211
async def llm_gen_text(llm_assignment: LLMAssignment) -> str:
@@ -31,7 +30,7 @@ async def llm_gen_object(object_assignment: ObjectAssignment) -> BaseModel:
3130
llm_job_params=llm_assignment.llm_job_params,
3231
)
3332
content_class_name = object_assignment.object_class_name
34-
content_class = class_registry.get_required_base_model(name=content_class_name)
33+
content_class = get_class_registry().get_required_base_model(name=content_class_name)
3534
generated_object: BaseModel = await llm_worker.gen_object(
3635
llm_job=llm_job,
3736
schema=content_class,
@@ -49,7 +48,7 @@ async def llm_gen_object_list(object_assignment: ObjectAssignment) -> List[BaseM
4948
llm_job_params=llm_assignment.llm_job_params,
5049
)
5150
item_class_name = object_assignment.object_class_name
52-
item_class = class_registry.get_required_class(name=item_class_name)
51+
item_class = get_class_registry().get_required_class(name=item_class_name)
5352

5453
class ListSchema(BaseModel):
5554
items: List[item_class] # type: ignore

pipelex/core/concept.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
from typing import List, Tuple
33

4-
from kajson.class_registry import class_registry
4+
from kajson.kajson_manager import KajsonManager
55
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
66
from typing_extensions import Self
77

@@ -91,10 +91,12 @@ def validate_structure_class_name(cls, value: str) -> str:
9191

9292
@classmethod
9393
def is_valid_structure_class(cls, structure_class_name: str) -> bool:
94-
if class_registry.has_subclass(name=structure_class_name, base_class=StuffContent):
94+
# we get_class_registry directly from KajsonManager instead of pipelex hub to avoid circular import
95+
if KajsonManager.get_class_registry().has_subclass(name=structure_class_name, base_class=StuffContent):
9596
return True
9697
else:
97-
if class_registry.has_class(name=structure_class_name):
98+
# we get_class_registry directly from KajsonManager instead of pipelex hub to avoid circular import
99+
if KajsonManager.get_class_registry().has_class(name=structure_class_name):
98100
log.warning(f"Concept class '{structure_class_name}' is registered but it's not a subclass of StuffContent")
99101
return False
100102

pipelex/core/concept_factory.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from inspect import getsource
22
from typing import Any, Dict, List, Optional, Type, Union
33

4-
from kajson.class_registry import class_registry
54
from pydantic import BaseModel, ConfigDict, Field, ValidationError
65

76
from pipelex.core.concept import Concept
@@ -10,6 +9,7 @@
109
from pipelex.core.domain import SpecialDomain
1110
from pipelex.core.stuff_content import TextContent
1211
from pipelex.exceptions import ConceptFactoryError, StructureClassError
12+
from pipelex.hub import get_class_registry
1313

1414

1515
class ConceptBlueprint(BaseModel):
@@ -180,8 +180,8 @@ def list_native_concepts(cls) -> List[Concept]:
180180

181181
@classmethod
182182
def get_concept_class_source_code(cls, concept_name: str, base_class: Type[Any]) -> str:
183-
if not class_registry.has_class(concept_name):
183+
if not get_class_registry().has_class(concept_name):
184184
raise RuntimeError(f"Class '{concept_name}' not found in registry")
185185

186-
cls = class_registry.get_required_subclass(name=concept_name, base_class=base_class)
186+
cls = get_class_registry().get_required_subclass(name=concept_name, base_class=base_class)
187187
return getsource(cls)

pipelex/core/stuff_factory.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import Any, Dict, List, Optional, Tuple
22

33
import shortuuid
4-
from kajson.class_registry import class_registry
54
from pydantic import BaseModel, Field
65

76
from pipelex.config import get_config
@@ -11,7 +10,7 @@
1110
from pipelex.core.stuff import Stuff, StuffCreationRecord
1211
from pipelex.core.stuff_content import StuffContent, StuffContentInitableFromStr
1312
from pipelex.exceptions import ConceptError, PipelexError
14-
from pipelex.hub import get_required_concept
13+
from pipelex.hub import get_class_registry, get_required_concept
1514

1615

1716
class StuffFactoryError(PipelexError):
@@ -111,7 +110,7 @@ def make_from_str(
111110
raise StuffFactoryError(f"Concept '{concept_str}' does not contain a domain, could not make stuff '{stuff_ref}'")
112111
the_concept = get_required_concept(concept_code=concept_code)
113112
the_subclass_name = the_concept.structure_class_name
114-
the_subclass = class_registry.get_class(name=the_subclass_name) or eval(the_subclass_name)
113+
the_subclass = get_class_registry().get_class(name=the_subclass_name) or eval(the_subclass_name)
115114
if not issubclass(the_subclass, StuffContentInitableFromStr):
116115
raise StuffFactoryError(f"Concept '{concept_code}', subclass '{the_subclass}' is not InitableFromStr")
117116
stuff_content: StuffContent = the_subclass.make_from_str(str_value)
@@ -155,7 +154,7 @@ def combine_stuffs(cls, concept_code: str, stuff_contents: Dict[str, StuffConten
155154
"""
156155
the_concept = get_required_concept(concept_code=concept_code)
157156
the_subclass_name = the_concept.structure_class_name
158-
the_subclass = class_registry.get_required_subclass(name=the_subclass_name, base_class=StuffContent)
157+
the_subclass = get_class_registry().get_required_subclass(name=the_subclass_name, base_class=StuffContent)
159158
the_stuff_content = the_subclass.model_validate(obj=stuff_contents)
160159
return cls.make_stuff(
161160
concept_str=concept_code,

pipelex/hub.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from collections import defaultdict
22
from typing import ClassVar, Dict, List, Optional, Type
33

4+
from kajson.class_registry_abstract import ClassRegistryAbstract
5+
46
from pipelex import log
57
from pipelex.cogt.content_generation.content_generator_protocol import ContentGeneratorProtocol
68
from pipelex.cogt.imgg.imgg_worker_abstract import ImggWorkerAbstract
@@ -43,6 +45,7 @@ def __init__(self):
4345
self._config: Optional[ConfigRoot] = None
4446
self._secrets_provider: Optional[SecretsProviderAbstract] = None
4547
self._template_provider: Optional[TemplateProviderAbstract] = None
48+
self._class_registry: Optional[ClassRegistryAbstract] = None
4649
# cogt
4750
self._llm_models_provider: Optional[LLMModelProviderAbstract] = None
4851
self._llm_deck_provider: Optional[LLMDeckAbstract] = None
@@ -112,6 +115,9 @@ def set_secrets_provider(self, secrets_provider: SecretsProviderAbstract):
112115
def set_template_provider(self, template_provider: TemplateProviderAbstract):
113116
self._template_provider = template_provider
114117

118+
def set_class_registry(self, class_registry: ClassRegistryAbstract):
119+
self._class_registry = class_registry
120+
115121
# cogt
116122

117123
def set_llm_models_provider(self, llm_models_provider: LLMModelProviderAbstract):
@@ -187,6 +193,11 @@ def get_required_template_provider(self) -> TemplateProviderAbstract:
187193
raise RuntimeError("Template provider is not set. You must initialize Pipelex first.")
188194
return self._template_provider
189195

196+
def get_required_class_registry(self) -> ClassRegistryAbstract:
197+
if self._class_registry is None:
198+
raise RuntimeError("ClassRegistry is not initialized")
199+
return self._class_registry
200+
190201
# cogt
191202

192203
def get_required_llm_models_provider(self) -> LLMModelProviderAbstract:
@@ -294,6 +305,10 @@ def get_template(template_name: str) -> str:
294305
return get_template_provider().get_template(template_name=template_name)
295306

296307

308+
def get_class_registry() -> ClassRegistryAbstract:
309+
return get_pipelex_hub().get_required_class_registry()
310+
311+
297312
# cogt
298313

299314

pipelex/libraries/library_manager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from pathlib import Path
33
from typing import Any, ClassVar, Dict, List, Optional, Type
44

5-
from kajson.class_registry import class_registry
65
from kajson.exceptions import ClassRegistryInheritanceError, ClassRegistryNotFoundError
6+
from kajson.kajson_manager import KajsonManager
77
from pydantic import ValidationError
88

99
from pipelex import log
@@ -74,13 +74,13 @@ def teardown(self) -> None:
7474
def load_libraries(self):
7575
log.debug("LibraryManager loading separate libraries")
7676

77-
class_registry.register_classes_in_folder(
77+
KajsonManager.get_class_registry().register_classes_in_folder(
7878
folder_path=LibraryConfig.loaded_pipelines_path,
7979
)
8080
library_paths = [LibraryConfig.loaded_pipelines_path]
8181
if runtime_manager.is_unit_testing:
8282
log.debug("Registering test pipeline structures for unit testing")
83-
class_registry.register_classes_in_folder(
83+
KajsonManager.get_class_registry().register_classes_in_folder(
8484
folder_path=LibraryConfig.test_pipelines_path,
8585
)
8686
library_paths += [LibraryConfig.test_pipelines_path]
@@ -283,7 +283,7 @@ def make_pipe_from_details_dict(
283283
# the factory class name for that specific type of Pipe is the pipe class name with "Factory" suffix
284284
factory_class_name = f"{pipe_class_name}Factory"
285285
try:
286-
pipe_factory: Type[PipeSpecificFactoryProtocol[Any, Any]] = class_registry.get_required_subclass(
286+
pipe_factory: Type[PipeSpecificFactoryProtocol[Any, Any]] = KajsonManager.get_class_registry().get_required_subclass(
287287
name=factory_class_name,
288288
base_class=PipeSpecificFactoryProtocol,
289289
)

pipelex/pipe_operators/pipe_llm.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import List, Optional, Set, Type, cast
22

3-
from kajson.class_registry import class_registry
43
from pydantic import model_validator
54
from typing_extensions import Self, override
65

@@ -34,6 +33,7 @@
3433
StaticValidationErrorType,
3534
)
3635
from pipelex.hub import (
36+
get_class_registry,
3737
get_concept_provider,
3838
get_content_generator,
3939
get_llm_deck,
@@ -118,7 +118,7 @@ def _validate_inputs(self):
118118
# let's check at least that the input is a structured concept
119119
input_concept = concept_provider.get_required_concept(concept_code=concept_code_of_declared_input)
120120
input_concept_class_name = input_concept.structure_class_name
121-
input_concept_class = class_registry.get_required_subclass(name=input_concept_class_name, base_class=StuffContent)
121+
input_concept_class = get_class_registry().get_required_subclass(name=input_concept_class_name, base_class=StuffContent)
122122
if issubclass(input_concept_class, StructuredContent):
123123
continue
124124
explanation = "The input provided for LLM Vision must be an image or a concept that refines image"
@@ -393,7 +393,7 @@ async def _llm_gen_object_stuff_content(
393393
llm_prompt_2_factory: Optional[LLMPromptFactoryAbstract],
394394
content_generator: ContentGeneratorProtocol,
395395
) -> StuffContent:
396-
content_class: Type[StuffContent] = class_registry.get_required_subclass(name=output_class_name, base_class=StuffContent)
396+
content_class: Type[StuffContent] = get_class_registry().get_required_subclass(name=output_class_name, base_class=StuffContent)
397397
task_desc: str
398398
the_content: StuffContent
399399

pipelex/pipe_operators/pipe_llm_prompt.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import ClassVar, List, Optional, Set, cast
22

3-
from kajson.class_registry import class_registry
43
from pydantic import model_validator
54
from typing_extensions import Self, override
65

@@ -22,7 +21,7 @@
2221
PipeRunParamsError,
2322
WorkingMemoryVariableError,
2423
)
25-
from pipelex.hub import get_template
24+
from pipelex.hub import get_class_registry, get_template
2625
from pipelex.pipe_operators.pipe_jinja2 import PipeJinja2, PipeJinja2Output
2726
from pipelex.pipe_operators.pipe_operator import PipeOperator
2827
from pipelex.pipeline.job_metadata import JobCategory, JobMetadata
@@ -219,7 +218,7 @@ async def _run_operator_pipe(
219218
@staticmethod
220219
def get_output_structure_prompt(output_concept: str) -> str:
221220
class_name = Concept.extract_concept_name_from_str(concept_str=output_concept)
222-
output_class = class_registry.get_class(class_name)
221+
output_class = get_class_registry().get_class(class_name)
223222
if not output_class:
224223
return ""
225224

0 commit comments

Comments
 (0)