pyhabitat 1.0.23__py3-none-any.whl → 1.0.25__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 pyhabitat might be problematic. Click here for more details.

pyhabitat/__init__.py CHANGED
@@ -27,6 +27,9 @@ from .environment import (
27
27
  edit_textfile,
28
28
  interp_path,
29
29
  main,
30
+ user_darrin_deyoung,
31
+ can_read_input,
32
+ can_spawn_shell,
30
33
  )
31
34
 
32
35
  # Optional: Set __all__ for explicit documentation and cleaner imports
@@ -57,6 +60,9 @@ __all__ = [
57
60
  'edit_textfile',
58
61
  'interp_path',
59
62
  'main',
63
+ 'user_darrin_deyoung',
64
+ 'can_read_input',
65
+ 'can_spawn_shell',
60
66
  ]
61
67
 
62
- __version__ = get_version()
68
+ __version__ = get_version()
pyhabitat/__main__.py CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env python3
1
2
  from pyhabitat.cli import run_cli
2
3
 
3
4
  if __name__ == "__main__":
pyhabitat/cli.py CHANGED
@@ -2,6 +2,7 @@ import argparse
2
2
  from pathlib import Path
3
3
  from pyhabitat.environment import main
4
4
  from pyhabitat.utils import get_version
5
+ import pyhabitat
5
6
 
6
7
  def run_cli():
7
8
  """Parse CLI arguments and run the pyhabitat environment report."""
@@ -28,6 +29,32 @@ def run_cli():
28
29
  action="store_true",
29
30
  help="Enable verbose debug output",
30
31
  )
32
+ parser.add_argument(
33
+ "--list",
34
+ action="store_true",
35
+ help="List available callable functions in pyhabitat"
36
+ )
37
+
38
+ parser.add_argument(
39
+ "command",
40
+ nargs="?",
41
+ help="Function name to run (or use --list)",
42
+ )
43
+
44
+
31
45
  args = parser.parse_args()
32
- main(path=Path(args.path) if args.path else None, debug=args.debug)
33
46
 
47
+ if args.list:
48
+ for name in dir(pyhabitat):
49
+ if callable(getattr(pyhabitat, name)) and not name.startswith("_"):
50
+ print(name)
51
+ return
52
+
53
+ if args.command:
54
+ func = getattr(pyhabitat, args.command, None)
55
+ if callable(func):
56
+ print(func())
57
+ else:
58
+ print(f"Unknown function: {args.command}")
59
+
60
+ main(path=Path(args.path) if args.path else None, debug=args.debug)
pyhabitat/environment.py CHANGED
@@ -14,6 +14,8 @@ import subprocess
14
14
  import io
15
15
  import zipfile
16
16
  import logging
17
+ import getpass
18
+ import select
17
19
 
18
20
  __all__ = [
19
21
  'matplotlib_is_available_for_gui_plotting',
@@ -41,13 +43,17 @@ __all__ = [
41
43
  'in_repl',
42
44
  'interp_path',
43
45
  'main',
46
+ 'user_darrin_deyoung',
47
+ 'can_read_input',
48
+ 'can_spawn_shell',
44
49
  ]
45
50
 
46
51
  # Global cache for tkinter and matplotlib (mpl) availability
47
52
  _TKINTER_AVAILABILITY: bool | None = None
48
53
  _MATPLOTLIB_EXPORT_AVAILABILITY: bool | None = None
49
54
  _MATPLOTLIB_WINDOWED_AVAILABILITY: bool | None = None
50
-
55
+ _CAN_SPAWN_SHELL: bool | None = None
56
+ _CAN_READ_INPUT: bool | None = None
51
57
 
52
58
  # --- GUI CHECKS ---
53
59
  def matplotlib_is_available_for_gui_plotting(termux_has_gui=False):
@@ -538,10 +544,66 @@ def interactive_terminal_is_available():
538
544
  without getting lost in a log or lost entirely.
539
545
 
540
546
  """
547
+ # Address walmart demo unit edge case, fast check, though this might hamstring othwrwise successful processes
548
+ if in_repl() and user_darrin_deyoung():
549
+ return False
550
+ # A new shell can be launched to print stuff
551
+ if not can_spawn_shell():
552
+ return False
553
+ # A user can interact with a console, providing input
554
+ if not can_read_input():
555
+ return False
541
556
  # Check if a tty is attached to stdin
542
557
  return sys.stdin.isatty() and sys.stdout.isatty()
543
-
544
558
 
559
+ def user_darrin_deyoung():
560
+ """Common demo unit undicator, edge case that is unable to launch terminal"""
561
+ if not on_windows():
562
+ return False
563
+ username = getpass.getuser()
564
+ return username.lower() == "darrin deyoung"
565
+
566
+ def can_spawn_shell(override_known:bool=False)->bool:
567
+ """Check if a shell command can be executed successfully."""
568
+ global _CAN_SPAWN_SHELL
569
+ if _CAN_SPAWN_SHELL is not None and override_known is False:
570
+ return _CAN_SPAWN_SHELL
571
+ try:
572
+ result = subprocess.run( ['echo', 'hello'],
573
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
574
+ timeout=2 )
575
+ _CAN_SPAWN_SHELL = True
576
+ return result.returncode == 0
577
+ except subprocess.TimeoutExpired:
578
+ logging.debug("Shell spawn failed: TimeoutExpired")
579
+ _CAN_SPAWN_SHELL = False
580
+ return False
581
+ except subprocess.SubprocessError:
582
+ logging.debug("Shell spawn failed: SubprocessError")
583
+ _CAN_SPAWN_SHELL = False
584
+ return False
585
+ except OSError:
586
+ _CAN_SPAWN_SHELL = False
587
+ logging.debug("Shell spawn failed: OSError (likely permission or missing binary)")
588
+ return False
589
+
590
+ def can_read_input(override_known:bool=False)-> bool:
591
+ """Check if input is readable from stdin."""
592
+ global _CAN_READ_INPUT
593
+ if _CAN_READ_INPUT is not None and override_known is False:
594
+ return _CAN_READ_INPUT
595
+ try:
596
+ _CAN_READ_INPUT = select.select([sys.stdin], [], [], 0.1)[0]
597
+ return _CAN_READ_INPUT
598
+ except ValueError:
599
+ logging.debug("Input check failed: ValueError (invalid file descriptor)")
600
+ _CAN_READ_INPUT = False
601
+ return False
602
+ except OSError:
603
+ logging.debug("Input check failed: OSError (likely I/O issue)")
604
+ _CAN_READ_INPUT = False
605
+ return False
606
+
545
607
  # --- Browser Check ---
546
608
  def web_browser_is_available() -> bool:
547
609
  """ Check if a web browser can be launched in the current environment."""
@@ -833,6 +895,13 @@ def main(path=None, debug=False):
833
895
  print("=== PyHabitat Report Complete ===")
834
896
  print("=================================")
835
897
  print("")
836
-
898
+ if is_pyz(): # and is_repl():
899
+ # Keep window open. This iteration is non rigorous.
900
+ # To address use pf Python launcher from Windows Store to launch downoaded .pyz, which closed quickly
901
+ try:
902
+ input("Press Return to Exit...")
903
+ except Exception as e:
904
+ logging.debug("input() failed")
905
+
837
906
  if __name__ == "__main__":
838
907
  main(debug=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.23
3
+ Version: 1.0.25
4
4
  Summary: A lightweight library for detecting system environment, GUI, and build properties.
5
5
  Author-email: George Clayton Bennett <george.bennett@memphistn.gov>
6
6
  License-Expression: MIT
@@ -0,0 +1,11 @@
1
+ pyhabitat/__init__.py,sha256=WI0Mx7cWQXrAXRX-AxTDjY_Jn8V5DWbAdJHI-2W5J3Y,1485
2
+ pyhabitat/__main__.py,sha256=6wcF1BhvoRQe4iwq1Px0GNughRXGFRBufWRdaZePu1s,99
3
+ pyhabitat/cli.py,sha256=oNZFFobkUhpNGTsizHmgINr9O79f5ZqwSJdtnbNe15E,1670
4
+ pyhabitat/environment.py,sha256=V489_X3uX7GcszEBNo16dG2kxOP9gA86cJwG99r-D20,36616
5
+ pyhabitat/utils.py,sha256=h-TPwxZr93LOvjrIrDrsBWdU2Vhc4FUvAhDqIv-N0GQ,537
6
+ pyhabitat-1.0.25.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
7
+ pyhabitat-1.0.25.dist-info/METADATA,sha256=nzq03FJd4aJUAdBinWwwyxaSVpibhBk4gw_W-u9UeFw,10900
8
+ pyhabitat-1.0.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ pyhabitat-1.0.25.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
10
+ pyhabitat-1.0.25.dist-info/top_level.txt,sha256=zXYK44Qu8EqxUETREvd2diMUaB5JiGRErkwFaoLQnnI,10
11
+ pyhabitat-1.0.25.dist-info/RECORD,,
@@ -1,72 +0,0 @@
1
- #from .cli import run_cli
2
-
3
- from .environment import (
4
- in_repl,
5
- on_termux,
6
- on_windows,
7
- on_apple,
8
- on_linux,
9
- on_ish_alpine,
10
- on_android,
11
- on_freebsd,
12
- is_elf,
13
- is_windows_portable_executable,
14
- is_macos_executable,
15
- is_pyz,
16
- is_pipx,
17
- is_python_script,
18
- as_frozen,
19
- as_pyinstaller,
20
- interp_path,
21
- tkinter_is_available,
22
- matplotlib_is_available_for_gui_plotting,
23
- matplotlib_is_available_for_headless_image_export,
24
- web_browser_is_available,
25
- interactive_terminal_is_available
26
- )
27
-
28
- def main():
29
- print("PyHabitat Environment Report")
30
- print("===========================")
31
- print("\nInterpreter Checks // Based on sys.executable()")
32
- print("-----------------------------")
33
- print(f"interp_path(): {interp_path()}")
34
- print(f"is_elf(interp_path()): {is_elf(interp_path())}")
35
- print(f"is_windows_portable_executable(interp_path()): {is_windows_portable_executable(interp_path())}")
36
- print(f"is_macos_executable(interp_path()): {is_macos_executable(interp_path())}")
37
- print(f"is_pyz(interp_path()): {is_pyz(interp_path())}")
38
- print(f"is_pipx(interp_path()): {is_pipx(interp_path())}")
39
- print(f"is_python_script(interp_path()): {is_python_script(interp_path())}")
40
- print("\nCurrent Environment Check // Based on sys.argv[0]")
41
- print("-----------------------------")
42
- print(f"is_elf(): {is_elf()}")
43
- print(f"is_windows_portable_executable(): {is_windows_portable_executable()}")
44
- print(f"is_macos_executable(): {is_macos_executable()}")
45
- print(f"is_pyz(): {is_pyz()}")
46
- print(f"is_pipx(): {is_pipx()}")
47
- print(f"is_python_script(): {is_python_script()}")
48
- print(f"\nCurrent Build Checks // Based on hasattr(sys,..) and getattr(sys,..)")
49
- print("------------------------------")
50
- print(f"in_repl(): {in_repl()}")
51
- print(f"as_frozen(): {as_frozen()}")
52
- print(f"as_pyinstaller(): {as_pyinstaller()}")
53
- print("\nOperating System Checks // Based on platform.system()")
54
- print("------------------------------")
55
- print(f"on_termux(): {on_termux()}")
56
- print(f"on_windows(): {on_windows()}")
57
- print(f"on_apple(): {on_apple()}")
58
- print(f"on_linux(): {on_linux()}")
59
- print(f"on_ish_alpine(): {on_ish_alpine()}")
60
- print(f"on_android(): {on_android()}")
61
- print(f"on_freebsd(): {on_freebsd()}")
62
- print("\nCapability Checks")
63
- print("-------------------------")
64
- print(f"tkinter_is_available(): {tkinter_is_available()}")
65
- print(f"matplotlib_is_available_for_gui_plotting(): {matplotlib_is_available_for_gui_plotting()}")
66
- print(f"matplotlib_is_available_for_headless_image_export(): {matplotlib_is_available_for_headless_image_export()}")
67
- print(f"web_browser_is_available(): {web_browser_is_available()}")
68
- print(f"interactive_terminal_is_available(): {interactive_terminal_is_available()}")
69
-
70
- if __name__ == "__main__":
71
- main()
72
- #run_cli()
@@ -1,12 +0,0 @@
1
- pyhabitat/__init__.py,sha256=spekwjNKjr9i8HgPmShCMpNFzol3okeQw3t2JReqtaY,1346
2
- pyhabitat/__main__.py,sha256=qlOKShd_Xk0HGpYOySXQSQe3K60a9wwrstCAceJkKIs,76
3
- pyhabitat/__main__stable.py,sha256=UACpHLrr_Rmf0L5dJCEae6kFzLn7dqCqIri68IBnb10,2910
4
- pyhabitat/cli.py,sha256=80t9cOwfmDktnHnhGHL-iEgJBXkB0bVCJBPrVWzWUPU,980
5
- pyhabitat/environment.py,sha256=CwAJDYRNbA5Dzb7feHptxmgKb4_IayQGA_yRGHEpse0,33876
6
- pyhabitat/utils.py,sha256=h-TPwxZr93LOvjrIrDrsBWdU2Vhc4FUvAhDqIv-N0GQ,537
7
- pyhabitat-1.0.23.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
8
- pyhabitat-1.0.23.dist-info/METADATA,sha256=9_vmgovmq-FWmFQM2K8kQO1qjrrG8B8t0yvkqzbvTbA,10900
9
- pyhabitat-1.0.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- pyhabitat-1.0.23.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
11
- pyhabitat-1.0.23.dist-info/top_level.txt,sha256=zXYK44Qu8EqxUETREvd2diMUaB5JiGRErkwFaoLQnnI,10
12
- pyhabitat-1.0.23.dist-info/RECORD,,