fabricatio 0.2.7.dev2__cp312-cp312-win_amd64.whl → 0.2.7.dev4__cp312-cp312-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.
@@ -0,0 +1,293 @@
1
+ """ArticleBase and ArticleSubsection classes for managing hierarchical document components."""
2
+
3
+ from itertools import chain
4
+ from typing import Generator, List, Self, Tuple
5
+
6
+ from fabricatio.journal import logger
7
+ from fabricatio.models.extra.article_base import (
8
+ ArticleBase,
9
+ ArticleMainBase,
10
+ ArticleRef,
11
+ ChapterBase,
12
+ SectionBase,
13
+ SubSectionBase,
14
+ )
15
+ from fabricatio.models.extra.article_outline import (
16
+ ArticleOutline,
17
+ )
18
+ from fabricatio.models.generic import CensoredAble, Display, PersistentAble, WithRef
19
+ from fabricatio.models.utils import ok
20
+
21
+
22
+ class Paragraph(CensoredAble):
23
+ """Structured academic paragraph blueprint for controlled content generation."""
24
+
25
+ description: str
26
+ """Functional summary of the paragraph's role in document structure."""
27
+
28
+ writing_aim: List[str]
29
+ """Specific communicative objectives for this paragraph's content."""
30
+
31
+ sentences: List[str]
32
+ """List of sentences forming the paragraph's content."""
33
+
34
+
35
+ class ArticleSubsection(ArticleMainBase, SubSectionBase):
36
+ """Atomic argumentative unit with technical specificity."""
37
+
38
+ paragraphs: List[Paragraph]
39
+ """List of Paragraph objects containing the content of the subsection."""
40
+
41
+ def resolve_update_error(self, other: Self) -> str:
42
+ """Resolve update errors in the article outline."""
43
+ if self.title != other.title:
44
+ return f"Title `{other.title}` mismatched, expected `{self.title}`. "
45
+ return ""
46
+
47
+ def _update_from_inner(self, other: Self) -> Self:
48
+ """Updates the current instance with the attributes of another instance."""
49
+ logger.debug(f"Updating SubSection {self.title}")
50
+ self.paragraphs = other.paragraphs
51
+ return self
52
+
53
+ def to_typst_code(self) -> str:
54
+ """Converts the component into a Typst code snippet for rendering.
55
+
56
+ Returns:
57
+ str: Typst code snippet for rendering.
58
+ """
59
+ return f"=== {self.title}\n" + "\n\n".join("".join(p.sentences) for p in self.paragraphs)
60
+
61
+
62
+ class ArticleSection(ArticleMainBase, SectionBase[ArticleSubsection]):
63
+ """Atomic argumentative unit with high-level specificity."""
64
+
65
+ def resolve_update_error(self, other: Self) -> str:
66
+ """Resolve update errors in the article outline."""
67
+ if (s_len := len(self.subsections)) == 0:
68
+ return ""
69
+
70
+ if s_len != len(other.subsections):
71
+ return f"Subsections length mismatched, expected {len(self.subsections)}, got {len(other.subsections)}"
72
+
73
+ sub_sec_err_seq = [
74
+ out for s, o in zip(self.subsections, other.subsections, strict=True) if (out := s.resolve_update_error(o))
75
+ ]
76
+
77
+ if sub_sec_err_seq:
78
+ return "\n".join(sub_sec_err_seq)
79
+ return ""
80
+
81
+ def _update_from_inner(self, other: Self) -> Self:
82
+ """Updates the current instance with the attributes of another instance."""
83
+ if len(self.subsections) == 0:
84
+ self.subsections = other.subsections
85
+ return self
86
+
87
+ for self_subsec, other_subsec in zip(self.subsections, other.subsections, strict=True):
88
+ self_subsec.update_from(other_subsec)
89
+ return self
90
+
91
+ def to_typst_code(self) -> str:
92
+ """Converts the section into a Typst formatted code snippet.
93
+
94
+ Returns:
95
+ str: The formatted Typst code snippet.
96
+ """
97
+ return f"== {self.title}\n" + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
98
+
99
+
100
+ class ArticleChapter(ArticleMainBase, ChapterBase[ArticleSection]):
101
+ """Thematic progression implementing research function."""
102
+
103
+ def resolve_update_error(self, other: Self) -> str:
104
+ """Resolve update errors in the article outline."""
105
+ if (s_len := len(self.sections)) == 0:
106
+ return ""
107
+
108
+ if s_len != len(other.sections):
109
+ return f"Sections length mismatched, expected {len(self.sections)}, got {len(other.sections)}"
110
+ sec_err_seq = [
111
+ out for s, o in zip(self.sections, other.sections, strict=True) if (out := s.resolve_update_error(o))
112
+ ]
113
+ if sec_err_seq:
114
+ return "\n".join(sec_err_seq)
115
+ return ""
116
+
117
+ def _update_from_inner(self, other: Self) -> Self:
118
+ """Updates the current instance with the attributes of another instance."""
119
+ if len(self.sections) == 0:
120
+ self.sections = other.sections
121
+ return self
122
+
123
+ for self_sec, other_sec in zip(self.sections, other.sections, strict=True):
124
+ self_sec.update_from(other_sec)
125
+ return self
126
+
127
+ def to_typst_code(self) -> str:
128
+ """Converts the chapter into a Typst formatted code snippet for rendering."""
129
+ return f"= {self.title}\n" + "\n\n".join(sec.to_typst_code() for sec in self.sections)
130
+
131
+
132
+ class Article(Display, CensoredAble, WithRef[ArticleOutline], PersistentAble, ArticleBase[ArticleChapter]):
133
+ """Represents a complete academic paper specification, incorporating validation constraints.
134
+
135
+ This class integrates display, censorship processing, article structure referencing, and persistence capabilities,
136
+ aiming to provide a comprehensive model for academic papers.
137
+ """
138
+
139
+ abstract: str
140
+ """Contains a summary of the academic paper."""
141
+
142
+ title: str
143
+ """Represents the title of the academic paper."""
144
+
145
+ language: str
146
+ """Written language of the article. SHALL be aligned to the language of the article outline provided."""
147
+
148
+ def finalized_dump(self) -> str:
149
+ """Exports the article in `typst` format.
150
+
151
+ Returns:
152
+ str: Strictly formatted outline with typst formatting.
153
+ """
154
+ return "\n\n".join(c.to_typst_code() for c in self.chapters)
155
+
156
+ @classmethod
157
+ def from_outline(cls, outline: ArticleOutline) -> "Article":
158
+ """Generates an article from the given outline.
159
+
160
+ Args:
161
+ outline (ArticleOutline): The outline to generate the article from.
162
+
163
+ Returns:
164
+ Article: The generated article.
165
+ """
166
+ # Set the title from the outline
167
+ article = Article(**outline.model_dump(include={"title", "abstract"}), chapters=[])
168
+
169
+ for chapter in outline.chapters:
170
+ # Create a new chapter
171
+ article_chapter = ArticleChapter(
172
+ sections=[],
173
+ **chapter.model_dump(exclude={"sections"}),
174
+ )
175
+ for section in chapter.sections:
176
+ # Create a new section
177
+ article_section = ArticleSection(
178
+ subsections=[],
179
+ **section.model_dump(exclude={"subsections"}),
180
+ )
181
+ for subsection in section.subsections:
182
+ # Create a new subsection
183
+ article_subsection = ArticleSubsection(
184
+ paragraphs=[],
185
+ **subsection.model_dump(),
186
+ )
187
+ article_section.subsections.append(article_subsection)
188
+ article_chapter.sections.append(article_section)
189
+ article.chapters.append(article_chapter)
190
+ return article
191
+
192
+ def iter_dfs(self) -> Generator[ArticleMainBase, None, None]:
193
+ """Performs a depth-first search (DFS) through the article structure.
194
+
195
+ Returns:
196
+ Generator[ArticleMainBase]: Each component in the article structure.
197
+ """
198
+ for chap in self.chapters:
199
+ for sec in chap.sections:
200
+ yield from sec.subsections
201
+ yield sec
202
+ yield chap
203
+
204
+ def deref(self, ref: ArticleRef) -> ArticleMainBase:
205
+ """Resolves a reference to the corresponding section or subsection in the article.
206
+
207
+ Args:
208
+ ref (ArticleRef): The reference to resolve.
209
+
210
+ Returns:
211
+ ArticleMainBase: The corresponding section or subsection.
212
+ """
213
+ return ok(ref.deref(self), f"{ref} not found in {self.title}")
214
+
215
+ def gather_dependencies(self, article: ArticleMainBase) -> List[ArticleMainBase]:
216
+ """Gathers dependencies for all sections and subsections in the article.
217
+
218
+ This method should be called after the article is fully constructed.
219
+ """
220
+ depends = [self.deref(a) for a in article.depend_on]
221
+
222
+ supports = []
223
+ for a in self.iter_dfs():
224
+ if article in {self.deref(b) for b in a.support_to}:
225
+ supports.append(a)
226
+
227
+ return list(set(depends + supports))
228
+
229
+ def gather_dependencies_recursive(self, article: ArticleMainBase) -> List[ArticleMainBase]:
230
+ """Gathers all dependencies recursively for the given article.
231
+
232
+ Args:
233
+ article (ArticleMainBase): The article to gather dependencies for.
234
+
235
+ Returns:
236
+ List[ArticleBase]: A list of all dependencies for the given article.
237
+ """
238
+ q = self.gather_dependencies(article)
239
+
240
+ deps = []
241
+ while q:
242
+ a = q.pop()
243
+ deps.extend(self.gather_dependencies(a))
244
+
245
+ deps = list(
246
+ chain(
247
+ filter(lambda x: isinstance(x, ArticleChapter), deps),
248
+ filter(lambda x: isinstance(x, ArticleSection), deps),
249
+ filter(lambda x: isinstance(x, ArticleSubsection), deps),
250
+ )
251
+ )
252
+
253
+ # Initialize result containers
254
+ formatted_code = ""
255
+ processed_components = []
256
+
257
+ # Process all dependencies
258
+ while deps:
259
+ component = deps.pop()
260
+ # Skip duplicates
261
+ if (component_code := component.to_typst_code()) in formatted_code:
262
+ continue
263
+
264
+ # Add this component
265
+ formatted_code += component_code
266
+ processed_components.append(component)
267
+
268
+ return processed_components
269
+
270
+ def iter_dfs_with_deps(
271
+ self, chapter: bool = True, section: bool = True, subsection: bool = True
272
+ ) -> Generator[Tuple[ArticleMainBase, List[ArticleMainBase]], None, None]:
273
+ """Iterates through the article in a depth-first manner, yielding each component and its dependencies.
274
+
275
+ Args:
276
+ chapter (bool, optional): Whether to include chapter components. Defaults to True.
277
+ section (bool, optional): Whether to include section components. Defaults to True.
278
+ subsection (bool, optional): Whether to include subsection components. Defaults to True.
279
+
280
+ Yields:
281
+ Tuple[ArticleBase, List[ArticleBase]]: Each component and its dependencies.
282
+ """
283
+ if all((not chapter, not section, not subsection)):
284
+ raise ValueError("At least one of chapter, section, or subsection must be True.")
285
+
286
+ for component in self.iter_dfs():
287
+ if not chapter and isinstance(component, ArticleChapter):
288
+ continue
289
+ if not section and isinstance(component, ArticleSection):
290
+ continue
291
+ if not subsection and isinstance(component, ArticleSubsection):
292
+ continue
293
+ yield component, (self.gather_dependencies_recursive(component))
@@ -0,0 +1,181 @@
1
+ """A module containing the ArticleOutline class, which represents the outline of an academic paper."""
2
+
3
+ from typing import Generator, List, Optional, Tuple, Union
4
+
5
+ import regex
6
+ from fabricatio.models.extra.article_base import (
7
+ ArticleBase,
8
+ ArticleOutlineBase,
9
+ ChapterBase,
10
+ SectionBase,
11
+ SubSectionBase,
12
+ )
13
+ from fabricatio.models.extra.article_proposal import ArticleProposal
14
+ from fabricatio.models.generic import CensoredAble, Display, PersistentAble, WithRef
15
+ from fabricatio.models.utils import ok
16
+
17
+
18
+ class ArticleSubsectionOutline(ArticleOutlineBase, SubSectionBase):
19
+ """Atomic research component specification for academic paper generation."""
20
+
21
+
22
+ class ArticleSectionOutline(ArticleOutlineBase, SectionBase[ArticleSubsectionOutline]):
23
+ """A slightly more detailed research component specification for academic paper generation."""
24
+
25
+
26
+ class ArticleChapterOutline(ArticleOutlineBase, ChapterBase[ArticleSectionOutline]):
27
+ """Macro-structural unit implementing standard academic paper organization."""
28
+
29
+
30
+ class ArticleOutline(
31
+ Display,
32
+ CensoredAble,
33
+ WithRef[ArticleProposal],
34
+ PersistentAble,
35
+ ArticleBase[ArticleChapterOutline],
36
+ ):
37
+ """Complete academic paper blueprint with hierarchical validation."""
38
+
39
+ abstract: str
40
+ """The abstract is a concise summary of the academic paper's main findings."""
41
+
42
+ prospect: str
43
+ """Consolidated research statement with four pillars:
44
+ 1. Problem Identification: Current limitations
45
+ 2. Methodological Response: Technical approach
46
+ 3. Empirical Validation: Evaluation strategy
47
+ 4. Scholarly Impact: Field contributions
48
+ """
49
+
50
+ title: str
51
+ """Title of the academic paper."""
52
+
53
+ language: str
54
+ """Written language of the article. SHALL be aligned to the language of the article proposal provided."""
55
+
56
+ def finalized_dump(self) -> str:
57
+ """Generates standardized hierarchical markup for academic publishing systems.
58
+
59
+ Implements ACL 2024 outline conventions with four-level structure:
60
+ = Chapter Title (Level 1)
61
+ == Section Title (Level 2)
62
+ === Subsection Title (Level 3)
63
+ ==== Subsubsection Title (Level 4)
64
+
65
+ Returns:
66
+ str: Strictly formatted outline with academic sectioning
67
+
68
+ Example:
69
+ = Methodology
70
+ == Neural Architecture Search Framework
71
+ === Differentiable Search Space
72
+ ==== Constrained Optimization Parameters
73
+ === Implementation Details
74
+ == Evaluation Protocol
75
+ """
76
+ lines: List[str] = []
77
+ for i, chapter in enumerate(self.chapters, 1):
78
+ lines.append(f"= Chapter {i}: {chapter.title}")
79
+ for j, section in enumerate(chapter.sections, 1):
80
+ lines.append(f"== {i}.{j} {section.title}")
81
+ for k, subsection in enumerate(section.subsections, 1):
82
+ lines.append(f"=== {i}.{j}.{k} {subsection.title}")
83
+ return "\n".join(lines)
84
+
85
+ def iter_dfs(self) -> Generator[ArticleOutlineBase, None, None]:
86
+ """Iterates through the article outline in a depth-first manner.
87
+
88
+ Returns:
89
+ ArticleOutlineBase: Each component in the article outline.
90
+ """
91
+ for chapter in self.chapters:
92
+ for section in chapter.sections:
93
+ yield from section.subsections
94
+ yield section
95
+ yield chapter
96
+
97
+ def resolve_ref_error(self) -> str:
98
+ """Resolve reference errors in the article outline.
99
+
100
+ Returns:
101
+ str: Error message indicating reference errors in the article outline.
102
+
103
+ Notes:
104
+ This function is designed to find all invalid `ArticleRef` objs in `depend_on` and `support_to` fields, which will be added to the final error summary.
105
+ """
106
+ summary = ""
107
+ for component in self.iter_dfs():
108
+ for ref in component.depend_on:
109
+ if not ref.deref(self):
110
+ summary += f"Invalid internal reference in {component.__class__.__name__} titled `{component.title}` at `depend_on` field, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
111
+ for ref in component.support_to:
112
+ if not ref.deref(self):
113
+ summary += f"Invalid internal reference in {component.__class__.__name__} titled `{component.title}` at `support_to` field, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
114
+
115
+ return summary
116
+
117
+ @classmethod
118
+ def from_typst_code(
119
+ cls, typst_code: str, title: str = "", article_language: str = "en", prospect: str = "", abstract: str = ""
120
+ ) -> "ArticleOutline":
121
+ """Parses a Typst code string and creates an ArticleOutline instance."""
122
+ self = cls(language=article_language, prospect=prospect, abstract=abstract, chapters=[], title=title)
123
+ stack = [self] # 根节点为ArticleOutline实例
124
+
125
+ for line in typst_code.splitlines():
126
+ parsed = cls._parse_line(line)
127
+ if not parsed:
128
+ continue
129
+ level, title = parsed
130
+ cls._adjust_stack(stack, level)
131
+ parent = stack[-1]
132
+ component = cls._create_component(level, title)
133
+ cls._add_to_parent(parent, component, level)
134
+ stack.append(component)
135
+
136
+ return self
137
+
138
+ @classmethod
139
+ def _parse_line(cls, line: str) -> Optional[Tuple[int, str]]:
140
+ stripped = line.strip()
141
+ if not stripped.startswith("="):
142
+ return None
143
+ match = regex.match(r"^(\=+)(.*)", stripped)
144
+ if not match:
145
+ return None
146
+ eqs, title_part = match.groups()
147
+ return len(eqs), title_part.strip()
148
+
149
+ @classmethod
150
+ def _adjust_stack(cls, stack: List[object], target_level: int) -> None:
151
+ while len(stack) > target_level:
152
+ stack.pop()
153
+
154
+ @classmethod
155
+ def _create_component(cls, level: int, title: str) -> ArticleOutlineBase:
156
+ default_kwargs = {
157
+ "writing_aim": [],
158
+ "depend_on": [],
159
+ "support_to": [],
160
+ "description": [],
161
+ }
162
+ component_map = {
163
+ 1: lambda: ArticleChapterOutline(title=title, sections=[], **default_kwargs),
164
+ 2: lambda: ArticleSectionOutline(title=title, subsections=[], **default_kwargs),
165
+ 3: lambda: ArticleSubsectionOutline(title=title, **default_kwargs),
166
+ }
167
+ return ok(component_map.get(level, lambda: None)(), "Invalid level")
168
+
169
+ @classmethod
170
+ def _add_to_parent(
171
+ cls,
172
+ parent: Union["ArticleOutline", ArticleChapterOutline, ArticleSectionOutline],
173
+ component: ArticleOutlineBase,
174
+ level: int,
175
+ ) -> None:
176
+ if level == 1 and isinstance(component, ArticleChapterOutline):
177
+ parent.chapters.append(component)
178
+ elif level == 2 and isinstance(component, ArticleSectionOutline): # noqa: PLR2004
179
+ parent.sections.append(component)
180
+ elif level == 3 and isinstance(component, ArticleSubsectionOutline): # noqa: PLR2004
181
+ parent.subsections.append(component)
@@ -0,0 +1,35 @@
1
+ """A structured proposal for academic paper development with core research elements."""
2
+
3
+ from typing import Dict, List
4
+
5
+ from fabricatio.models.generic import AsPrompt, CensoredAble, Display, PersistentAble, WithRef
6
+
7
+
8
+ class ArticleProposal(CensoredAble, Display, WithRef[str], AsPrompt, PersistentAble):
9
+ """Structured proposal for academic paper development with core research elements.
10
+
11
+ Guides LLM in generating comprehensive research proposals with clearly defined components.
12
+ """
13
+
14
+ technical_approaches: List[str]
15
+ """Technical approaches"""
16
+
17
+ research_methods: List[str]
18
+ """Methodological components (list of techniques/tools).
19
+ Example: ['Differentiable architecture search', 'Transformer-based search space', 'Multi-lingual perplexity evaluation']"""
20
+
21
+ research_aim: List[str]
22
+ """Primary research objectives (list of 2-4 measurable goals).
23
+ Example: ['Develop parameter-efficient NAS framework', 'Establish cross-lingual architecture transfer metrics']"""
24
+
25
+ focused_problem: List[str]
26
+ """Specific research problem(s) or question(s) addressed (list of 1-3 concise statements).
27
+ Example: ['NAS computational overhead in low-resource settings', 'Architecture transferability across language pairs']"""
28
+
29
+ title: str
30
+ """Paper title in academic style (Title Case, 8-15 words). Example: 'Exploring Neural Architecture Search for Low-Resource Machine Translation'"""
31
+ language: str
32
+ """Written language of the article. SHALL be aligned to the language of the article briefing provided."""
33
+
34
+ def _as_prompt_inner(self) -> Dict[str, str]:
35
+ return {"ArticleBriefing": self.referenced, "ArticleProposal": self.display()}
@@ -23,6 +23,7 @@ from pydantic import (
23
23
  PrivateAttr,
24
24
  SecretStr,
25
25
  )
26
+ from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
26
27
 
27
28
 
28
29
  class Base(BaseModel):
@@ -103,6 +104,41 @@ class WithRef[T](Base):
103
104
  return self
104
105
 
105
106
 
107
+ class PersistentAble(Base):
108
+ """Class that provides a method to persist the object."""
109
+
110
+ def persist(self, path: str | Path) -> Self:
111
+ """Persist the object to a file.
112
+
113
+ Args:
114
+ path (str | Path): The path to save the object.
115
+
116
+ Returns:
117
+ Self: The current instance of the object.
118
+ """
119
+ p = Path(path)
120
+ out = self.model_dump_json()
121
+ if p.is_dir():
122
+ p.joinpath(f"{self.__class__.__name__}_{blake3_hash(out.encode())[:6]}.json").write_text(
123
+ out, encoding="utf-8"
124
+ )
125
+ return self
126
+ p.mkdir(exist_ok=True, parents=True)
127
+ p.write_text(out, encoding="utf-8")
128
+ return self
129
+
130
+ def from_persistent(self, path: str | Path) -> Self:
131
+ """Load the object from a file.
132
+
133
+ Args:
134
+ path (str | Path): The path to load the object from.
135
+
136
+ Returns:
137
+ Self: The current instance of the object.
138
+ """
139
+ return self.model_validate_json(Path(path).read_text(encoding="utf-8"))
140
+
141
+
106
142
  class WithBriefing(Named, Described):
107
143
  """Class that provides a briefing based on the name and description."""
108
144
 
@@ -137,6 +173,23 @@ class WithBriefing(Named, Described):
137
173
  raise TypeError(f"{system_msg_like} is not a dict or str")
138
174
 
139
175
 
176
+ class ReverseGenerate(GenerateJsonSchema):
177
+ """Class that provides a reverse JSON schema of the model."""
178
+
179
+ def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any:
180
+ if isinstance(value, dict):
181
+ sorted_dict: dict[str, JsonSchemaValue] = {}
182
+ # Reverse all keys regardless of parent_key
183
+ keys = reversed(value.keys())
184
+ for key in keys:
185
+ sorted_dict[key] = self._sort_recursive(value[key], parent_key=key)
186
+ return sorted_dict
187
+ if isinstance(value, list):
188
+ # Reverse list order and process each item
189
+ return [self._sort_recursive(item, parent_key) for item in reversed(value)]
190
+ return value
191
+
192
+
140
193
  class WithFormatedJsonSchema(Base):
141
194
  """Class that provides a formatted JSON schema of the model."""
142
195
 
@@ -148,8 +201,8 @@ class WithFormatedJsonSchema(Base):
148
201
  str: The JSON schema of the model in a formatted string.
149
202
  """
150
203
  return orjson.dumps(
151
- cls.model_json_schema(),
152
- option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS,
204
+ cls.model_json_schema(schema_generator=ReverseGenerate),
205
+ option=orjson.OPT_INDENT_2,
153
206
  ).decode()
154
207
 
155
208
 
@@ -11,11 +11,11 @@ class Message(BaseModel):
11
11
  """A class representing a message."""
12
12
 
13
13
  model_config = ConfigDict(use_attribute_docstrings=True)
14
- role: Literal["user", "system", "assistant"] = Field(default="user")
14
+ role: Literal["user", "system", "assistant"]
15
15
  """
16
16
  Who is sending the message.
17
17
  """
18
- content: str = Field(default="")
18
+ content: str
19
19
  """
20
20
  The content of the message.
21
21
  """
@@ -172,12 +172,14 @@ def override_kwargs[T](kwargs: Dict[str, T], **overrides) -> Dict[str, T]:
172
172
  kwargs.update({k: v for k, v in overrides.items() if v is not None})
173
173
  return kwargs
174
174
 
175
+
175
176
  def fallback_kwargs[T](kwargs: Dict[str, T], **overrides) -> Dict[str, T]:
176
177
  """Fallback the values in kwargs with the provided overrides."""
177
- kwargs.update({k: v for k, v in overrides.items() if k not in kwargs})
178
+ kwargs.update({k: v for k, v in overrides.items() if k not in kwargs})
178
179
  return kwargs
179
180
 
180
- def ok[T](val: Optional[T], msg:str="Value is None") -> T:
181
+
182
+ def ok[T](val: Optional[T], msg: str = "Value is None") -> T:
181
183
  """Check if a value is None and raise a ValueError with the provided message if it is.
182
184
 
183
185
  Args: