praisonai-code 0.0.1__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.
- praisonai_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Packaging checks for the Doctor CLI module.
|
|
3
|
+
|
|
4
|
+
Validates PraisonAI packaging and entry point configurations to diagnose
|
|
5
|
+
Windows daemon invocation issues (python -m praisonai vs praisonai console script).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import platform
|
|
12
|
+
import shutil
|
|
13
|
+
|
|
14
|
+
from ..models import (
|
|
15
|
+
CheckCategory,
|
|
16
|
+
CheckResult,
|
|
17
|
+
CheckStatus,
|
|
18
|
+
CheckSeverity,
|
|
19
|
+
DoctorConfig,
|
|
20
|
+
)
|
|
21
|
+
from ..registry import register_check
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@register_check(
|
|
25
|
+
id="praisonai_package_structure",
|
|
26
|
+
title="PraisonAI Package Structure",
|
|
27
|
+
description="Check if praisonai package has correct structure for both -m and script execution",
|
|
28
|
+
category=CheckCategory.PACKAGING,
|
|
29
|
+
severity=CheckSeverity.HIGH,
|
|
30
|
+
)
|
|
31
|
+
def check_praisonai_package_structure(config: DoctorConfig) -> CheckResult:
|
|
32
|
+
"""Check PraisonAI package structure and entry points."""
|
|
33
|
+
issues = []
|
|
34
|
+
metadata = {}
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import praisonai
|
|
38
|
+
package_path = praisonai.__file__
|
|
39
|
+
if package_path:
|
|
40
|
+
package_dir = os.path.dirname(package_path)
|
|
41
|
+
metadata["package_path"] = package_path
|
|
42
|
+
metadata["package_dir"] = package_dir
|
|
43
|
+
|
|
44
|
+
# Check for __main__.py
|
|
45
|
+
main_py_path = os.path.join(package_dir, "__main__.py")
|
|
46
|
+
if os.path.exists(main_py_path):
|
|
47
|
+
metadata["has_main_py"] = True
|
|
48
|
+
metadata["main_py_path"] = main_py_path
|
|
49
|
+
else:
|
|
50
|
+
issues.append("Missing __main__.py - python -m praisonai will not work")
|
|
51
|
+
metadata["has_main_py"] = False
|
|
52
|
+
else:
|
|
53
|
+
issues.append("praisonai.__file__ is None - namespace package detected")
|
|
54
|
+
|
|
55
|
+
except ImportError as e:
|
|
56
|
+
issues.append(f"Cannot import praisonai: {e}")
|
|
57
|
+
metadata["import_error"] = str(e)
|
|
58
|
+
|
|
59
|
+
# Check console script availability
|
|
60
|
+
praisonai_script = shutil.which("praisonai")
|
|
61
|
+
if praisonai_script:
|
|
62
|
+
metadata["console_script_path"] = praisonai_script
|
|
63
|
+
metadata["has_console_script"] = True
|
|
64
|
+
else:
|
|
65
|
+
issues.append("praisonai console script not found in PATH")
|
|
66
|
+
metadata["has_console_script"] = False
|
|
67
|
+
|
|
68
|
+
if not issues:
|
|
69
|
+
return CheckResult(
|
|
70
|
+
id="praisonai_package_structure",
|
|
71
|
+
title="PraisonAI Package Structure",
|
|
72
|
+
category=CheckCategory.PACKAGING,
|
|
73
|
+
status=CheckStatus.PASS,
|
|
74
|
+
message="Package structure is correct for both -m and script execution",
|
|
75
|
+
metadata=metadata,
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
return CheckResult(
|
|
79
|
+
id="praisonai_package_structure",
|
|
80
|
+
title="PraisonAI Package Structure",
|
|
81
|
+
category=CheckCategory.PACKAGING,
|
|
82
|
+
status=CheckStatus.FAIL,
|
|
83
|
+
message=f"Package structure issues: {'; '.join(issues)}",
|
|
84
|
+
metadata=metadata,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@register_check(
|
|
89
|
+
id="python_module_execution",
|
|
90
|
+
title="Python Module Execution",
|
|
91
|
+
description="Test if python -m praisonai works correctly",
|
|
92
|
+
category=CheckCategory.PACKAGING,
|
|
93
|
+
severity=CheckSeverity.HIGH,
|
|
94
|
+
)
|
|
95
|
+
def check_python_module_execution(config: DoctorConfig) -> CheckResult:
|
|
96
|
+
"""Test python -m praisonai execution."""
|
|
97
|
+
metadata = {"test_command": f"{sys.executable} -m praisonai --version"}
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
[sys.executable, "-m", "praisonai", "--version"],
|
|
102
|
+
capture_output=True,
|
|
103
|
+
text=True,
|
|
104
|
+
timeout=config.timeout,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
metadata["return_code"] = result.returncode
|
|
108
|
+
metadata["stdout"] = result.stdout.strip()
|
|
109
|
+
metadata["stderr"] = result.stderr.strip()
|
|
110
|
+
|
|
111
|
+
if result.returncode == 0:
|
|
112
|
+
return CheckResult(
|
|
113
|
+
id="python_module_execution",
|
|
114
|
+
title="Python Module Execution",
|
|
115
|
+
category=CheckCategory.PACKAGING,
|
|
116
|
+
status=CheckStatus.PASS,
|
|
117
|
+
message=f"python -m praisonai works: {result.stdout.strip() or result.stderr.strip()}",
|
|
118
|
+
metadata=metadata,
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
return CheckResult(
|
|
122
|
+
id="python_module_execution",
|
|
123
|
+
title="Python Module Execution",
|
|
124
|
+
category=CheckCategory.PACKAGING,
|
|
125
|
+
status=CheckStatus.FAIL,
|
|
126
|
+
message=f"python -m praisonai failed with exit code {result.returncode}",
|
|
127
|
+
metadata=metadata,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
except subprocess.TimeoutExpired:
|
|
131
|
+
return CheckResult(
|
|
132
|
+
id="python_module_execution",
|
|
133
|
+
title="Python Module Execution",
|
|
134
|
+
category=CheckCategory.PACKAGING,
|
|
135
|
+
status=CheckStatus.FAIL,
|
|
136
|
+
message=f"python -m praisonai timed out (>{config.timeout}s)",
|
|
137
|
+
metadata=metadata,
|
|
138
|
+
)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
metadata["exception"] = str(e)
|
|
141
|
+
return CheckResult(
|
|
142
|
+
id="python_module_execution",
|
|
143
|
+
title="Python Module Execution",
|
|
144
|
+
category=CheckCategory.PACKAGING,
|
|
145
|
+
status=CheckStatus.FAIL,
|
|
146
|
+
message=f"python -m praisonai execution failed: {e}",
|
|
147
|
+
metadata=metadata,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@register_check(
|
|
152
|
+
id="console_script_execution",
|
|
153
|
+
title="Console Script Execution",
|
|
154
|
+
description="Test if praisonai console script works correctly",
|
|
155
|
+
category=CheckCategory.PACKAGING,
|
|
156
|
+
severity=CheckSeverity.HIGH,
|
|
157
|
+
)
|
|
158
|
+
def check_console_script_execution(config: DoctorConfig) -> CheckResult:
|
|
159
|
+
"""Test praisonai console script execution."""
|
|
160
|
+
praisonai_script = shutil.which("praisonai")
|
|
161
|
+
|
|
162
|
+
if not praisonai_script:
|
|
163
|
+
return CheckResult(
|
|
164
|
+
id="console_script_execution",
|
|
165
|
+
title="Console Script Execution",
|
|
166
|
+
category=CheckCategory.PACKAGING,
|
|
167
|
+
status=CheckStatus.FAIL,
|
|
168
|
+
message="praisonai console script not found in PATH",
|
|
169
|
+
metadata={"script_path": None},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
metadata = {
|
|
173
|
+
"script_path": praisonai_script,
|
|
174
|
+
"test_command": f"{praisonai_script} --version"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
result = subprocess.run(
|
|
179
|
+
[praisonai_script, "--version"],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
timeout=config.timeout,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
metadata["return_code"] = result.returncode
|
|
186
|
+
metadata["stdout"] = result.stdout.strip()
|
|
187
|
+
metadata["stderr"] = result.stderr.strip()
|
|
188
|
+
|
|
189
|
+
if result.returncode == 0:
|
|
190
|
+
return CheckResult(
|
|
191
|
+
id="console_script_execution",
|
|
192
|
+
title="Console Script Execution",
|
|
193
|
+
category=CheckCategory.PACKAGING,
|
|
194
|
+
status=CheckStatus.PASS,
|
|
195
|
+
message=f"praisonai console script works: {result.stdout.strip() or result.stderr.strip()}",
|
|
196
|
+
metadata=metadata,
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
return CheckResult(
|
|
200
|
+
id="console_script_execution",
|
|
201
|
+
title="Console Script Execution",
|
|
202
|
+
category=CheckCategory.PACKAGING,
|
|
203
|
+
status=CheckStatus.FAIL,
|
|
204
|
+
message=f"praisonai console script failed with exit code {result.returncode}",
|
|
205
|
+
metadata=metadata,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except subprocess.TimeoutExpired:
|
|
209
|
+
return CheckResult(
|
|
210
|
+
id="console_script_execution",
|
|
211
|
+
title="Console Script Execution",
|
|
212
|
+
category=CheckCategory.PACKAGING,
|
|
213
|
+
status=CheckStatus.FAIL,
|
|
214
|
+
message=f"praisonai console script timed out (>{config.timeout}s)",
|
|
215
|
+
metadata=metadata,
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
metadata["exception"] = str(e)
|
|
219
|
+
return CheckResult(
|
|
220
|
+
id="console_script_execution",
|
|
221
|
+
title="Console Script Execution",
|
|
222
|
+
category=CheckCategory.PACKAGING,
|
|
223
|
+
status=CheckStatus.FAIL,
|
|
224
|
+
message=f"praisonai console script execution failed: {e}",
|
|
225
|
+
metadata=metadata,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@register_check(
|
|
230
|
+
id="windows_daemon_context",
|
|
231
|
+
title="Windows Daemon Context",
|
|
232
|
+
description="Check Windows-specific daemon execution context",
|
|
233
|
+
category=CheckCategory.PACKAGING,
|
|
234
|
+
severity=CheckSeverity.MEDIUM,
|
|
235
|
+
)
|
|
236
|
+
def check_windows_daemon_context(config: DoctorConfig) -> CheckResult:
|
|
237
|
+
"""Check Windows daemon execution context and provide recommendations."""
|
|
238
|
+
metadata = {
|
|
239
|
+
"platform": platform.system(),
|
|
240
|
+
"is_windows": platform.system().lower() == "windows"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if not metadata["is_windows"]:
|
|
244
|
+
return CheckResult(
|
|
245
|
+
id="windows_daemon_context",
|
|
246
|
+
title="Windows Daemon Context",
|
|
247
|
+
category=CheckCategory.PACKAGING,
|
|
248
|
+
status=CheckStatus.SKIP,
|
|
249
|
+
message="Not Windows - check skipped",
|
|
250
|
+
metadata=metadata,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
recommendations = []
|
|
254
|
+
warnings = []
|
|
255
|
+
|
|
256
|
+
# Check if in virtual environment
|
|
257
|
+
in_venv = hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
|
|
258
|
+
metadata["in_venv"] = in_venv
|
|
259
|
+
metadata["sys_prefix"] = sys.prefix
|
|
260
|
+
metadata["sys_executable"] = sys.executable
|
|
261
|
+
|
|
262
|
+
if in_venv:
|
|
263
|
+
venv_scripts_dir = os.path.join(sys.prefix, "Scripts")
|
|
264
|
+
metadata["venv_scripts_dir"] = venv_scripts_dir
|
|
265
|
+
|
|
266
|
+
# Check if Scripts dir exists and has praisonai.exe
|
|
267
|
+
if os.path.exists(venv_scripts_dir):
|
|
268
|
+
praisonai_exe = os.path.join(venv_scripts_dir, "praisonai.exe")
|
|
269
|
+
metadata["venv_praisonai_exe_exists"] = os.path.exists(praisonai_exe)
|
|
270
|
+
|
|
271
|
+
if os.path.exists(praisonai_exe):
|
|
272
|
+
recommendations.append(f"For daemons, use absolute path: {praisonai_exe}")
|
|
273
|
+
else:
|
|
274
|
+
warnings.append("praisonai.exe not found in venv Scripts directory")
|
|
275
|
+
else:
|
|
276
|
+
warnings.append("Virtual environment Scripts directory not found")
|
|
277
|
+
else:
|
|
278
|
+
warnings.append("Not in virtual environment - daemon may use global Python")
|
|
279
|
+
|
|
280
|
+
# Check Python executable type
|
|
281
|
+
if "WindowsApps" in sys.executable:
|
|
282
|
+
warnings.append("Using Microsoft Store Python - may cause daemon issues")
|
|
283
|
+
recommendations.append("Consider using python.org Python or conda for daemons")
|
|
284
|
+
|
|
285
|
+
# Check encoding environment
|
|
286
|
+
pythonioencoding = os.environ.get("PYTHONIOENCODING")
|
|
287
|
+
metadata["pythonioencoding"] = pythonioencoding
|
|
288
|
+
|
|
289
|
+
if not pythonioencoding:
|
|
290
|
+
recommendations.append("Set PYTHONIOENCODING=utf-8 for daemon encoding safety")
|
|
291
|
+
|
|
292
|
+
# Generate canonical daemon command
|
|
293
|
+
praisonai_script = shutil.which("praisonai")
|
|
294
|
+
if praisonai_script:
|
|
295
|
+
canonical_command = f'"{praisonai_script}"'
|
|
296
|
+
else:
|
|
297
|
+
canonical_command = f'"{sys.executable}" -m praisonai'
|
|
298
|
+
|
|
299
|
+
metadata["canonical_daemon_command"] = canonical_command
|
|
300
|
+
recommendations.append(f"Recommended daemon command: {canonical_command}")
|
|
301
|
+
|
|
302
|
+
if warnings:
|
|
303
|
+
status = CheckStatus.WARN
|
|
304
|
+
message = f"Windows daemon context has warnings: {'; '.join(warnings[:2])}"
|
|
305
|
+
else:
|
|
306
|
+
status = CheckStatus.PASS
|
|
307
|
+
message = "Windows daemon context is good"
|
|
308
|
+
|
|
309
|
+
if recommendations:
|
|
310
|
+
metadata["recommendations"] = recommendations
|
|
311
|
+
|
|
312
|
+
return CheckResult(
|
|
313
|
+
id="windows_daemon_context",
|
|
314
|
+
title="Windows Daemon Context",
|
|
315
|
+
category=CheckCategory.PACKAGING,
|
|
316
|
+
status=status,
|
|
317
|
+
message=message,
|
|
318
|
+
metadata=metadata,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@register_check(
|
|
323
|
+
id="packaging_metadata",
|
|
324
|
+
title="Packaging Metadata",
|
|
325
|
+
description="Check PraisonAI package installation metadata",
|
|
326
|
+
category=CheckCategory.PACKAGING,
|
|
327
|
+
severity=CheckSeverity.LOW,
|
|
328
|
+
)
|
|
329
|
+
def check_packaging_metadata(config: DoctorConfig) -> CheckResult:
|
|
330
|
+
"""Check PraisonAI package installation metadata."""
|
|
331
|
+
metadata = {}
|
|
332
|
+
issues = []
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
import praisonai
|
|
336
|
+
metadata["praisonai_file"] = getattr(praisonai, "__file__", None)
|
|
337
|
+
metadata["praisonai_version"] = getattr(praisonai, "__version__", "unknown")
|
|
338
|
+
|
|
339
|
+
# Check if installed via pip (using modern importlib.metadata)
|
|
340
|
+
try:
|
|
341
|
+
try:
|
|
342
|
+
from importlib.metadata import distribution, PackageNotFoundError
|
|
343
|
+
except ImportError:
|
|
344
|
+
# Python < 3.8 fallback
|
|
345
|
+
from importlib_metadata import distribution, PackageNotFoundError
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
dist = distribution("praisonai")
|
|
349
|
+
metadata["pip_version"] = dist.version
|
|
350
|
+
metadata["pip_location"] = str(dist.locate_file(""))
|
|
351
|
+
metadata["pip_installed"] = True
|
|
352
|
+
except PackageNotFoundError:
|
|
353
|
+
metadata["pip_installed"] = False
|
|
354
|
+
issues.append("Package not found in pip registry (editable install?)")
|
|
355
|
+
except ImportError:
|
|
356
|
+
metadata["importlib_metadata_available"] = False
|
|
357
|
+
|
|
358
|
+
# Check for editable install markers
|
|
359
|
+
if praisonai.__file__:
|
|
360
|
+
package_dir = os.path.dirname(praisonai.__file__)
|
|
361
|
+
# Look for .egg-link or similar editable install markers
|
|
362
|
+
site_packages = []
|
|
363
|
+
try:
|
|
364
|
+
import site
|
|
365
|
+
site_packages.extend(site.getsitepackages())
|
|
366
|
+
if hasattr(site, 'getusersitepackages'):
|
|
367
|
+
site_packages.append(site.getusersitepackages())
|
|
368
|
+
except Exception as e:
|
|
369
|
+
metadata["site_packages_probe_error"] = str(e)
|
|
370
|
+
|
|
371
|
+
is_editable = False
|
|
372
|
+
for sp in site_packages:
|
|
373
|
+
if sp and os.path.exists(sp):
|
|
374
|
+
# Check for both case variations of egg-link files
|
|
375
|
+
for egg_link_name in ("praisonai.egg-link", "PraisonAI.egg-link"):
|
|
376
|
+
egg_link = os.path.join(sp, egg_link_name)
|
|
377
|
+
if os.path.exists(egg_link):
|
|
378
|
+
is_editable = True
|
|
379
|
+
metadata["editable_install"] = True
|
|
380
|
+
metadata["egg_link_path"] = egg_link
|
|
381
|
+
break
|
|
382
|
+
if is_editable:
|
|
383
|
+
break
|
|
384
|
+
|
|
385
|
+
# Check for modern editable install markers (PEP 660)
|
|
386
|
+
try:
|
|
387
|
+
for file in os.listdir(sp):
|
|
388
|
+
if file.startswith("__editable__") and "praisonai" in file.lower() and file.endswith(".pth"):
|
|
389
|
+
is_editable = True
|
|
390
|
+
metadata["editable_install"] = True
|
|
391
|
+
metadata["editable_pth_path"] = os.path.join(sp, file)
|
|
392
|
+
break
|
|
393
|
+
except OSError:
|
|
394
|
+
pass
|
|
395
|
+
if is_editable:
|
|
396
|
+
break
|
|
397
|
+
|
|
398
|
+
if not is_editable:
|
|
399
|
+
metadata["editable_install"] = False
|
|
400
|
+
|
|
401
|
+
except ImportError as e:
|
|
402
|
+
issues.append(f"Cannot import praisonai: {e}")
|
|
403
|
+
metadata["import_error"] = str(e)
|
|
404
|
+
|
|
405
|
+
if not issues:
|
|
406
|
+
return CheckResult(
|
|
407
|
+
id="packaging_metadata",
|
|
408
|
+
title="Packaging Metadata",
|
|
409
|
+
category=CheckCategory.PACKAGING,
|
|
410
|
+
status=CheckStatus.PASS,
|
|
411
|
+
message="Package metadata is accessible",
|
|
412
|
+
metadata=metadata,
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
return CheckResult(
|
|
416
|
+
id="packaging_metadata",
|
|
417
|
+
title="Packaging Metadata",
|
|
418
|
+
category=CheckCategory.PACKAGING,
|
|
419
|
+
status=CheckStatus.WARN,
|
|
420
|
+
message=f"Package metadata issues: {'; '.join(issues)}",
|
|
421
|
+
metadata=metadata,
|
|
422
|
+
)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Performance checks for the Doctor CLI module.
|
|
3
|
+
|
|
4
|
+
Measures import times and identifies slow imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from ..models import (
|
|
11
|
+
CheckCategory,
|
|
12
|
+
CheckResult,
|
|
13
|
+
CheckStatus,
|
|
14
|
+
CheckSeverity,
|
|
15
|
+
DoctorConfig,
|
|
16
|
+
)
|
|
17
|
+
from ..registry import register_check
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _measure_import_time(module_name: str) -> tuple:
|
|
21
|
+
"""Measure import time for a module."""
|
|
22
|
+
# Remove from cache if present
|
|
23
|
+
modules_before = set(sys.modules.keys())
|
|
24
|
+
|
|
25
|
+
start = time.perf_counter()
|
|
26
|
+
try:
|
|
27
|
+
__import__(module_name)
|
|
28
|
+
elapsed = (time.perf_counter() - start) * 1000 # ms
|
|
29
|
+
return True, elapsed, None
|
|
30
|
+
except ImportError as e:
|
|
31
|
+
elapsed = (time.perf_counter() - start) * 1000
|
|
32
|
+
return False, elapsed, str(e)
|
|
33
|
+
finally:
|
|
34
|
+
# Clean up imported modules (optional, for accurate re-measurement)
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@register_check(
|
|
39
|
+
id="performance_praisonai_import",
|
|
40
|
+
title="PraisonAI Import Time",
|
|
41
|
+
description="Measure praisonai package import time",
|
|
42
|
+
category=CheckCategory.PERFORMANCE,
|
|
43
|
+
severity=CheckSeverity.INFO,
|
|
44
|
+
)
|
|
45
|
+
def check_performance_praisonai_import(config: DoctorConfig) -> CheckResult:
|
|
46
|
+
"""Measure praisonai package import time."""
|
|
47
|
+
budget_ms = config.budget_ms or 2000 # 2 second default budget
|
|
48
|
+
|
|
49
|
+
# praisonai is already imported, measure a fresh import of a submodule
|
|
50
|
+
start = time.perf_counter()
|
|
51
|
+
try:
|
|
52
|
+
# Import version module (lightweight)
|
|
53
|
+
from praisonai import version
|
|
54
|
+
elapsed = (time.perf_counter() - start) * 1000
|
|
55
|
+
|
|
56
|
+
if elapsed > budget_ms:
|
|
57
|
+
return CheckResult(
|
|
58
|
+
id="performance_praisonai_import",
|
|
59
|
+
title="PraisonAI Import Time",
|
|
60
|
+
category=CheckCategory.PERFORMANCE,
|
|
61
|
+
status=CheckStatus.WARN,
|
|
62
|
+
message=f"Import time {elapsed:.0f}ms exceeds budget {budget_ms}ms",
|
|
63
|
+
remediation="Check for slow dependencies or disk I/O issues",
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
return CheckResult(
|
|
67
|
+
id="performance_praisonai_import",
|
|
68
|
+
title="PraisonAI Import Time",
|
|
69
|
+
category=CheckCategory.PERFORMANCE,
|
|
70
|
+
status=CheckStatus.PASS,
|
|
71
|
+
message=f"Import time: {elapsed:.0f}ms (budget: {budget_ms}ms)",
|
|
72
|
+
metadata={"import_time_ms": elapsed, "budget_ms": budget_ms},
|
|
73
|
+
)
|
|
74
|
+
except ImportError as e:
|
|
75
|
+
return CheckResult(
|
|
76
|
+
id="performance_praisonai_import",
|
|
77
|
+
title="PraisonAI Import Time",
|
|
78
|
+
category=CheckCategory.PERFORMANCE,
|
|
79
|
+
status=CheckStatus.FAIL,
|
|
80
|
+
message=f"Import failed: {e}",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@register_check(
|
|
85
|
+
id="performance_praisonaiagents_import",
|
|
86
|
+
title="PraisonAI Agents Import Time",
|
|
87
|
+
description="Measure praisonaiagents package import time",
|
|
88
|
+
category=CheckCategory.PERFORMANCE,
|
|
89
|
+
severity=CheckSeverity.INFO,
|
|
90
|
+
)
|
|
91
|
+
def check_performance_praisonaiagents_import(config: DoctorConfig) -> CheckResult:
|
|
92
|
+
"""Measure praisonaiagents package import time."""
|
|
93
|
+
budget_ms = config.budget_ms or 3000 # 3 second default budget
|
|
94
|
+
|
|
95
|
+
start = time.perf_counter()
|
|
96
|
+
try:
|
|
97
|
+
import praisonaiagents
|
|
98
|
+
elapsed = (time.perf_counter() - start) * 1000
|
|
99
|
+
|
|
100
|
+
if elapsed > budget_ms:
|
|
101
|
+
return CheckResult(
|
|
102
|
+
id="performance_praisonaiagents_import",
|
|
103
|
+
title="PraisonAI Agents Import Time",
|
|
104
|
+
category=CheckCategory.PERFORMANCE,
|
|
105
|
+
status=CheckStatus.WARN,
|
|
106
|
+
message=f"Import time {elapsed:.0f}ms exceeds budget {budget_ms}ms",
|
|
107
|
+
remediation="Check for slow dependencies",
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
return CheckResult(
|
|
111
|
+
id="performance_praisonaiagents_import",
|
|
112
|
+
title="PraisonAI Agents Import Time",
|
|
113
|
+
category=CheckCategory.PERFORMANCE,
|
|
114
|
+
status=CheckStatus.PASS,
|
|
115
|
+
message=f"Import time: {elapsed:.0f}ms (budget: {budget_ms}ms)",
|
|
116
|
+
metadata={"import_time_ms": elapsed, "budget_ms": budget_ms},
|
|
117
|
+
)
|
|
118
|
+
except ImportError as e:
|
|
119
|
+
return CheckResult(
|
|
120
|
+
id="performance_praisonaiagents_import",
|
|
121
|
+
title="PraisonAI Agents Import Time",
|
|
122
|
+
category=CheckCategory.PERFORMANCE,
|
|
123
|
+
status=CheckStatus.FAIL,
|
|
124
|
+
message=f"Import failed: {e}",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@register_check(
|
|
129
|
+
id="performance_slow_imports",
|
|
130
|
+
title="Slow Import Detection",
|
|
131
|
+
description="Detect slow-loading optional dependencies",
|
|
132
|
+
category=CheckCategory.PERFORMANCE,
|
|
133
|
+
severity=CheckSeverity.INFO,
|
|
134
|
+
requires_deep=True,
|
|
135
|
+
)
|
|
136
|
+
def check_performance_slow_imports(config: DoctorConfig) -> CheckResult:
|
|
137
|
+
"""Detect slow-loading optional dependencies."""
|
|
138
|
+
modules_to_check = [
|
|
139
|
+
"litellm",
|
|
140
|
+
"chromadb",
|
|
141
|
+
"langchain",
|
|
142
|
+
"torch",
|
|
143
|
+
"transformers",
|
|
144
|
+
"pandas",
|
|
145
|
+
"numpy",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
top_n = config.top_n or 5
|
|
149
|
+
budget_ms = config.budget_ms or 1000
|
|
150
|
+
|
|
151
|
+
import_times = []
|
|
152
|
+
|
|
153
|
+
for module in modules_to_check:
|
|
154
|
+
if module in sys.modules:
|
|
155
|
+
# Already imported, skip
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
success, elapsed, error = _measure_import_time(module)
|
|
159
|
+
if success:
|
|
160
|
+
import_times.append((module, elapsed))
|
|
161
|
+
|
|
162
|
+
# Sort by time, descending
|
|
163
|
+
import_times.sort(key=lambda x: x[1], reverse=True)
|
|
164
|
+
|
|
165
|
+
slow_imports = [(m, t) for m, t in import_times if t > budget_ms]
|
|
166
|
+
top_imports = import_times[:top_n]
|
|
167
|
+
|
|
168
|
+
if slow_imports:
|
|
169
|
+
slow_str = ", ".join(f"{m}({t:.0f}ms)" for m, t in slow_imports[:3])
|
|
170
|
+
return CheckResult(
|
|
171
|
+
id="performance_slow_imports",
|
|
172
|
+
title="Slow Import Detection",
|
|
173
|
+
category=CheckCategory.PERFORMANCE,
|
|
174
|
+
status=CheckStatus.WARN,
|
|
175
|
+
message=f"{len(slow_imports)} slow import(s) detected",
|
|
176
|
+
details=f"Slow: {slow_str}",
|
|
177
|
+
metadata={"slow_imports": slow_imports, "top_imports": top_imports},
|
|
178
|
+
)
|
|
179
|
+
elif top_imports:
|
|
180
|
+
top_str = ", ".join(f"{m}({t:.0f}ms)" for m, t in top_imports[:3])
|
|
181
|
+
return CheckResult(
|
|
182
|
+
id="performance_slow_imports",
|
|
183
|
+
title="Slow Import Detection",
|
|
184
|
+
category=CheckCategory.PERFORMANCE,
|
|
185
|
+
status=CheckStatus.PASS,
|
|
186
|
+
message=f"No slow imports (top: {top_str})",
|
|
187
|
+
metadata={"top_imports": top_imports},
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
return CheckResult(
|
|
191
|
+
id="performance_slow_imports",
|
|
192
|
+
title="Slow Import Detection",
|
|
193
|
+
category=CheckCategory.PERFORMANCE,
|
|
194
|
+
status=CheckStatus.SKIP,
|
|
195
|
+
message="No optional modules to measure",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@register_check(
|
|
200
|
+
id="performance_loaded_modules",
|
|
201
|
+
title="Loaded Modules Count",
|
|
202
|
+
description="Count currently loaded Python modules",
|
|
203
|
+
category=CheckCategory.PERFORMANCE,
|
|
204
|
+
severity=CheckSeverity.INFO,
|
|
205
|
+
)
|
|
206
|
+
def check_performance_loaded_modules(config: DoctorConfig) -> CheckResult:
|
|
207
|
+
"""Count currently loaded Python modules."""
|
|
208
|
+
module_count = len(sys.modules)
|
|
209
|
+
|
|
210
|
+
# Categorize modules
|
|
211
|
+
stdlib_count = 0
|
|
212
|
+
third_party_count = 0
|
|
213
|
+
praison_count = 0
|
|
214
|
+
|
|
215
|
+
for name in sys.modules:
|
|
216
|
+
if name.startswith("praisonai"):
|
|
217
|
+
praison_count += 1
|
|
218
|
+
elif "site-packages" in str(getattr(sys.modules[name], "__file__", "")):
|
|
219
|
+
third_party_count += 1
|
|
220
|
+
else:
|
|
221
|
+
stdlib_count += 1
|
|
222
|
+
|
|
223
|
+
return CheckResult(
|
|
224
|
+
id="performance_loaded_modules",
|
|
225
|
+
title="Loaded Modules Count",
|
|
226
|
+
category=CheckCategory.PERFORMANCE,
|
|
227
|
+
status=CheckStatus.PASS,
|
|
228
|
+
message=f"{module_count} modules loaded (praison: {praison_count})",
|
|
229
|
+
metadata={
|
|
230
|
+
"total": module_count,
|
|
231
|
+
"praison": praison_count,
|
|
232
|
+
"third_party": third_party_count,
|
|
233
|
+
"stdlib": stdlib_count,
|
|
234
|
+
},
|
|
235
|
+
)
|