fabricatio 0.2.13.dev3__cp312-cp312-win_amd64.whl → 0.3.13__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.
Files changed (41) hide show
  1. fabricatio/__init__.py +3 -3
  2. fabricatio/actions/article.py +32 -34
  3. fabricatio/actions/article_rag.py +58 -58
  4. fabricatio/actions/output.py +22 -21
  5. fabricatio/actions/rag.py +4 -3
  6. fabricatio/capabilities/check.py +29 -30
  7. fabricatio/capabilities/correct.py +23 -23
  8. fabricatio/capabilities/extract.py +36 -32
  9. fabricatio/capabilities/rag.py +34 -35
  10. fabricatio/capabilities/rating.py +77 -72
  11. fabricatio/capabilities/review.py +12 -11
  12. fabricatio/capabilities/task.py +4 -5
  13. fabricatio/core.py +22 -24
  14. fabricatio/decorators.py +10 -10
  15. fabricatio/fs/__init__.py +1 -2
  16. fabricatio/journal.py +2 -9
  17. fabricatio/models/action.py +2 -1
  18. fabricatio/models/extra/aricle_rag.py +10 -9
  19. fabricatio/models/extra/article_base.py +5 -6
  20. fabricatio/models/extra/article_main.py +11 -10
  21. fabricatio/models/generic.py +31 -59
  22. fabricatio/models/role.py +5 -3
  23. fabricatio/models/task.py +9 -9
  24. fabricatio/models/tool.py +5 -4
  25. fabricatio/models/usages.py +170 -161
  26. fabricatio/parser.py +2 -2
  27. fabricatio/rust.cp312-win_amd64.pyd +0 -0
  28. fabricatio/rust.pyi +435 -17
  29. fabricatio-0.3.13.data/scripts/tdown.exe +0 -0
  30. fabricatio-0.3.13.data/scripts/ttm.exe +0 -0
  31. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.13.dist-info}/METADATA +1 -3
  32. fabricatio-0.3.13.dist-info/RECORD +63 -0
  33. fabricatio/config.py +0 -430
  34. fabricatio/constants.py +0 -20
  35. fabricatio/models/events.py +0 -120
  36. fabricatio/rust_instances.py +0 -10
  37. fabricatio-0.2.13.dev3.data/scripts/tdown.exe +0 -0
  38. fabricatio-0.2.13.dev3.data/scripts/ttm.exe +0 -0
  39. fabricatio-0.2.13.dev3.dist-info/RECORD +0 -67
  40. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.13.dist-info}/WHEEL +0 -0
  41. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.13.dist-info}/licenses/LICENSE +0 -0
@@ -2,14 +2,14 @@
2
2
 
3
3
  from typing import Dict, Optional, Set, Unpack
4
4
 
5
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
6
+
5
7
  from fabricatio.capabilities.propose import Propose
6
8
  from fabricatio.capabilities.rating import Rating
7
- from fabricatio.config import configs
8
9
  from fabricatio.models.extra.problem import Improvement
9
10
  from fabricatio.models.generic import Display, WithBriefing
10
11
  from fabricatio.models.kwargs_types import ReviewKwargs, ValidateKwargs
11
12
  from fabricatio.models.task import Task
12
- from fabricatio.rust_instances import TEMPLATE_MANAGER
13
13
  from fabricatio.utils import ok
14
14
 
15
15
 
@@ -41,12 +41,12 @@ class Review(Rating, Propose):
41
41
  return await self.review_obj(task, **kwargs)
42
42
 
43
43
  async def review_string(
44
- self,
45
- input_text: str,
46
- topic: str,
47
- criteria: Optional[Set[str]] = None,
48
- rating_manual: Optional[Dict[str, str]] = None,
49
- **kwargs: Unpack[ValidateKwargs[Improvement]],
44
+ self,
45
+ input_text: str,
46
+ topic: str,
47
+ criteria: Optional[Set[str]] = None,
48
+ rating_manual: Optional[Dict[str, str]] = None,
49
+ **kwargs: Unpack[ValidateKwargs[Improvement]],
50
50
  ) -> Optional[Improvement]:
51
51
  """Review a string based on specified topic and criteria.
52
52
 
@@ -70,7 +70,7 @@ class Review(Rating, Propose):
70
70
  # this `default` is the default for the `propose` method
71
71
  default = kwargs.pop("default")
72
72
 
73
- criteria = ok(criteria or (await self.draft_rating_criteria(topic, **kwargs))," No criteria could be use.")
73
+ criteria = ok(criteria or (await self.draft_rating_criteria(topic, **kwargs)), " No criteria could be use.")
74
74
  manual = rating_manual or await self.draft_rating_manual(topic, criteria, **kwargs)
75
75
 
76
76
  if default is not None:
@@ -78,13 +78,14 @@ class Review(Rating, Propose):
78
78
  return await self.propose(
79
79
  Improvement,
80
80
  TEMPLATE_MANAGER.render_template(
81
- configs.templates.review_string_template,
81
+ CONFIG.templates.review_string_template,
82
82
  {"text": input_text, "topic": topic, "criteria_manual": manual},
83
83
  ),
84
84
  **kwargs,
85
85
  )
86
86
 
87
- async def review_obj[M: (Display, WithBriefing)](self, obj: M, **kwargs: Unpack[ReviewKwargs[Improvement]]) -> Optional[Improvement]:
87
+ async def review_obj[M: (Display, WithBriefing)](self, obj: M, **kwargs: Unpack[ReviewKwargs[Improvement]]) -> \
88
+ Optional[Improvement]:
88
89
  """Review an object that implements Display or WithBriefing interface.
89
90
 
90
91
  This method extracts displayable text from the object and performs a review
@@ -4,16 +4,15 @@ from types import CodeType
4
4
  from typing import Any, Dict, List, Optional, Tuple, Unpack
5
5
 
6
6
  import ujson
7
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
7
8
 
8
9
  from fabricatio.capabilities.propose import Propose
9
- from fabricatio.config import configs
10
10
  from fabricatio.journal import logger
11
11
  from fabricatio.models.kwargs_types import ChooseKwargs, ValidateKwargs
12
12
  from fabricatio.models.task import Task
13
13
  from fabricatio.models.tool import Tool, ToolExecutor
14
14
  from fabricatio.models.usages import ToolBoxUsage
15
15
  from fabricatio.parser import JsonCapture, PythonCapture
16
- from fabricatio.rust_instances import TEMPLATE_MANAGER
17
16
 
18
17
 
19
18
  class ProposeTask(Propose):
@@ -67,10 +66,10 @@ class HandleTask(ToolBoxUsage):
67
66
  return None
68
67
 
69
68
  q = TEMPLATE_MANAGER.render_template(
70
- configs.templates.draft_tool_usage_code_template,
69
+ CONFIG.templates.draft_tool_usage_code_template,
71
70
  {
72
- "data_module_name": configs.toolbox.data_module_name,
73
- "tool_module_name": configs.toolbox.tool_module_name,
71
+ "data_module_name": CONFIG.toolbox.data_module_name,
72
+ "tool_module_name": CONFIG.toolbox.tool_module_name,
74
73
  "task": task.briefing,
75
74
  "deps": task.dependencies_prompt,
76
75
  "tools": [{"name": t.name, "briefing": t.briefing} for t in tools],
fabricatio/core.py CHANGED
@@ -2,12 +2,10 @@
2
2
 
3
3
  from typing import Callable, Optional, Self, overload
4
4
 
5
+ from fabricatio.rust import CONFIG, Event
5
6
  from pydantic import BaseModel, ConfigDict, PrivateAttr
6
7
  from pymitter import EventEmitter
7
8
 
8
- from fabricatio.config import configs
9
- from fabricatio.models.events import Event
10
-
11
9
 
12
10
  class Env(BaseModel):
13
11
  """Environment class that manages event handling using EventEmitter."""
@@ -15,9 +13,9 @@ class Env(BaseModel):
15
13
  model_config = ConfigDict(use_attribute_docstrings=True)
16
14
  _ee: EventEmitter = PrivateAttr(
17
15
  default_factory=lambda: EventEmitter(
18
- delimiter=configs.pymitter.delimiter,
19
- new_listener=configs.pymitter.new_listener_event,
20
- max_listeners=configs.pymitter.max_listeners,
16
+ delimiter=CONFIG.pymitter.delimiter,
17
+ new_listener=CONFIG.pymitter.new_listener_event,
18
+ max_listeners=CONFIG.pymitter.max_listeners,
21
19
  wildcard=True,
22
20
  )
23
21
  )
@@ -38,11 +36,11 @@ class Env(BaseModel):
38
36
 
39
37
  @overload
40
38
  def on[**P, R](
41
- self,
42
- event: str | Event,
43
- func: Optional[Callable[P, R]] = None,
44
- /,
45
- ttl: int = -1,
39
+ self,
40
+ event: str | Event,
41
+ func: Optional[Callable[P, R]] = None,
42
+ /,
43
+ ttl: int = -1,
46
44
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
47
45
  """
48
46
  Registers an event listener with a specific function that listens indefinitely or for a specified number of times.
@@ -58,11 +56,11 @@ class Env(BaseModel):
58
56
  ...
59
57
 
60
58
  def on[**P, R](
61
- self,
62
- event: str | Event,
63
- func: Optional[Callable[P, R]] = None,
64
- /,
65
- ttl=-1,
59
+ self,
60
+ event: str | Event,
61
+ func: Optional[Callable[P, R]] = None,
62
+ /,
63
+ ttl=-1,
66
64
  ) -> Callable[[Callable[P, R]], Callable[P, R]] | Self:
67
65
  """Registers an event listener with a specific function that listens indefinitely or for a specified number of times.
68
66
 
@@ -83,8 +81,8 @@ class Env(BaseModel):
83
81
 
84
82
  @overload
85
83
  def once[**P, R](
86
- self,
87
- event: str | Event,
84
+ self,
85
+ event: str | Event,
88
86
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
89
87
  """
90
88
  Registers an event listener that listens only once.
@@ -99,9 +97,9 @@ class Env(BaseModel):
99
97
 
100
98
  @overload
101
99
  def once[**P, R](
102
- self,
103
- event: str | Event,
104
- func: Callable[[Callable[P, R]], Callable[P, R]],
100
+ self,
101
+ event: str | Event,
102
+ func: Callable[[Callable[P, R]], Callable[P, R]],
105
103
  ) -> Self:
106
104
  """
107
105
  Registers an event listener with a specific function that listens only once.
@@ -116,9 +114,9 @@ class Env(BaseModel):
116
114
  ...
117
115
 
118
116
  def once[**P, R](
119
- self,
120
- event: str | Event,
121
- func: Optional[Callable[P, R]] = None,
117
+ self,
118
+ event: str | Event,
119
+ func: Optional[Callable[P, R]] = None,
122
120
  ) -> Callable[[Callable[P, R]], Callable[P, R]] | Self:
123
121
  """Registers an event listener with a specific function that listens only once.
124
122
 
fabricatio/decorators.py CHANGED
@@ -8,7 +8,8 @@ from shutil import which
8
8
  from types import ModuleType
9
9
  from typing import Callable, Coroutine, List, Optional
10
10
 
11
- from fabricatio.config import configs
11
+ from fabricatio.rust import CONFIG
12
+
12
13
  from fabricatio.journal import logger
13
14
 
14
15
 
@@ -24,7 +25,7 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
24
25
  """
25
26
 
26
27
  def _wrapper(
27
- func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
28
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
28
29
  ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
29
30
  if iscoroutinefunction(func):
30
31
 
@@ -48,7 +49,7 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
48
49
 
49
50
 
50
51
  def depend_on_external_cmd[**P, R](
51
- bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
52
+ bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
52
53
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
53
54
  """Decorator to check for the presence of an external command.
54
55
 
@@ -113,7 +114,7 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
113
114
  Returns:
114
115
  Callable: A decorator that wraps the function to confirm before execution.
115
116
  """
116
- if not configs.general.confirm_on_ops:
117
+ if not CONFIG.general.confirm_on_ops:
117
118
  # Skip confirmation if the configuration is set to False
118
119
  return func
119
120
  from questionary import confirm
@@ -123,8 +124,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
123
124
  @wraps(func)
124
125
  async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
125
126
  if await confirm(
126
- f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
127
- instruction="Please input [Yes/No] to proceed (default: Yes):",
127
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
128
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
128
129
  ).ask_async():
129
130
  return await func(*args, **kwargs)
130
131
  logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
@@ -135,8 +136,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
135
136
  @wraps(func)
136
137
  def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
137
138
  if confirm(
138
- f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
139
- instruction="Please input [Yes/No] to proceed (default: Yes):",
139
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
140
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
140
141
  ).ask():
141
142
  return func(*args, **kwargs)
142
143
  logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
@@ -218,7 +219,7 @@ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[
218
219
 
219
220
 
220
221
  def logging_exec_time[**P, R](
221
- func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
222
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
222
223
  ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
223
224
  """Decorator to log the execution time of a function.
224
225
 
@@ -231,7 +232,6 @@ def logging_exec_time[**P, R](
231
232
  from time import time
232
233
 
233
234
  if iscoroutinefunction(func):
234
-
235
235
  @wraps(func)
236
236
  async def _async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
237
237
  start_time = time()
fabricatio/fs/__init__.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """FileSystem manipulation module for Fabricatio."""
2
2
  from importlib.util import find_spec
3
3
 
4
- from fabricatio.config import configs
5
4
  from fabricatio.fs.curd import (
6
5
  absolute_path,
7
6
  copy_file,
@@ -32,5 +31,5 @@ __all__ = [
32
31
  if find_spec("magika"):
33
32
  from magika import Magika
34
33
 
35
- MAGIKA = Magika(model_dir=configs.magika.model_dir)
34
+ MAGIKA = Magika()
36
35
  __all__ += ["MAGIKA"]
fabricatio/journal.py CHANGED
@@ -2,20 +2,13 @@
2
2
 
3
3
  import sys
4
4
 
5
+ from fabricatio.rust import CONFIG
5
6
  from loguru import logger
6
7
  from rich import pretty, traceback
7
8
 
8
- from fabricatio.config import configs
9
-
10
9
  pretty.install()
11
10
  traceback.install()
12
11
  logger.remove()
13
- logger.add(
14
- configs.debug.log_file,
15
- level=configs.debug.log_level,
16
- rotation=f"{configs.debug.rotation} weeks",
17
- retention=f"{configs.debug.retention} weeks",
18
- )
19
- logger.add(sys.stderr, level=configs.debug.log_level)
12
+ logger.add(sys.stderr, level=CONFIG.debug.log_level)
20
13
 
21
14
  __all__ = ["logger"]
@@ -14,12 +14,13 @@ from abc import abstractmethod
14
14
  from asyncio import Queue, create_task
15
15
  from typing import Any, ClassVar, Dict, Self, Sequence, Tuple, Type, Union, final
16
16
 
17
+ from pydantic import Field, PrivateAttr
18
+
17
19
  from fabricatio.journal import logger
18
20
  from fabricatio.models.generic import WithBriefing
19
21
  from fabricatio.models.task import Task
20
22
  from fabricatio.models.usages import ToolBoxUsage
21
23
  from fabricatio.utils import override_kwargs
22
- from pydantic import Field, PrivateAttr
23
24
 
24
25
  OUTPUT_KEY = "task_output"
25
26
 
@@ -5,16 +5,17 @@ from itertools import groupby
5
5
  from pathlib import Path
6
6
  from typing import ClassVar, Dict, List, Optional, Self, Unpack
7
7
 
8
+ from fabricatio.rust import BibManager, blake3_hash, split_into_chunks
9
+ from more_itertools.more import first
10
+ from more_itertools.recipes import flatten, unique
11
+ from pydantic import Field
12
+
8
13
  from fabricatio.fs import safe_text_read
9
14
  from fabricatio.journal import logger
10
15
  from fabricatio.models.extra.rag import MilvusDataBase
11
16
  from fabricatio.models.generic import AsPrompt
12
17
  from fabricatio.models.kwargs_types import ChunkKwargs
13
- from fabricatio.rust import BibManager, blake3_hash, split_into_chunks
14
18
  from fabricatio.utils import ok, wrapp_in_block
15
- from more_itertools.more import first
16
- from more_itertools.recipes import flatten, unique
17
- from pydantic import Field
18
19
 
19
20
 
20
21
  class ArticleChunk(MilvusDataBase):
@@ -67,7 +68,7 @@ class ArticleChunk(MilvusDataBase):
67
68
 
68
69
  @classmethod
69
70
  def from_file[P: str | Path](
70
- cls, path: P | List[P], bib_mgr: BibManager, **kwargs: Unpack[ChunkKwargs]
71
+ cls, path: P | List[P], bib_mgr: BibManager, **kwargs: Unpack[ChunkKwargs]
71
72
  ) -> List[Self]:
72
73
  """Load the article chunks from the file."""
73
74
  if isinstance(path, list):
@@ -84,9 +85,9 @@ class ArticleChunk(MilvusDataBase):
84
85
  title_seg = path.stem.split(" - ").pop()
85
86
 
86
87
  key = (
87
- bib_mgr.get_cite_key_by_title(title_seg)
88
- or bib_mgr.get_cite_key_by_title_fuzzy(title_seg)
89
- or bib_mgr.get_cite_key_fuzzy(path.stem)
88
+ bib_mgr.get_cite_key_by_title(title_seg)
89
+ or bib_mgr.get_cite_key_by_title_fuzzy(title_seg)
90
+ or bib_mgr.get_cite_key_fuzzy(path.stem)
90
91
  )
91
92
  if key is None:
92
93
  logger.warning(f"no cite key found for {path.as_posix()}, skip.")
@@ -178,7 +179,7 @@ class CitationManager(AsPrompt):
178
179
  """Separator for abbreviated citation numbers."""
179
180
 
180
181
  def update_chunks(
181
- self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
182
+ self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
182
183
  ) -> Self:
183
184
  """Update article chunks."""
184
185
  self.article_chunks.clear()
@@ -5,6 +5,9 @@ from enum import StrEnum
5
5
  from pathlib import Path
6
6
  from typing import ClassVar, Generator, List, Optional, Self, Tuple, Type
7
7
 
8
+ from fabricatio.rust import extract_body, inplace_update, split_out_metadata, to_metadata, word_count
9
+ from pydantic import Field
10
+
8
11
  from fabricatio.fs import dump_text, safe_text_read
9
12
  from fabricatio.fs.readers import extract_sections
10
13
  from fabricatio.journal import logger
@@ -17,14 +20,11 @@ from fabricatio.models.generic import (
17
20
  ModelHash,
18
21
  PersistentAble,
19
22
  ProposedUpdateAble,
20
- ResolveUpdateConflict,
21
23
  SketchedAble,
22
24
  Titled,
23
25
  WordCount,
24
26
  )
25
- from fabricatio.rust import extract_body, inplace_update, split_out_metadata, to_metadata, word_count
26
27
  from fabricatio.utils import fallback_kwargs, ok
27
- from pydantic import Field
28
28
 
29
29
  ARTICLE_WRAPPER = "// =-=-=-=-=-=-=-=-=-="
30
30
 
@@ -88,7 +88,6 @@ class ToTypstCode(ArticleMetaData):
88
88
 
89
89
 
90
90
  class ArticleOutlineBase(
91
- ResolveUpdateConflict,
92
91
  ProposedUpdateAble,
93
92
  PersistentAble,
94
93
  ModelHash,
@@ -277,7 +276,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
277
276
  )
278
277
 
279
278
  def iter_dfs_rev(
280
- self,
279
+ self,
281
280
  ) -> Generator[ArticleOutlineBase, None, None]:
282
281
  """Performs a depth-first search (DFS) through the article structure in reverse order.
283
282
 
@@ -415,7 +414,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
415
414
  return self
416
415
 
417
416
  @classmethod
418
- def from_article_file(cls, file: str | Path, title: str) -> Self:
417
+ def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str) -> S:
419
418
  """Load article from file."""
420
419
  file = Path(file)
421
420
  string = safe_text_read(file)
@@ -2,6 +2,15 @@
2
2
 
3
3
  from typing import ClassVar, Dict, Generator, List, Self, Tuple, Type, override
4
4
 
5
+ from fabricatio.rust import (
6
+ convert_all_block_tex,
7
+ convert_all_inline_tex,
8
+ fix_misplaced_labels,
9
+ split_out_metadata,
10
+ word_count,
11
+ )
12
+ from pydantic import Field, NonNegativeInt
13
+
5
14
  from fabricatio.decorators import precheck_package
6
15
  from fabricatio.journal import logger
7
16
  from fabricatio.models.extra.article_base import (
@@ -17,14 +26,6 @@ from fabricatio.models.extra.article_outline import (
17
26
  ArticleSubsectionOutline,
18
27
  )
19
28
  from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
20
- from fabricatio.rust import (
21
- convert_all_block_tex,
22
- convert_all_inline_tex,
23
- fix_misplaced_labels,
24
- split_out_metadata,
25
- word_count,
26
- )
27
- from pydantic import Field, NonNegativeInt
28
29
 
29
30
  PARAGRAPH_SEP = "\n\n// - - -\n\n"
30
31
 
@@ -81,8 +82,8 @@ class ArticleSubsection(SubSectionBase):
81
82
  if len(self.paragraphs) == 0:
82
83
  summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
83
84
  if (
84
- abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
85
- > self._max_word_count_deviation
85
+ abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
86
+ > self._max_word_count_deviation
86
87
  ):
87
88
  summary += f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
88
89