machineconfig 8.14__py3-none-any.whl → 8.45__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 machineconfig might be problematic. Click here for more details.

Files changed (269) hide show
  1. machineconfig/cluster/remote/run_cluster.py +1 -1
  2. machineconfig/cluster/remote/run_remote.py +1 -1
  3. machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
  4. machineconfig/cluster/sessions_managers/wt_local.py +1 -1
  5. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  6. machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
  7. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
  8. machineconfig/jobs/installer/checks/check_installations.py +133 -0
  9. machineconfig/jobs/installer/checks/install_utils.py +132 -0
  10. machineconfig/jobs/installer/checks/report_utils.py +39 -0
  11. machineconfig/jobs/installer/checks/vt_utils.py +89 -0
  12. machineconfig/jobs/installer/installer_data.json +225 -140
  13. machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
  14. machineconfig/jobs/installer/package_groups.py +10 -9
  15. machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
  16. machineconfig/jobs/installer/python_scripts/code.py +10 -8
  17. machineconfig/jobs/installer/python_scripts/hx.py +30 -13
  18. machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
  19. machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
  20. machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
  21. machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
  22. machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
  23. machineconfig/jobs/scripts_dynamic/a.py +413 -10
  24. machineconfig/profile/create_links.py +77 -20
  25. machineconfig/profile/create_links_export.py +40 -51
  26. machineconfig/profile/mapper_data.toml +30 -0
  27. machineconfig/profile/mapper_dotfiles.toml +253 -0
  28. machineconfig/scripts/python/agents.py +70 -172
  29. machineconfig/scripts/python/ai/initai.py +3 -1
  30. machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
  31. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
  32. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
  33. machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
  34. machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
  35. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +1 -1
  36. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -0
  37. machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
  38. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
  39. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
  40. machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
  41. machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
  42. machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
  43. machineconfig/scripts/python/cloud.py +58 -11
  44. machineconfig/scripts/python/croshell.py +4 -156
  45. machineconfig/scripts/python/devops.py +57 -40
  46. machineconfig/scripts/python/devops_navigator.py +17 -3
  47. machineconfig/scripts/python/fire_jobs.py +8 -207
  48. machineconfig/scripts/python/ftpx.py +5 -225
  49. machineconfig/scripts/python/graph/cli_graph.json +8743 -0
  50. machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
  51. machineconfig/scripts/python/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
  52. machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
  53. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
  54. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
  55. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
  56. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
  57. machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
  58. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
  59. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
  60. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
  61. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
  62. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
  63. machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
  64. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
  65. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +7 -6
  66. machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
  67. machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -0
  68. machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
  69. machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
  70. machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +76 -0
  71. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
  72. machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +265 -0
  73. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +39 -23
  74. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
  75. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
  76. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
  77. machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
  78. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
  79. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
  80. machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
  81. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
  82. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
  83. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
  84. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +3 -3
  85. machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
  86. machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +3 -3
  87. machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
  88. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
  89. machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
  90. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
  91. machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
  92. machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
  93. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
  94. machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
  95. machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
  96. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
  97. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
  98. machineconfig/scripts/python/helpers/helpers_network/__init__.py +0 -0
  99. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
  100. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
  101. machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
  102. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
  103. machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
  104. machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
  105. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
  106. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
  107. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
  108. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
  109. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +116 -33
  110. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
  111. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
  112. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
  113. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
  114. machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
  115. machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
  116. machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +177 -0
  117. machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +1 -1
  118. machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
  119. machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
  120. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/download.py +1 -1
  121. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +46 -26
  122. machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
  123. machineconfig/scripts/python/mcfg_entry.py +133 -48
  124. machineconfig/scripts/python/msearch.py +15 -61
  125. machineconfig/scripts/python/sessions.py +59 -194
  126. machineconfig/scripts/python/terminal.py +18 -96
  127. machineconfig/scripts/python/utils.py +101 -20
  128. machineconfig/settings/atuin/config.toml +294 -0
  129. machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
  130. machineconfig/settings/linters/.ruff.toml +1 -0
  131. machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
  132. machineconfig/settings/shells/bash/init.sh +6 -3
  133. machineconfig/settings/shells/pwsh/init.ps1 +69 -1
  134. machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
  135. machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
  136. machineconfig/settings/shells/wt/settings.json +20 -7
  137. machineconfig/settings/shells/zsh/init.sh +34 -4
  138. machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
  139. machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
  140. machineconfig/settings/tv/config.toml +234 -0
  141. machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
  142. machineconfig/settings/wsl/.wslconfig +5 -30
  143. machineconfig/settings/yazi/yazi_linux.toml +18 -8
  144. machineconfig/settings/zellij/layouts/st.kdl +2 -2
  145. machineconfig/settings/zellij/layouts/st2.kdl +1 -1
  146. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  147. machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
  148. machineconfig/setup_mac/__init__.py +0 -2
  149. machineconfig/setup_windows/__init__.py +0 -1
  150. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
  151. machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
  152. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
  153. machineconfig/type_hinting/sql/__init__.py +1 -0
  154. machineconfig/type_hinting/sql/base.py +216 -0
  155. machineconfig/type_hinting/sql/core_schema.py +64 -0
  156. machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
  157. machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
  158. machineconfig/type_hinting/typedict/__init__.py +1 -0
  159. machineconfig/type_hinting/typedict/ast_utils.py +130 -0
  160. machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
  161. machineconfig/type_hinting/typedict/generators.py +231 -0
  162. machineconfig/type_hinting/typedict/polars_schema.py +24 -0
  163. machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
  164. machineconfig/utils/accessories.py +24 -0
  165. machineconfig/utils/code.py +41 -13
  166. machineconfig/utils/files/ascii_art.py +10 -14
  167. machineconfig/utils/files/headers.py +3 -5
  168. machineconfig/utils/files/read.py +8 -1
  169. machineconfig/utils/installer_utils/github_release_bulk.py +1 -0
  170. machineconfig/utils/installer_utils/install_from_url.py +1 -1
  171. machineconfig/utils/installer_utils/installer_class.py +12 -4
  172. machineconfig/utils/installer_utils/installer_cli.py +1 -15
  173. machineconfig/utils/installer_utils/installer_helper.py +2 -2
  174. machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
  175. machineconfig/utils/installer_utils/installer_runner.py +4 -4
  176. machineconfig/utils/meta.py +6 -4
  177. machineconfig/utils/options.py +49 -19
  178. machineconfig/utils/options_utils/__init__.py +0 -0
  179. machineconfig/utils/options_utils/options_tv_linux.py +211 -0
  180. machineconfig/utils/options_utils/options_tv_windows.py +88 -0
  181. machineconfig/utils/options_utils/tv_options.py +37 -0
  182. machineconfig/utils/path_extended.py +6 -6
  183. machineconfig/utils/scheduler.py +8 -2
  184. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  185. machineconfig/utils/source_of_truth.py +6 -1
  186. machineconfig/utils/ssh.py +69 -18
  187. machineconfig/utils/ssh_utils/abc.py +1 -1
  188. machineconfig/utils/ssh_utils/wsl.py +107 -170
  189. machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
  190. machineconfig/utils/upgrade_packages.py +4 -8
  191. {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/METADATA +29 -22
  192. {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/RECORD +247 -208
  193. machineconfig/jobs/installer/check_installations.py +0 -248
  194. machineconfig/profile/backup.toml +0 -49
  195. machineconfig/profile/mapper.toml +0 -263
  196. machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
  197. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
  198. machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
  199. machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
  200. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
  201. machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
  202. machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
  203. machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
  204. machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
  205. machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
  206. machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
  207. machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
  208. machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
  209. machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
  210. machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
  211. machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
  212. machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
  213. machineconfig/utils/options_tv.py +0 -119
  214. machineconfig/utils/tst.py +0 -20
  215. /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
  216. /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
  217. /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
  218. /machineconfig/scripts/python/{helpers_cloud → helpers}/__init__.py +0 -0
  219. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
  220. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
  221. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_agents}/__init__.py +0 -0
  222. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
  223. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
  224. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
  225. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
  226. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
  227. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
  228. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
  229. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
  230. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
  231. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
  232. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
  233. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
  234. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
  235. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
  236. /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
  237. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
  238. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
  239. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
  240. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +0 -0
  241. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
  242. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
  243. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
  244. /machineconfig/scripts/python/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
  245. /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_devops/themes}/__init__.py +0 -0
  246. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
  247. /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
  248. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
  249. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/f.py +0 -0
  250. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
  251. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
  252. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
  253. /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
  254. /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
  255. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
  256. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
  257. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
  258. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
  259. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
  260. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
  261. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
  262. /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
  263. /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
  264. /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
  265. /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
  266. /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
  267. {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/WHEEL +0 -0
  268. {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/entry_points.txt +0 -0
  269. {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,130 @@
1
+ import ast
2
+ from pathlib import Path
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ type FieldInfo = tuple[str, ast.expr | None, dict[str, tuple[str, str]]]
7
+ type ClassFields = tuple[str, list[FieldInfo]]
8
+
9
+ _file_cache: dict[Path, ast.Module] = {}
10
+
11
+
12
+ def _get_ast(path: Path) -> ast.Module:
13
+ if path not in _file_cache:
14
+ _file_cache[path] = ast.parse(path.read_text(encoding="utf-8"))
15
+ return _file_cache[path]
16
+
17
+
18
+ @dataclass
19
+ class FileContext:
20
+ path: Path
21
+ tree: ast.Module
22
+ classes: dict[str, ast.ClassDef] = field(default_factory=dict)
23
+ imports: dict[str, tuple[str, str]] = field(default_factory=dict)
24
+
25
+ def __post_init__(self):
26
+ self.classes = {node.name: node for node in ast.walk(self.tree) if isinstance(node, ast.ClassDef)}
27
+ for node in ast.walk(self.tree):
28
+ if isinstance(node, ast.ImportFrom) and node.module:
29
+ for alias in node.names:
30
+ self.imports[alias.asname or alias.name] = (node.module, alias.name)
31
+
32
+
33
+ def _get_context(path: Path) -> FileContext:
34
+ tree = _get_ast(path)
35
+ return FileContext(path, tree)
36
+
37
+
38
+ def _resolve_imported_class(module_name: str, class_name: str, search_paths: list[Path]) -> tuple[ast.ClassDef, FileContext] | None:
39
+ parts = module_name.split(".")
40
+ for root in search_paths:
41
+ file_path = root.joinpath(*parts).with_suffix(".py")
42
+ if file_path.exists():
43
+ try:
44
+ context = _get_context(file_path)
45
+ if class_name in context.classes:
46
+ return context.classes[class_name], context
47
+ except Exception:
48
+ pass
49
+ init_path = root.joinpath(*parts, "__init__.py")
50
+ if init_path.exists():
51
+ try:
52
+ context = _get_context(init_path)
53
+ if class_name in context.classes:
54
+ return context.classes[class_name], context
55
+ except Exception:
56
+ pass
57
+ return None
58
+
59
+
60
+ def load_target_class_fields(source_file_path: Path, search_paths: list[Path] | None = None) -> list[ClassFields]:
61
+ source_path = Path(source_file_path).resolve()
62
+ if search_paths is None:
63
+ search_paths = []
64
+
65
+ if not source_path.exists():
66
+ raise FileNotFoundError(f"Source file not found: {source_path}")
67
+
68
+ initial_context = _get_context(source_path)
69
+
70
+ def get_parent_node(base: ast.expr, context: FileContext) -> tuple[ast.ClassDef, FileContext] | None:
71
+ if isinstance(base, ast.Name):
72
+ if base.id in context.classes:
73
+ return context.classes[base.id], context
74
+ if base.id in context.imports:
75
+ module_name, original_name = context.imports[base.id]
76
+ return _resolve_imported_class(module_name, original_name, search_paths) # type: ignore
77
+ return None
78
+
79
+ def collect_fields(class_node: ast.ClassDef, context: FileContext, visited: set[str]) -> list[FieldInfo]:
80
+ key = f"{context.path}:{class_node.name}"
81
+ if key in visited:
82
+ return []
83
+ visited.add(key)
84
+ field_info: list[FieldInfo] = []
85
+ for base in class_node.bases:
86
+ parent_info = get_parent_node(base, context)
87
+ if parent_info:
88
+ parent_node, parent_context = parent_info
89
+ field_info.extend(collect_fields(parent_node, parent_context, visited))
90
+ for statement in class_node.body:
91
+ if isinstance(statement, ast.AnnAssign) and isinstance(statement.target, ast.Name):
92
+ field_info.append((statement.target.id, statement.annotation, context.imports))
93
+ return field_info
94
+
95
+ def deduplicate_fields(fields: list[FieldInfo]) -> list[FieldInfo]:
96
+ deduped: dict[str, FieldInfo] = {}
97
+ for item in fields:
98
+ field_name = item[0]
99
+ deduped[field_name] = item
100
+ return list(deduped.values())
101
+
102
+ def is_typeddict(node: ast.ClassDef, context: FileContext, visited: set[str]) -> bool:
103
+ key = f"{context.path}:{node.name}"
104
+ if key in visited:
105
+ return False
106
+ visited.add(key)
107
+ for base in node.bases:
108
+ if (isinstance(base, ast.Name) and base.id == "TypedDict") or (isinstance(base, ast.Attribute) and base.attr == "TypedDict"):
109
+ return True
110
+ parent_info = get_parent_node(base, context)
111
+ if parent_info:
112
+ parent_node, parent_context = parent_info
113
+ if is_typeddict(parent_node, parent_context, visited):
114
+ return True
115
+ return False
116
+
117
+ target_classes: list[ClassFields] = []
118
+ for node in ast.walk(initial_context.tree):
119
+ if not isinstance(node, ast.ClassDef):
120
+ continue
121
+ is_dataclass = any(
122
+ (isinstance(decorator, ast.Name) and decorator.id == "dataclass")
123
+ or (isinstance(decorator, ast.Attribute) and decorator.attr == "dataclass")
124
+ or (isinstance(decorator, ast.Call) and ((isinstance(decorator.func, ast.Name) and decorator.func.id == "dataclass") or (isinstance(decorator.func, ast.Attribute) and decorator.func.attr == "dataclass")))
125
+ for decorator in node.decorator_list
126
+ )
127
+ if is_dataclass or is_typeddict(node, initial_context, set()):
128
+ fields = collect_fields(node, initial_context, set())
129
+ target_classes.append((node.name, deduplicate_fields(fields)))
130
+ return target_classes
@@ -0,0 +1,319 @@
1
+ import ast
2
+ import inspect
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+
7
+ WRAPPER_TYPES = {"ReadOnly", "NotRequired", "Optional", "Required", "Final"}
8
+ BUILTIN_TYPES = {"str", "int", "float", "bool", "bytes", "None", "list", "dict", "set", "tuple", "Any", "Literal"}
9
+ PYTHON_TYPE_TO_SERIES_TYPE: dict[str, str] = {"str": "str", "int": "int", "float": "float", "bool": "bool", "bytes": "bytes"}
10
+ PYTHON_TYPE_TO_POLARS_DTYPE: dict[str, str] = {"str": '"pl.String"', "int": '"pl.Int64"', "float": '"pl.Float64"', "bool": '"pl.Boolean"', "bytes": '"pl.Binary"'}
11
+
12
+
13
+ def get_types_class_name(class_name: str) -> str:
14
+ return f"{class_name}_Types"
15
+
16
+
17
+ def collect_type_names_from_annotation(annotation: ast.expr | None) -> set[str]:
18
+ if annotation is None:
19
+ return set()
20
+ names: set[str] = set()
21
+ if isinstance(annotation, ast.Name):
22
+ if annotation.id not in BUILTIN_TYPES and annotation.id not in WRAPPER_TYPES:
23
+ names.add(annotation.id)
24
+ elif isinstance(annotation, ast.Subscript):
25
+ names.update(collect_type_names_from_annotation(annotation.value))
26
+ names.update(collect_type_names_from_annotation(annotation.slice))
27
+ elif isinstance(annotation, ast.BinOp):
28
+ names.update(collect_type_names_from_annotation(annotation.left))
29
+ names.update(collect_type_names_from_annotation(annotation.right))
30
+ elif isinstance(annotation, ast.Tuple):
31
+ for elt in annotation.elts:
32
+ names.update(collect_type_names_from_annotation(elt))
33
+ return names
34
+
35
+
36
+ def extract_imports_from_source(source_file_path: Path) -> tuple[dict[str, str], dict[str, str], set[str]]:
37
+ source_content = source_file_path.read_text(encoding="utf-8")
38
+ tree = ast.parse(source_content)
39
+ imports: dict[str, str] = {}
40
+ local_type_aliases: dict[str, str] = {}
41
+ local_classes: set[str] = set()
42
+ for node in ast.walk(tree):
43
+ if isinstance(node, ast.ImportFrom) and node.module:
44
+ for alias in node.names:
45
+ name = alias.asname or alias.name
46
+ imports[name] = node.module
47
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
48
+ if isinstance(node.annotation, ast.Name) and node.annotation.id == "TypeAlias" and node.value is not None:
49
+ local_type_aliases[node.target.id] = ast.unparse(node.value)
50
+ elif isinstance(node, ast.ClassDef):
51
+ local_classes.add(node.name)
52
+ return imports, local_type_aliases, local_classes
53
+
54
+
55
+ def unwrap_type_annotation(annotation: ast.expr | None) -> ast.expr | None:
56
+ if annotation is None:
57
+ return None
58
+ if isinstance(annotation, ast.Subscript) and isinstance(annotation.value, ast.Name) and annotation.value.id in WRAPPER_TYPES:
59
+ return unwrap_type_annotation(annotation.slice)
60
+ if isinstance(annotation, ast.Subscript) and isinstance(annotation.value, ast.Attribute) and annotation.value.attr in WRAPPER_TYPES:
61
+ return unwrap_type_annotation(annotation.slice)
62
+ return annotation
63
+
64
+
65
+ def is_builtin_type(annotation: ast.expr) -> bool:
66
+ if isinstance(annotation, ast.Name):
67
+ return annotation.id in BUILTIN_TYPES
68
+ if isinstance(annotation, ast.Constant):
69
+ return True
70
+ if isinstance(annotation, ast.Subscript):
71
+ return is_builtin_type(annotation.value) and is_builtin_type(annotation.slice)
72
+ if isinstance(annotation, ast.Attribute):
73
+ return False
74
+ if isinstance(annotation, ast.BinOp):
75
+ return is_builtin_type(annotation.left) and is_builtin_type(annotation.right)
76
+ if isinstance(annotation, ast.Tuple):
77
+ return all(is_builtin_type(elt) for elt in annotation.elts)
78
+ return False
79
+
80
+
81
+ def python_type_to_iterable_str(annotation: ast.expr | None) -> str:
82
+ unwrapped = unwrap_type_annotation(annotation)
83
+ if unwrapped is None:
84
+ return 'Iterable["Any"]'
85
+ type_str = ast.unparse(unwrapped)
86
+ if not is_builtin_type(unwrapped):
87
+ return f'Iterable["{type_str}"]'
88
+ return f"Iterable[{type_str}]"
89
+
90
+
91
+ def get_iterable_type_for_col(annotation: ast.expr | None) -> str:
92
+ unwrapped = unwrap_type_annotation(annotation)
93
+ if unwrapped is None:
94
+ return 'Annotated["pl.Series", Any]'
95
+ if isinstance(unwrapped, ast.Name):
96
+ dtype = PYTHON_TYPE_TO_POLARS_DTYPE.get(unwrapped.id)
97
+ if dtype:
98
+ return f'Annotated["pl.Series", {dtype}]'
99
+ return 'Annotated["pl.Series", Any]'
100
+ if isinstance(unwrapped, ast.Subscript) and isinstance(unwrapped.value, ast.Name):
101
+ if unwrapped.value.id == "list":
102
+ return 'Annotated["pl.Series", "pl.List"]'
103
+ return 'Annotated["pl.Series", Any]'
104
+
105
+
106
+ def get_random_value_expr(annotation: ast.expr | None) -> str:
107
+ unwrapped = unwrap_type_annotation(annotation)
108
+ if unwrapped is None:
109
+ return "None"
110
+ if isinstance(unwrapped, ast.Name):
111
+ type_name = unwrapped.id
112
+ if type_name == "str":
113
+ return "pl.Series([secrets.token_hex(8) for _ in range(n_rows)])"
114
+ if type_name == "int":
115
+ return "pl.Series(random.choices(range(-1000, 1000), k=n_rows))"
116
+ if type_name == "float":
117
+ return "pl.Series([random.uniform(-1000.0, 1000.0) for _ in range(n_rows)])"
118
+ if type_name == "bool":
119
+ return "pl.Series(random.choices([True, False], k=n_rows))"
120
+ if type_name == "bytes":
121
+ return "pl.Series([secrets.token_bytes(16) for _ in range(n_rows)])"
122
+ return "pl.Series([None] * n_rows)"
123
+ if isinstance(unwrapped, ast.Subscript) and isinstance(unwrapped.value, ast.Name):
124
+ if unwrapped.value.id == "list":
125
+ return "pl.Series([[random.randint(0, 100) for _ in range(3)] for _ in range(n_rows)])"
126
+ return "pl.Series([None] * n_rows)"
127
+
128
+
129
+ # def _check_random_imports_needed(field_infos: list[tuple[str, ast.expr | None]]) -> tuple[bool, bool]:
130
+ # needs_random = False
131
+ # needs_secrets = False
132
+ # for _field_name, annotation in field_infos:
133
+ # unwrapped = unwrap_type_annotation(annotation)
134
+ # if unwrapped is None:
135
+ # continue
136
+ # if isinstance(unwrapped, ast.Name):
137
+ # type_name = unwrapped.id
138
+ # if type_name in {"int", "float", "bool"}:
139
+ # needs_random = True
140
+ # elif type_name in {"str", "bytes"}:
141
+ # needs_secrets = True
142
+ # elif isinstance(unwrapped, ast.Subscript) and isinstance(unwrapped.value, ast.Name):
143
+ # if unwrapped.value.id == "list":
144
+ # needs_random = True
145
+ # return needs_random, needs_secrets
146
+
147
+
148
+ def quote_pl_in_annotation(annotation_str: str) -> str:
149
+ """Quote pl.X types in annotation strings since pl is only available under TYPE_CHECKING."""
150
+ import re
151
+ # Match pl.X only when NOT already inside quotes (preceded by ' or ")
152
+ # Use negative lookbehind for quotes
153
+ return re.sub(r"(?<!['\"])(\bpl\.\w+)(?!['\"])", r"'\1'", annotation_str)
154
+
155
+
156
+ def _get_module_name_from_output_path(file_path: Path) -> str:
157
+ """Get the module name from the output file path."""
158
+ resolved = file_path.resolve()
159
+ parts: list[str] = []
160
+ current = resolved.parent
161
+ while current != current.parent:
162
+ if not (current / "__init__.py").exists():
163
+ break
164
+ parts.append(current.name)
165
+ current = current.parent
166
+ parts.reverse()
167
+ return ".".join(parts) if parts else ""
168
+
169
+
170
+ def get_module_level_helper_functions() -> list[str]:
171
+ """Get helper functions to be defined once at module level for self-contained mode.
172
+ Returns only the function definitions, not imports (imports are handled separately at file top).
173
+ """
174
+ from machineconfig.type_hinting.typedict import polars_schema_typeddict
175
+
176
+ lines = []
177
+ lines.append("# Helper functions for self-contained mode (defined once at module level)")
178
+ lines.append("")
179
+
180
+ # Add all helper functions
181
+ for func_name in ["_unwrap_type", "_get_polars_type", "get_polars_schema_from_typeddict", "get_polars_df_random_data_from_typeddict"]:
182
+ func = getattr(polars_schema_typeddict, func_name)
183
+ source = inspect.getsource(func)
184
+ for line in source.splitlines():
185
+ lines.append(line)
186
+ lines.append("")
187
+
188
+ return lines
189
+
190
+
191
+ def generate_for_class(class_name: str, field_infos: list[tuple[str, ast.expr | None]], source_module: str, dependency: Literal["import", "self-contained"] = "self-contained", output_file_path: Path | None = None) -> list[str]:
192
+ lines: list[str] = []
193
+ field_names = [fn for fn, _ in field_infos]
194
+
195
+ types_class_name = get_types_class_name(class_name)
196
+
197
+ names_class_name = f"{class_name}Names"
198
+ lines.append(f"class {names_class_name}:")
199
+ if field_infos:
200
+ for field_name in field_names:
201
+ lines.append(f' {field_name}: Literal["{field_name}"] = "{field_name}"')
202
+ else:
203
+ lines.append(" pass")
204
+ lines.append("")
205
+ lines.append("")
206
+
207
+ names_literal = f"{class_name}_NAMES"
208
+ if field_names:
209
+ literal_values = ", ".join(f'"{fn}"' for fn in field_names)
210
+ lines.append(f'{names_literal}: TypeAlias = Literal[{literal_values}]')
211
+ else:
212
+ lines.append(f'{names_literal}: TypeAlias = Literal[""]')
213
+ lines.append("")
214
+ lines.append("")
215
+
216
+ lines.append(f"class {types_class_name}:")
217
+ if field_infos:
218
+ for field_name, annotation in field_infos:
219
+ if annotation is None:
220
+ raise ValueError(f"Field '{field_name}' in class '{class_name}' lacks an annotation")
221
+ unwrapped = unwrap_type_annotation(annotation)
222
+ if unwrapped is None:
223
+ raise ValueError(f"Field '{field_name}' in class '{class_name}' lacks an annotation")
224
+ annotation_source = ast.unparse(unwrapped)
225
+ annotation_source = quote_pl_in_annotation(annotation_source)
226
+ lines.append(f" {field_name}: TypeAlias = {annotation_source}")
227
+ else:
228
+ lines.append(" pass")
229
+ lines.append("")
230
+ lines.append("")
231
+
232
+ wrapper_class_name = f"{class_name}_Wrapper"
233
+ lines.append(f"class {wrapper_class_name}:")
234
+ lines.append(f" c = {names_class_name}")
235
+ lines.append(f" ct: TypeAlias = {names_literal}")
236
+ lines.append(f' e: TypeAlias = {class_name}')
237
+ lines.append(f" t: TypeAlias = {types_class_name}")
238
+ lines.append("")
239
+ lines.append(' def __init__(self, df: "pl.DataFrame") -> None:')
240
+ lines.append(" self.df = df")
241
+ lines.append("")
242
+
243
+ if field_infos:
244
+ # Group fields by their return type to reduce number of overloads
245
+ grouped_fields: dict[str, list[str]] = {}
246
+ for field_name, annotation in field_infos:
247
+ col_type = get_iterable_type_for_col(annotation)
248
+ grouped_fields.setdefault(col_type, []).append(field_name)
249
+
250
+ if len(grouped_fields) == 1:
251
+ col_type = list(grouped_fields.keys())[0]
252
+ lines.append(f' def get_col(self, name: {names_literal}) -> {col_type}:')
253
+ lines.append(" return self.df.select(name).to_series()")
254
+ lines.append("")
255
+ else:
256
+ for col_type, fields in grouped_fields.items():
257
+ lines.append(" @overload")
258
+ if len(fields) == 1:
259
+ lines.append(f' def get_col(self, name: Literal["{fields[0]}"]) -> {col_type}: ...')
260
+ else:
261
+ literals = ", ".join(f'"{f}"' for f in fields)
262
+ lines.append(f' def get_col(self, name: Literal[{literals}]) -> {col_type}: ...')
263
+
264
+ lines.append(f' def get_col(self, name: {names_literal}) -> Annotated["pl.Series", Any]:')
265
+ lines.append(" return self.df.select(name).to_series()")
266
+ lines.append("")
267
+
268
+ params: list[str] = []
269
+ dict_entries: list[str] = []
270
+ for field_name, annotation in field_infos:
271
+ iterable_type = python_type_to_iterable_str(annotation)
272
+ params.append(f"{field_name}: {iterable_type}")
273
+ dict_entries.append(f'"{field_name}": {field_name}')
274
+ params_str = ", ".join(params)
275
+ dict_str = "{" + ", ".join(dict_entries) + "}"
276
+ lines.append(" @staticmethod")
277
+ lines.append(f' def make({params_str}) -> "{wrapper_class_name}":')
278
+
279
+ if dependency == "import":
280
+ # Use fully qualified import from dtypes_utils
281
+ if output_file_path:
282
+ output_module = _get_module_name_from_output_path(output_file_path)
283
+ lines.append(f" from {output_module}.dtypes_utils import get_polars_schema_from_typeddict as get_polars_schema")
284
+ else:
285
+ lines.append(" from machineconfig.type_hinting.polars_schema_typeddict import get_polars_schema_from_typeddict as get_polars_schema")
286
+ else: # self-contained - use module-level function
287
+ lines.append(" get_polars_schema = get_polars_schema_from_typeddict")
288
+
289
+ lines.append(f" return {wrapper_class_name}(pl.DataFrame({dict_str}, schema=get_polars_schema({class_name})))")
290
+ lines.append("")
291
+
292
+ lines.append(" @staticmethod")
293
+ lines.append(f' def make_fake(n_rows: int) -> "{wrapper_class_name}":')
294
+
295
+ if dependency == "import":
296
+ # Use fully qualified import from dtypes_utils
297
+ if output_file_path:
298
+ output_module = _get_module_name_from_output_path(output_file_path)
299
+ lines.append(f" from {output_module}.dtypes_utils import get_polars_df_random_data_from_typeddict")
300
+ else:
301
+ lines.append(" from machineconfig.type_hinting.polars_schema_typeddict import get_polars_df_random_data_from_typeddict")
302
+ else: # self-contained - use module-level function
303
+ pass # No imports needed, function is at module level
304
+
305
+ lines.append(f" return {wrapper_class_name}(get_polars_df_random_data_from_typeddict({class_name}, n_rows))")
306
+ else:
307
+ lines.append(" @staticmethod")
308
+ lines.append(f' def make() -> "{wrapper_class_name}":')
309
+ lines.append(" import polars as pl")
310
+ lines.append(f" return {wrapper_class_name}(pl.DataFrame())")
311
+ lines.append("")
312
+ lines.append(" @staticmethod")
313
+ lines.append(f' def make_fake(n_rows: int) -> "{wrapper_class_name}":')
314
+ lines.append(" import polars as pl")
315
+ lines.append(" _ = n_rows")
316
+ lines.append(f" return {wrapper_class_name}(pl.DataFrame())")
317
+ lines.append("")
318
+ lines.append("")
319
+ return lines
@@ -0,0 +1,231 @@
1
+ import ast
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+ from machineconfig.type_hinting.typedict.ast_utils import load_target_class_fields
7
+ from machineconfig.type_hinting.typedict.generator_helpers import (
8
+ collect_type_names_from_annotation,
9
+ extract_imports_from_source,
10
+ generate_for_class,
11
+ get_types_class_name,
12
+ unwrap_type_annotation,
13
+ )
14
+
15
+
16
+ def _get_module_name_from_path(file_path: Path) -> str:
17
+ resolved = file_path.resolve()
18
+ parts: list[str] = [resolved.stem]
19
+ current = resolved.parent
20
+ while current != current.parent:
21
+ if not (current / "__init__.py").exists():
22
+ break
23
+ parts.append(current.name)
24
+ current = current.parent
25
+ parts.reverse()
26
+ return ".".join(parts)
27
+
28
+
29
+ def generate_names_file(source_file_path: Path, output_file_path: Path, search_paths: list[Path] | None = None, dependency: Literal["import", "self-contained"] = "self-contained") -> Path:
30
+ target_classes = load_target_class_fields(source_file_path, search_paths)
31
+ target_path = Path(output_file_path).resolve()
32
+
33
+ # Handle dependency mode: copy dtypes_utils.py for 'import' mode
34
+ if dependency == "import":
35
+ source_polars_schema = Path(__file__).parent / "polars_schema_typeddict.py"
36
+ target_dtypes_utils = target_path.parent / "dtypes_utils.py"
37
+ shutil.copy2(source_polars_schema, target_dtypes_utils)
38
+
39
+ source_imports, local_type_aliases, local_classes = extract_imports_from_source(Path(source_file_path))
40
+
41
+ all_custom_types: dict[str, str | None] = {}
42
+ for _class_name, field_infos in target_classes:
43
+ for _field_name, annotation, field_imports in field_infos:
44
+ unwrapped = unwrap_type_annotation(annotation)
45
+ type_names = collect_type_names_from_annotation(unwrapped)
46
+ for type_name in type_names:
47
+ if type_name not in all_custom_types:
48
+ if type_name in field_imports:
49
+ all_custom_types[type_name] = field_imports[type_name][0]
50
+ else:
51
+ all_custom_types[type_name] = None
52
+
53
+ grouped_imports: dict[str, list[str]] = {}
54
+ needed_local_aliases: list[str] = []
55
+ needed_local_classes: list[str] = []
56
+ for type_name in sorted(all_custom_types.keys()):
57
+ module = all_custom_types[type_name]
58
+ if module:
59
+ grouped_imports.setdefault(module, []).append(type_name)
60
+ elif type_name in source_imports:
61
+ module = source_imports[type_name]
62
+ grouped_imports.setdefault(module, []).append(type_name)
63
+ elif type_name in local_type_aliases:
64
+ needed_local_aliases.append(type_name)
65
+ elif type_name in local_classes:
66
+ needed_local_classes.append(type_name)
67
+
68
+ # Check if Any is actually used in get_col return type annotations
69
+ uses_any = False
70
+ for _class_name, field_infos in target_classes:
71
+ if not field_infos: # Empty class won't have get_col with Any
72
+ continue
73
+ # Check if there are multiple different column types (which means we need Any fallback)
74
+ from machineconfig.type_hinting.typedict.generator_helpers import get_iterable_type_for_col
75
+ col_types = set()
76
+ for _field_name, annotation in [(fn, ann) for fn, ann, _ in field_infos]:
77
+ col_types.add(get_iterable_type_for_col(annotation))
78
+ if len(col_types) > 1:
79
+ uses_any = True
80
+ break
81
+
82
+ # Build typing imports based on what's needed
83
+ typing_imports = ["Annotated", "Literal", "TypeAlias", "TYPE_CHECKING", "overload"]
84
+ if uses_any:
85
+ typing_imports.insert(1, "Any") # Insert after Annotated
86
+
87
+ lines: list[str] = ["from collections.abc import Iterable", f"from typing import {', '.join(typing_imports)}"]
88
+
89
+ # Add self-contained mode imports right at the top
90
+ if dependency == "self-contained":
91
+ lines.append("from typing import ReadOnly, get_origin, get_args")
92
+ lines.append("import polars as pl")
93
+ lines.append("import random")
94
+ lines.append("import secrets")
95
+
96
+ lines.append("")
97
+
98
+ target_class_names = [class_name for class_name, _ in target_classes]
99
+ source_module = _get_module_name_from_path(source_file_path)
100
+ if target_class_names:
101
+ lines.append(f"from {source_module} import {', '.join(sorted(target_class_names))}")
102
+
103
+ for alias_name in sorted(needed_local_aliases):
104
+ lines.append(f"{alias_name}: TypeAlias = {local_type_aliases[alias_name]}")
105
+ if needed_local_aliases:
106
+ lines.append("")
107
+
108
+ # Runtime imports: types used in _Types classes (TypeAlias assignments) need to be available at runtime
109
+ for module, type_names in sorted(grouped_imports.items()):
110
+ lines.append(f"from {module} import {', '.join(sorted(type_names))}")
111
+
112
+ # Check if numpy (np) is used in type aliases - look through all annotations
113
+ needs_numpy = False
114
+ for _class_name, field_infos in target_classes:
115
+ for _field_name, annotation, _field_imports in field_infos:
116
+ if annotation:
117
+ import ast
118
+ annotation_str = ast.unparse(annotation)
119
+ if 'np.' in annotation_str:
120
+ needs_numpy = True
121
+ break
122
+ if needs_numpy:
123
+ break
124
+
125
+ if needs_numpy:
126
+ lines.append("import numpy as np")
127
+
128
+ lines.append("")
129
+ # Only add TYPE_CHECKING block for non-self-contained mode (self-contained imports polars at top)
130
+ if dependency != "self-contained":
131
+ lines.append("if TYPE_CHECKING:")
132
+ lines.append(" import polars as pl")
133
+ else:
134
+ # For self-contained, polars is already imported at the top
135
+ lines.append("if TYPE_CHECKING:")
136
+ lines.append(" pass")
137
+ all_type_checking_imports = sorted(set(needed_local_classes) - set(target_class_names))
138
+ if all_type_checking_imports:
139
+ lines.append(f" from {source_module} import {', '.join(all_type_checking_imports)}")
140
+ lines.append("")
141
+
142
+ # For self-contained mode, add helper functions at module level
143
+ if dependency == "self-contained":
144
+ from machineconfig.type_hinting.typedict.generator_helpers import get_module_level_helper_functions
145
+ helper_lines = get_module_level_helper_functions()
146
+ lines.extend(helper_lines)
147
+ lines.append("")
148
+
149
+ for class_name, field_infos in target_classes:
150
+ source_module = _get_module_name_from_path(source_file_path)
151
+ stripped_field_infos = [(fn, ann) for fn, ann, _ in field_infos]
152
+ lines.extend(generate_for_class(class_name, stripped_field_infos, source_module, dependency=dependency, output_file_path=target_path))
153
+
154
+ lines.append("")
155
+ lines.append('if __name__ == "__main__":')
156
+ lines.append(" import polars as pl")
157
+ lines.append("")
158
+
159
+ output_content = "\n".join(lines)
160
+ with target_path.open(mode="w", encoding="utf-8") as f:
161
+ f.write(output_content)
162
+ return target_path
163
+
164
+
165
+ def generate_types_file(source_file_path: Path, output_file_path: Path, search_paths: list[Path] | None = None) -> Path:
166
+ target_classes = load_target_class_fields(source_file_path, search_paths)
167
+ target_path = Path(output_file_path).resolve()
168
+
169
+ source_imports, local_type_aliases, local_classes = extract_imports_from_source(Path(source_file_path))
170
+
171
+ all_custom_types: dict[str, str | None] = {}
172
+ for _class_name, field_infos in target_classes:
173
+ for _field_name, annotation, field_imports in field_infos:
174
+ unwrapped = unwrap_type_annotation(annotation)
175
+ type_names = collect_type_names_from_annotation(unwrapped)
176
+ for type_name in type_names:
177
+ if type_name not in all_custom_types:
178
+ if type_name in field_imports:
179
+ all_custom_types[type_name] = field_imports[type_name][0]
180
+ else:
181
+ all_custom_types[type_name] = None
182
+
183
+ grouped_imports: dict[str, list[str]] = {}
184
+ needed_local_aliases: list[str] = []
185
+ needed_local_classes: list[str] = []
186
+ for type_name in sorted(all_custom_types.keys()):
187
+ module = all_custom_types[type_name]
188
+ if module:
189
+ grouped_imports.setdefault(module, []).append(type_name)
190
+ elif type_name in source_imports:
191
+ module = source_imports[type_name]
192
+ grouped_imports.setdefault(module, []).append(type_name)
193
+ elif type_name in local_type_aliases:
194
+ needed_local_aliases.append(type_name)
195
+ elif type_name in local_classes:
196
+ needed_local_classes.append(type_name)
197
+
198
+ lines: list[str] = ["from typing import TypeAlias"]
199
+ for module, type_names in sorted(grouped_imports.items()):
200
+ lines.append(f"from {module} import {', '.join(sorted(type_names))}")
201
+ if needed_local_classes:
202
+ source_module = _get_module_name_from_path(source_file_path)
203
+ lines.append(f"from {source_module} import {', '.join(sorted(needed_local_classes))}")
204
+ lines.append("")
205
+ for alias_name in sorted(needed_local_aliases):
206
+ lines.append(f"{alias_name}: TypeAlias = {local_type_aliases[alias_name]}")
207
+ if needed_local_aliases:
208
+ lines.append("")
209
+ lines.append("")
210
+
211
+ for class_name, field_infos in target_classes:
212
+ types_class_name = get_types_class_name(class_name)
213
+ lines.append(f"class {types_class_name}:")
214
+ if field_infos:
215
+ for field_name, annotation, _ in field_infos:
216
+ if annotation is None:
217
+ raise ValueError(f"Field '{field_name}' in class '{class_name}' lacks an annotation")
218
+ unwrapped = unwrap_type_annotation(annotation)
219
+ if unwrapped is None:
220
+ raise ValueError(f"Field '{field_name}' in class '{class_name}' lacks an annotation")
221
+ annotation_source = ast.unparse(unwrapped)
222
+ lines.append(f" {field_name}: TypeAlias = {annotation_source}")
223
+ else:
224
+ lines.append(" pass")
225
+ lines.append("")
226
+ lines.append("")
227
+
228
+ output_content = "\n".join(lines)
229
+ with target_path.open(mode="w", encoding="utf-8") as f:
230
+ f.write(output_content)
231
+ return target_path