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.
- machineconfig/cluster/remote/run_cluster.py +1 -1
- machineconfig/cluster/remote/run_remote.py +1 -1
- machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
- machineconfig/cluster/sessions_managers/wt_local.py +1 -1
- machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
- machineconfig/jobs/installer/checks/check_installations.py +133 -0
- machineconfig/jobs/installer/checks/install_utils.py +132 -0
- machineconfig/jobs/installer/checks/report_utils.py +39 -0
- machineconfig/jobs/installer/checks/vt_utils.py +89 -0
- machineconfig/jobs/installer/installer_data.json +225 -140
- machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
- machineconfig/jobs/installer/package_groups.py +10 -9
- machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
- machineconfig/jobs/installer/python_scripts/code.py +10 -8
- machineconfig/jobs/installer/python_scripts/hx.py +30 -13
- machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
- machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
- machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
- machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
- machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
- machineconfig/jobs/scripts_dynamic/a.py +413 -10
- machineconfig/profile/create_links.py +77 -20
- machineconfig/profile/create_links_export.py +40 -51
- machineconfig/profile/mapper_data.toml +30 -0
- machineconfig/profile/mapper_dotfiles.toml +253 -0
- machineconfig/scripts/python/agents.py +70 -172
- machineconfig/scripts/python/ai/initai.py +3 -1
- machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
- machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
- machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -0
- machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
- machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
- machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
- machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
- machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
- machineconfig/scripts/python/cloud.py +58 -11
- machineconfig/scripts/python/croshell.py +4 -156
- machineconfig/scripts/python/devops.py +57 -40
- machineconfig/scripts/python/devops_navigator.py +17 -3
- machineconfig/scripts/python/fire_jobs.py +8 -207
- machineconfig/scripts/python/ftpx.py +5 -225
- machineconfig/scripts/python/graph/cli_graph.json +8743 -0
- machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
- machineconfig/scripts/python/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
- machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
- machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
- machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
- machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
- machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +7 -6
- machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
- machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +76 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
- machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +265 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +39 -23
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
- machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
- machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
- machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +3 -3
- machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
- machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +3 -3
- machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
- machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
- machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
- machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
- machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
- machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
- machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
- machineconfig/scripts/python/helpers/helpers_network/__init__.py +0 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
- machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
- machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
- machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +116 -33
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
- machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
- machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
- machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +177 -0
- machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +1 -1
- machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
- machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
- machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/download.py +1 -1
- machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +46 -26
- machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
- machineconfig/scripts/python/mcfg_entry.py +133 -48
- machineconfig/scripts/python/msearch.py +15 -61
- machineconfig/scripts/python/sessions.py +59 -194
- machineconfig/scripts/python/terminal.py +18 -96
- machineconfig/scripts/python/utils.py +101 -20
- machineconfig/settings/atuin/config.toml +294 -0
- machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
- machineconfig/settings/linters/.ruff.toml +1 -0
- machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
- machineconfig/settings/shells/bash/init.sh +6 -3
- machineconfig/settings/shells/pwsh/init.ps1 +69 -1
- machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
- machineconfig/settings/shells/wt/settings.json +20 -7
- machineconfig/settings/shells/zsh/init.sh +34 -4
- machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
- machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
- machineconfig/settings/tv/config.toml +234 -0
- machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
- machineconfig/settings/wsl/.wslconfig +5 -30
- machineconfig/settings/yazi/yazi_linux.toml +18 -8
- machineconfig/settings/zellij/layouts/st.kdl +2 -2
- machineconfig/settings/zellij/layouts/st2.kdl +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
- machineconfig/setup_mac/__init__.py +0 -2
- machineconfig/setup_windows/__init__.py +0 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
- machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
- machineconfig/type_hinting/sql/__init__.py +1 -0
- machineconfig/type_hinting/sql/base.py +216 -0
- machineconfig/type_hinting/sql/core_schema.py +64 -0
- machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
- machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
- machineconfig/type_hinting/typedict/__init__.py +1 -0
- machineconfig/type_hinting/typedict/ast_utils.py +130 -0
- machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
- machineconfig/type_hinting/typedict/generators.py +231 -0
- machineconfig/type_hinting/typedict/polars_schema.py +24 -0
- machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
- machineconfig/utils/accessories.py +24 -0
- machineconfig/utils/code.py +41 -13
- machineconfig/utils/files/ascii_art.py +10 -14
- machineconfig/utils/files/headers.py +3 -5
- machineconfig/utils/files/read.py +8 -1
- machineconfig/utils/installer_utils/github_release_bulk.py +1 -0
- machineconfig/utils/installer_utils/install_from_url.py +1 -1
- machineconfig/utils/installer_utils/installer_class.py +12 -4
- machineconfig/utils/installer_utils/installer_cli.py +1 -15
- machineconfig/utils/installer_utils/installer_helper.py +2 -2
- machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
- machineconfig/utils/installer_utils/installer_runner.py +4 -4
- machineconfig/utils/meta.py +6 -4
- machineconfig/utils/options.py +49 -19
- machineconfig/utils/options_utils/__init__.py +0 -0
- machineconfig/utils/options_utils/options_tv_linux.py +211 -0
- machineconfig/utils/options_utils/options_tv_windows.py +88 -0
- machineconfig/utils/options_utils/tv_options.py +37 -0
- machineconfig/utils/path_extended.py +6 -6
- machineconfig/utils/scheduler.py +8 -2
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/source_of_truth.py +6 -1
- machineconfig/utils/ssh.py +69 -18
- machineconfig/utils/ssh_utils/abc.py +1 -1
- machineconfig/utils/ssh_utils/wsl.py +107 -170
- machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
- machineconfig/utils/upgrade_packages.py +4 -8
- {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/METADATA +29 -22
- {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/RECORD +247 -208
- machineconfig/jobs/installer/check_installations.py +0 -248
- machineconfig/profile/backup.toml +0 -49
- machineconfig/profile/mapper.toml +0 -263
- machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
- machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
- machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
- machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
- machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
- machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
- machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
- machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
- machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
- machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
- machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
- machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
- machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
- machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
- machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
- machineconfig/utils/options_tv.py +0 -119
- machineconfig/utils/tst.py +0 -20
- /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
- /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
- /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers}/__init__.py +0 -0
- /machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
- /machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_agents}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
- /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
- /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_devops/themes}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
- /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/f.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
- /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
- /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
- /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
- /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
- /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
- /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
- /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/WHEEL +0 -0
- {machineconfig-8.14.dist-info → machineconfig-8.45.dist-info}/entry_points.txt +0 -0
- {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
|