flock-core 0.4.0b45__py3-none-any.whl → 0.4.0b48__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (35) hide show
  1. flock/core/flock.py +105 -61
  2. flock/core/flock_registry.py +45 -38
  3. flock/core/util/spliter.py +4 -0
  4. flock/evaluators/__init__.py +1 -0
  5. flock/evaluators/declarative/__init__.py +1 -0
  6. flock/modules/__init__.py +1 -0
  7. flock/modules/assertion/__init__.py +1 -0
  8. flock/modules/callback/__init__.py +1 -0
  9. flock/modules/memory/__init__.py +1 -0
  10. flock/modules/output/__init__.py +1 -0
  11. flock/modules/performance/__init__.py +1 -0
  12. flock/modules/zep/__init__.py +1 -0
  13. flock/tools/__init__.py +188 -0
  14. flock/{core/tools → tools}/azure_tools.py +284 -0
  15. flock/tools/code_tools.py +56 -0
  16. flock/tools/file_tools.py +140 -0
  17. flock/{core/tools/dev_tools/github.py → tools/github_tools.py} +3 -3
  18. flock/{core/tools → tools}/markdown_tools.py +14 -4
  19. flock/tools/system_tools.py +9 -0
  20. flock/{core/tools/llm_tools.py → tools/text_tools.py} +47 -25
  21. flock/tools/web_tools.py +90 -0
  22. flock/{core/tools → tools}/zendesk_tools.py +6 -6
  23. flock/webapp/app/config.py +1 -1
  24. flock/webapp/app/main.py +109 -16
  25. flock/webapp/static/css/chat.css +22 -7
  26. flock/webapp/templates/base.html +1 -1
  27. flock/webapp/templates/chat.html +7 -1
  28. flock/webapp/templates/partials/_chat_container.html +7 -1
  29. flock/workflow/activities.py +1 -0
  30. {flock_core-0.4.0b45.dist-info → flock_core-0.4.0b48.dist-info}/METADATA +24 -13
  31. {flock_core-0.4.0b45.dist-info → flock_core-0.4.0b48.dist-info}/RECORD +34 -21
  32. flock/core/tools/basic_tools.py +0 -317
  33. {flock_core-0.4.0b45.dist-info → flock_core-0.4.0b48.dist-info}/WHEEL +0 -0
  34. {flock_core-0.4.0b45.dist-info → flock_core-0.4.0b48.dist-info}/entry_points.txt +0 -0
  35. {flock_core-0.4.0b45.dist-info → flock_core-0.4.0b48.dist-info}/licenses/LICENSE +0 -0
flock/core/flock.py CHANGED
@@ -4,6 +4,7 @@
4
4
  from __future__ import annotations # Ensure forward references work
5
5
 
6
6
  import asyncio
7
+ import concurrent.futures # Added import
7
8
  import os
8
9
  import uuid
9
10
  from collections.abc import Callable, Sequence
@@ -115,6 +116,19 @@ class Flock(BaseModel, Serializable):
115
116
  default=True,
116
117
  description="If True (default) and enable_temporal=True, start a temporary in-process worker for development/testing convenience. Set to False when using dedicated workers.",
117
118
  )
119
+
120
+ benchmark_agent_name: str | None = Field(
121
+ default=None,
122
+ description="The name of the agent to use for the benchmark.",
123
+ )
124
+ benchmark_eval_field: str | None = Field(
125
+ default=None,
126
+ description="The output field to use for the benchmark.",
127
+ )
128
+ benchmark_input_field: str | None = Field(
129
+ default=None,
130
+ description="The input field to use for the benchmark.",
131
+ )
118
132
  # Internal agent storage - not part of the Pydantic model for direct serialization
119
133
  # Marked with underscore to indicate it's managed internally and accessed via property
120
134
  _agents: dict[str, FlockAgent]
@@ -190,6 +204,8 @@ class Flock(BaseModel, Serializable):
190
204
  # Ensure session ID exists in baggage
191
205
  self._ensure_session_id()
192
206
 
207
+ FlockRegistry.discover_and_register_components()
208
+
193
209
  logger.info(
194
210
  "Flock instance initialized",
195
211
  name=self.name,
@@ -197,6 +213,46 @@ class Flock(BaseModel, Serializable):
197
213
  enable_temporal=self.enable_temporal,
198
214
  )
199
215
 
216
+ def prepare_benchmark(self, agent: FlockAgent | str | None = None, input_field: str | None = None, eval_field: str | None = None):
217
+ """Prepare a benchmark for the Flock instance."""
218
+ from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
219
+
220
+ logger.info(f"Preparing benchmark for Flock instance '{self.name}' with agent '{agent}'.")
221
+
222
+ name = agent.name if isinstance(agent, ConcreteFlockAgent) else agent
223
+
224
+ if self._agents.get(name) is None:
225
+ raise ValueError(f"Agent '{name}' not found in Flock instance '{self.name}'.")
226
+
227
+ self.benchmark_agent_name = name
228
+ self.benchmark_eval_field = eval_field
229
+ self.benchmark_input_field = input_field
230
+
231
+
232
+
233
+ def inspect(self):
234
+ """Inspect the Flock instance."""
235
+ logger.info(f"Inspecting Flock instance '{self.name}' with start agent '{self.benchmark_agent_name}' and input '{input}'.")
236
+
237
+ async def run(input: dict[str, Any])-> dict[str, Any]:
238
+ """Inspect the Flock instance."""
239
+ logger.info(f"Inspecting Flock instance '{self.name}' with start agent '{self.benchmark_agent_name}' and input '{input}'.")
240
+ msg_content = input.get("messages")[0].get("content")
241
+
242
+ agent_input = {
243
+ self.benchmark_input_field: msg_content
244
+ }
245
+
246
+ result = await self.run_async(start_agent=self.benchmark_agent_name, input=agent_input, box_result=False)
247
+
248
+ agent_output = result.get(self.benchmark_eval_field, "No answer found")
249
+
250
+ return {
251
+ "output": agent_output,
252
+ }
253
+
254
+ return run
255
+
200
256
  def _configure_logging(self, enable_logging_config: bool | list[str]):
201
257
  """Configure logging levels based on the enable_logging flag."""
202
258
  is_enabled_globally = False
@@ -302,45 +358,37 @@ class Flock(BaseModel, Serializable):
302
358
  agents: list[FlockAgent] | None = None,
303
359
  ) -> Box | dict:
304
360
  """Entry point for running an agent system synchronously."""
361
+ # Prepare the coroutine that needs to be run
362
+ coro = self.run_async(
363
+ start_agent=start_agent,
364
+ input=input,
365
+ context=context,
366
+ run_id=run_id,
367
+ box_result=box_result, # run_async handles boxing
368
+ agents=agents,
369
+ )
370
+
305
371
  try:
372
+ # Check if an event loop is already running in the current thread
306
373
  loop = asyncio.get_running_loop()
307
- if loop.is_closed():
308
- raise RuntimeError("Event loop is closed")
309
- except RuntimeError: # No running loop
310
- loop = asyncio.new_event_loop()
311
- asyncio.set_event_loop(loop)
312
-
313
- # Ensure the loop runs the task and handles closure if we created it
314
- if asyncio.get_event_loop() is loop and not loop.is_running():
315
- result = loop.run_until_complete(
316
- self.run_async(
317
- start_agent=start_agent,
318
- input=input,
319
- context=context,
320
- run_id=run_id,
321
- box_result=box_result,
322
- agents=agents,
323
- )
324
- )
325
- return result
374
+ except RuntimeError:
375
+ # No event loop is running in the current thread.
376
+ # We can safely use asyncio.run() to create a new loop,
377
+ # run the coroutine, and close the loop.
378
+ return asyncio.run(coro)
326
379
  else:
327
- # If called from an already running loop (e.g. Jupyter, FastAPI endpoint)
328
- # Create a future and run it to completion. This is tricky and
329
- # ideally, one should use `await self.run_async` directly in async contexts.
330
- # This simple run_until_complete on a future might block the existing loop.
331
- # For truly non-blocking execution in an existing loop, one might need
332
- # to schedule the coroutine differently or advise users to use `await`.
333
- future = asyncio.ensure_future(
334
- self.run_async(
335
- start_agent=start_agent,
336
- input=input,
337
- context=context,
338
- run_id=run_id,
339
- box_result=box_result,
340
- agents=agents,
341
- )
380
+ # An event loop is already running in the current thread.
381
+ # Calling loop.run_until_complete() or asyncio.run() here would raise an error.
382
+ # To run the async code and wait for its result synchronously,
383
+ # we execute it in a separate thread with its own event loop.
384
+ logger.debug(
385
+ "Flock.run called in a context with an existing event loop. "
386
+ "Running async task in a separate thread to avoid event loop conflict."
342
387
  )
343
- return loop.run_until_complete(future)
388
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
389
+ future = executor.submit(asyncio.run, coro)
390
+ # Block and wait for the result from the other thread
391
+ return future.result()
344
392
 
345
393
 
346
394
  async def run_async(
@@ -555,14 +603,6 @@ class Flock(BaseModel, Serializable):
555
603
  delimiter: str = ",",
556
604
  ) -> list[Box | dict | None | Exception]:
557
605
  """Synchronous wrapper for run_batch_async."""
558
- try:
559
- loop = asyncio.get_running_loop()
560
- if loop.is_closed():
561
- raise RuntimeError("Event loop is closed")
562
- except RuntimeError: # No running loop
563
- loop = asyncio.new_event_loop()
564
- asyncio.set_event_loop(loop)
565
-
566
606
  coro = self.run_batch_async(
567
607
  start_agent=start_agent,
568
608
  batch_inputs=batch_inputs,
@@ -579,12 +619,18 @@ class Flock(BaseModel, Serializable):
579
619
  delimiter=delimiter,
580
620
  )
581
621
 
582
- if asyncio.get_event_loop() is loop and not loop.is_running():
583
- results = loop.run_until_complete(coro)
584
- return results
622
+ try:
623
+ loop = asyncio.get_running_loop()
624
+ except RuntimeError:
625
+ return asyncio.run(coro)
585
626
  else:
586
- future = asyncio.ensure_future(coro)
587
- return loop.run_until_complete(future)
627
+ logger.debug(
628
+ "Flock.run_batch called in a context with an existing event loop. "
629
+ "Running async task in a separate thread."
630
+ )
631
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
632
+ future = executor.submit(asyncio.run, coro)
633
+ return future.result()
588
634
 
589
635
  # --- Evaluation (Delegation) ---
590
636
  async def evaluate_async(
@@ -659,14 +705,6 @@ class Flock(BaseModel, Serializable):
659
705
  metadata_columns: list[str] | None = None,
660
706
  ) -> DataFrame | list[dict[str, Any]]: # type: ignore
661
707
  """Synchronous wrapper for evaluate_async."""
662
- try:
663
- loop = asyncio.get_running_loop()
664
- if loop.is_closed():
665
- raise RuntimeError("Event loop is closed")
666
- except RuntimeError: # No running loop
667
- loop = asyncio.new_event_loop()
668
- asyncio.set_event_loop(loop)
669
-
670
708
  coro = self.evaluate_async(
671
709
  dataset=dataset,
672
710
  start_agent=start_agent,
@@ -685,12 +723,18 @@ class Flock(BaseModel, Serializable):
685
723
  metadata_columns=metadata_columns,
686
724
  )
687
725
 
688
- if asyncio.get_event_loop() is loop and not loop.is_running():
689
- results = loop.run_until_complete(coro)
690
- return results
726
+ try:
727
+ loop = asyncio.get_running_loop()
728
+ except RuntimeError:
729
+ return asyncio.run(coro)
691
730
  else:
692
- future = asyncio.ensure_future(coro)
693
- return loop.run_until_complete(future)
731
+ logger.debug(
732
+ "Flock.evaluate called in a context with an existing event loop. "
733
+ "Running async task in a separate thread."
734
+ )
735
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
736
+ future = executor.submit(asyncio.run, coro)
737
+ return future.result()
694
738
 
695
739
  # --- Server & CLI Starters (Delegation) ---
696
740
  def start_api(
@@ -8,6 +8,8 @@ from __future__ import annotations # Add this at the very top
8
8
  import builtins
9
9
  import importlib
10
10
  import inspect
11
+ import os
12
+ import pkgutil
11
13
  import sys
12
14
  from collections.abc import Callable, Mapping, Sequence
13
15
  from dataclasses import is_dataclass
@@ -614,45 +616,50 @@ def flock_type(cls: ClassType | None = None, *, name: str | None = None) -> Any:
614
616
 
615
617
 
616
618
  # --- Auto-register known core components and tools ---
617
- def _auto_register_by_path():
618
- components_to_register = [
619
- (
620
- "flock.evaluators.declarative.declarative_evaluator",
621
- "DeclarativeEvaluator",
622
- ),
623
- ("flock.evaluators.memory.memory_evaluator", "MemoryEvaluator"),
624
- ("flock.modules.output.output_module", "OutputModule"),
625
- ("flock.modules.performance.metrics_module", "MetricsModule"),
626
- ("flock.modules.memory.memory_module", "MemoryModule"),
627
- # ("flock.modules.hierarchical.module", "HierarchicalMemoryModule"), # Uncomment if exists
628
- ("flock.routers.default.default_router", "DefaultRouter"),
629
- ("flock.routers.llm.llm_router", "LLMRouter"),
630
- ("flock.routers.agent.agent_router", "AgentRouter"),
619
+ def _auto_register_by_path(self):
620
+ # List of base packages to scan for components and tools
621
+ packages_to_scan = [
622
+ "flock.tools",
623
+ "flock.evaluators",
624
+ "flock.modules",
625
+ "flock.routers",
631
626
  ]
632
- for module_path, class_name in components_to_register:
633
- try:
634
- module = importlib.import_module(module_path)
635
- component_class = getattr(module, class_name)
636
- _registry_instance.register_component(component_class)
637
- except (ImportError, AttributeError) as e:
638
- logger.warning(f"{class_name} not found for auto-registration: {e}")
639
-
640
- # Auto-register standard tools by scanning modules
641
- tool_modules = [
642
- "flock.core.tools.basic_tools",
643
- "flock.core.tools.azure_tools",
644
- "flock.core.tools.dev_tools.github",
645
- "flock.core.tools.llm_tools",
646
- "flock.core.tools.markdown_tools",
647
- ]
648
- for module_path in tool_modules:
649
- try:
650
- _registry_instance.register_module_components(module_path)
651
- except ImportError as e:
652
- logger.warning(
653
- f"Could not auto-register tools from {module_path}: {e}"
654
- )
655
627
 
628
+ for package_name in packages_to_scan:
629
+ try:
630
+ package_spec = importlib.util.find_spec(package_name)
631
+ if package_spec and package_spec.origin:
632
+ package_path_list = [os.path.dirname(package_spec.origin)]
633
+ logger.info(f"Recursively scanning for modules in package: {package_name} (path: {package_path_list[0]})")
634
+
635
+ # Use walk_packages to recursively find all modules
636
+ for module_loader, module_name, is_pkg in pkgutil.walk_packages(
637
+ path=package_path_list,
638
+ prefix=package_name + ".", # Ensures module_name is fully qualified
639
+ onerror=lambda name: logger.warning(f"Error importing module {name} during scan.")
640
+ ):
641
+ if not is_pkg and not module_name.split('.')[-1].startswith("_"):
642
+ # We are interested in actual modules, not sub-packages themselves for registration
643
+ # And also skip modules starting with underscore (e.g. __main__.py)
644
+ try:
645
+ logger.debug(f"Attempting to auto-register components from module: {module_name}")
646
+ _registry_instance.register_module_components(module_name)
647
+ except ImportError as e:
648
+ logger.warning(
649
+ f"Could not auto-register from {module_name}: Module not found or import error: {e}"
650
+ )
651
+ except Exception as e: # Catch other potential errors during registration
652
+ logger.error(
653
+ f"Unexpected error during auto-registration of {module_name}: {e}",
654
+ exc_info=True
655
+ )
656
+ else:
657
+ logger.warning(f"Could not find package spec for '{package_name}' to auto-register components/tools.")
658
+ except Exception as e:
659
+ logger.error(f"Error while trying to dynamically register from '{package_name}': {e}", exc_info=True)
656
660
 
657
661
  # Bootstrapping the registry
658
- # _auto_register_by_path()
662
+ # _auto_register_by_path() # Commented out or removed
663
+
664
+ # Make the registration function public and rename it
665
+ FlockRegistry.discover_and_register_components = _auto_register_by_path
@@ -185,6 +185,9 @@ if __name__ == "__main__":
185
185
  # 'Arbitrarily complex structure')
186
186
 
187
187
  SAMPLE_17 = "münze: str | Deutsche Münzbezeichnung, engl. 'coin'"
188
+
189
+
190
+ SAMPLE_18 = "ticket_info : str, reasoning : str, search_queries : list[str], relevant_documents: dict[str, float] | dict of pdf_ids as keys and scores as values"
188
191
  # ➜ [('münze', 'str', "Deutsche Münzbezeichnung, engl. 'coin'")]
189
192
 
190
193
  for title, spec in [
@@ -205,6 +208,7 @@ if __name__ == "__main__":
205
208
  ("Sample-15", SAMPLE_15),
206
209
  ("Sample-16", SAMPLE_16),
207
210
  ("Sample-17", SAMPLE_17),
211
+ ("Sample-18", SAMPLE_18),
208
212
  ]:
209
213
  print(f"\n{title}")
210
214
  for row in parse_schema(spec):
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1,188 @@
1
+ from .azure_tools import (
2
+ azure_search_create_index,
3
+ azure_search_create_vector_index,
4
+ azure_search_delete_documents,
5
+ azure_search_get_document,
6
+ azure_search_get_index_statistics,
7
+ azure_search_initialize_clients,
8
+ azure_search_list_indexes,
9
+ azure_search_query,
10
+ azure_search_upload_documents,
11
+ azure_storage_create_container,
12
+ azure_storage_delete_blob,
13
+ azure_storage_delete_container,
14
+ azure_storage_download_blob_to_bytes,
15
+ azure_storage_download_blob_to_file,
16
+ azure_storage_download_blob_to_text,
17
+ azure_storage_get_blob_properties,
18
+ azure_storage_list_blobs,
19
+ azure_storage_list_containers,
20
+ azure_storage_upload_blob_bytes,
21
+ azure_storage_upload_blob_from_file,
22
+ azure_storage_upload_blob_text,
23
+ )
24
+ from .code_tools import code_code_eval, code_evaluate_math
25
+ from .file_tools import (
26
+ file_get_anything_as_markdown,
27
+ file_json_parse_safe,
28
+ file_json_search,
29
+ file_read_from_file,
30
+ file_save_to_file,
31
+ )
32
+ from .github_tools import (
33
+ github_create_files,
34
+ github_create_user_stories_as_github_issue,
35
+ github_upload_readme,
36
+ )
37
+ from .markdown_tools import (
38
+ markdown_extract_code_blocks,
39
+ markdown_extract_links,
40
+ markdown_extract_tables,
41
+ markdown_split_by_headers,
42
+ markdown_to_plain_text,
43
+ )
44
+ from .text_tools import (
45
+ text_calculate_hash,
46
+ text_chunking_for_embedding,
47
+ text_clean_text,
48
+ text_count_tokens,
49
+ text_count_tokens_estimate,
50
+ text_count_words,
51
+ text_detect_language,
52
+ text_extract_json_from_text,
53
+ text_extract_keywords,
54
+ text_extract_numbers,
55
+ text_extract_urls,
56
+ text_format_chat_history,
57
+ text_format_table_from_dicts,
58
+ text_recursive_splitter,
59
+ text_split_by_characters,
60
+ text_split_by_sentences,
61
+ text_split_by_separator,
62
+ text_split_by_tokens,
63
+ text_split_code_by_functions,
64
+ text_tiktoken_split,
65
+ text_truncate_to_token_limit,
66
+ )
67
+ from .web_tools import (
68
+ web_content_as_markdown,
69
+ web_search_bing,
70
+ web_search_duckduckgo,
71
+ web_search_tavily,
72
+ )
73
+ from .zendesk_tools import (
74
+ zendesk_get_article_by_id,
75
+ zendesk_get_articles,
76
+ zendesk_get_comments_by_ticket_id,
77
+ zendesk_get_ticket_by_id,
78
+ zendesk_get_tickets,
79
+ zendesk_search_articles,
80
+ )
81
+
82
+ storage_tools = [
83
+ azure_storage_list_containers,
84
+ azure_storage_create_container,
85
+ azure_storage_delete_container,
86
+ azure_storage_list_blobs,
87
+ azure_storage_upload_blob_text,
88
+ azure_storage_upload_blob_bytes,
89
+ azure_storage_upload_blob_from_file,
90
+ azure_storage_download_blob_to_text,
91
+ azure_storage_download_blob_to_bytes,
92
+ azure_storage_download_blob_to_file,
93
+ azure_storage_delete_blob,
94
+ azure_storage_get_blob_properties,
95
+ ]
96
+
97
+ azure_search_tools = [
98
+ azure_search_initialize_clients,
99
+ azure_search_create_index,
100
+ azure_search_upload_documents,
101
+ azure_search_query,
102
+ azure_search_get_document,
103
+ azure_search_delete_documents,
104
+ azure_search_list_indexes,
105
+ azure_search_get_index_statistics,
106
+ azure_search_create_vector_index,
107
+ ]
108
+
109
+ file_tools_collection = [
110
+ file_get_anything_as_markdown,
111
+ file_save_to_file,
112
+ file_read_from_file,
113
+ file_json_parse_safe,
114
+ file_json_search,
115
+ ]
116
+
117
+ code_tools_collection = [code_evaluate_math, code_code_eval]
118
+
119
+ web_tools_collection = [
120
+ web_content_as_markdown,
121
+ web_search_bing,
122
+ web_search_duckduckgo,
123
+ web_search_tavily,
124
+ ]
125
+
126
+ github_tools_collection = [
127
+ github_create_user_stories_as_github_issue,
128
+ github_upload_readme,
129
+ github_create_files,
130
+ ]
131
+
132
+ llm_processing_tools = [
133
+ text_split_by_sentences,
134
+ text_split_by_characters,
135
+ text_split_by_tokens,
136
+ text_split_by_separator,
137
+ text_recursive_splitter,
138
+ text_chunking_for_embedding,
139
+ text_split_code_by_functions,
140
+ text_count_tokens,
141
+ text_count_tokens_estimate,
142
+ text_truncate_to_token_limit,
143
+ text_extract_keywords,
144
+ text_clean_text,
145
+ text_format_chat_history,
146
+ text_extract_json_from_text,
147
+ text_calculate_hash,
148
+ text_format_table_from_dicts,
149
+ text_detect_language,
150
+ text_tiktoken_split,
151
+ text_count_words,
152
+ text_extract_urls,
153
+ text_extract_numbers,
154
+ ]
155
+
156
+ markdown_processing_tools = [
157
+ markdown_split_by_headers,
158
+ markdown_extract_code_blocks,
159
+ markdown_extract_links,
160
+ markdown_extract_tables,
161
+ markdown_to_plain_text,
162
+ ]
163
+
164
+ zendesk_tools_collection = [
165
+ zendesk_get_tickets,
166
+ zendesk_get_ticket_by_id,
167
+ zendesk_get_comments_by_ticket_id,
168
+ zendesk_get_article_by_id,
169
+ zendesk_get_articles,
170
+ zendesk_search_articles,
171
+ ]
172
+
173
+ __all__ = [
174
+ "azure_search_tools",
175
+ "code_tools_collection",
176
+ "file_tools_collection",
177
+ "github_tools_collection",
178
+ "llm_processing_tools",
179
+ "markdown_processing_tools",
180
+ "storage_tools",
181
+ "web_tools_collection",
182
+ "zendesk_tools_collection",
183
+ ]
184
+
185
+ # If there was existing content in __init__.py, this approach might overwrite it.
186
+ # A safer approach if __init__.py might exist and have other critical initializations
187
+ # would be to read it first, then append/modify.
188
+ # For now, assuming a fresh creation or simple __init__.py.