pyhabitat 1.0.32__tar.gz → 1.0.35__tar.gz

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.

Files changed (21) hide show
  1. {pyhabitat-1.0.32/pyhabitat.egg-info → pyhabitat-1.0.35}/PKG-INFO +1 -1
  2. {pyhabitat-1.0.32/pyhabitat-build → pyhabitat-1.0.35}/pyhabitat/environment.py +88 -13
  3. {pyhabitat-1.0.32 → pyhabitat-1.0.35/pyhabitat.egg-info}/PKG-INFO +1 -1
  4. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyproject.toml +1 -1
  5. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/LICENSE +0 -0
  6. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/README.md +0 -0
  7. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat/__init__.py +0 -0
  8. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat/cli.py +0 -0
  9. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat/demo.py +0 -0
  10. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat/report.py +0 -0
  11. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat/utils.py +0 -0
  12. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat-build/__main__.py +0 -0
  13. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat-build/pyhabitat/__init__.py +0 -0
  14. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat-build/pyhabitat/cli.py +0 -0
  15. {pyhabitat-1.0.32 → pyhabitat-1.0.35/pyhabitat-build}/pyhabitat/environment.py +0 -0
  16. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat-build/pyhabitat/utils.py +0 -0
  17. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat.egg-info/SOURCES.txt +0 -0
  18. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat.egg-info/dependency_links.txt +0 -0
  19. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat.egg-info/entry_points.txt +0 -0
  20. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/pyhabitat.egg-info/top_level.txt +0 -0
  21. {pyhabitat-1.0.32 → pyhabitat-1.0.35}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.32
3
+ Version: 1.0.35
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
@@ -17,6 +17,12 @@ import logging
17
17
  import getpass
18
18
  import select
19
19
 
20
+ # On Windows, we need the msvcrt module for non-blocking I/O
21
+ try:
22
+ import msvcrt
23
+ except ImportError:
24
+ msvcrt = None
25
+
20
26
  __all__ = [
21
27
  'matplotlib_is_available_for_gui_plotting',
22
28
  'matplotlib_is_available_for_headless_image_export',
@@ -556,21 +562,46 @@ def interactive_terminal_is_available():
556
562
  then typer.prompt() or input() will work reliably,
557
563
  without getting lost in a log or lost entirely.
558
564
 
565
+ Solution correctly identifies that true interactivity requires:
566
+ (1) a TTY (potential) connection
567
+ (2) the ability to execute
568
+ (3) the ability to read I/O
569
+ (4) ignores known limitatons in restrictive environments
570
+
571
+ Jargon:
572
+ A TTY, short for Teletypewriter or TeleTYpe,
573
+ is a conceptual or physical device that serves
574
+ as the interface for a user to interact with
575
+ a computer system.
559
576
  """
560
577
  # Address walmart demo unit edge case, fast check, though this might hamstring othwrwise successful processes
561
578
  if user_darrin_deyoung():
562
579
  return False
580
+
581
+ # Check if a tty is attached to stdin,
582
+ # quick failure here if not before testing spwaning and reading
583
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
584
+ return False
585
+
563
586
  # Check of a new shell can be launched to print stuff
564
587
  if not can_spawn_shell():
565
588
  return False
589
+
566
590
  # A user can interact with a console, providing input
567
591
  #if not can_read_input():
568
592
  # return False
569
- # Check if a tty is attached to stdin
593
+
570
594
  return sys.stdin.isatty() and sys.stdout.isatty()
571
595
 
572
596
  def user_darrin_deyoung():
573
597
  """Common demo unit undicator, edge case that is unable to launch terminal"""
598
+ # Enable teating on non-Windows, non-demo systems
599
+ # where this function would otherwise return False.
600
+ # Linux: `export USER_DARRIN_DEYOUNG=True`
601
+ if os.getenv('USER_DARRIN_DEYOUNG','').lower() == "true":
602
+ print("env var USER_DARRIN_DEYOUNG is set to True.")
603
+ return True
604
+ # Darrin Deyoung is the typical username on demo-mode Windows systems
574
605
  if not on_windows():
575
606
  return False
576
607
  username = getpass.getuser()
@@ -581,25 +612,33 @@ def can_spawn_shell(override_known:bool=False)->bool:
581
612
  global _CAN_SPAWN_SHELL
582
613
  if _CAN_SPAWN_SHELL is not None and override_known is False:
583
614
  return _CAN_SPAWN_SHELL
584
- try:
585
- result = subprocess.run( ['echo', 'hello'],
586
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
587
- timeout=2 )
588
-
589
- _CAN_SPAWN_SHELL = True
615
+
616
+ try:
617
+ # Use a simple, universally applicable command with shell=True
618
+ # 'true' on Linux/macOS, or a basic command on Windows via cmd.exe
619
+ # A simple 'echo' or 'exit 0' would also work
620
+ result = subprocess.run(
621
+ 'exit 0', # A shell-internal command that succeeds on most shells
622
+ stdout=subprocess.PIPE,
623
+ stderr=subprocess.PIPE,
624
+ timeout=2,
625
+ shell=True # <--- ESSENTIAL for cross-platform reliability
626
+ )
590
627
 
591
- return result.returncode == 0
628
+ _CAN_SPAWN_SHELL = result.returncode == 0
629
+ return _CAN_SPAWN_SHELL
630
+
592
631
  except subprocess.TimeoutExpired:
593
- logging.debug("Shell spawn failed: TimeoutExpired")
632
+ print("Shell spawn failed: TimeoutExpired")
594
633
  _CAN_SPAWN_SHELL = result.returncode == 0
595
634
  return _CAN_SPAWN_SHELL
596
635
  except subprocess.SubprocessError:
597
- logging.debug("Shell spawn failed: SubprocessError")
636
+ print("Shell spawn failed: SubprocessError")
598
637
  _CAN_SPAWN_SHELL = False
599
638
  return False
600
639
  except OSError:
601
640
  _CAN_SPAWN_SHELL = False
602
- logging.debug("Shell spawn failed: OSError (likely permission or missing binary)")
641
+ print("Shell spawn failed: OSError (likely permission or missing binary)")
603
642
  return False
604
643
 
605
644
  def can_read_input(override_known:bool=False)-> bool:
@@ -607,10 +646,43 @@ def can_read_input(override_known:bool=False)-> bool:
607
646
  global _CAN_READ_INPUT
608
647
  if _CAN_READ_INPUT is not None and override_known is False:
609
648
  return _CAN_READ_INPUT
649
+
650
+ # --- 1. Windows Specific Check (msvcrt) ---
651
+ if msvcrt is not None and sys.stdin.isatty():
652
+ try:
653
+ # msvcrt.kbhit() checks if a keyboard hit is present
654
+ # We don't read the input yet, just check if it's there
655
+ _CAN_READ_INPUT = msvcrt.kbhit()
656
+ # If kbhit returns True, it means a key press is waiting.
657
+ # We assume if the terminal *is* a TTY, it *can* read input.
658
+ # We can't actually call input() without blocking, so we check TTY instead.
659
+ if _CAN_READ_INPUT:
660
+ return True
661
+
662
+ # Since we are checking if a *user can* interact, if we are in a TTY,
663
+ # we assume the capability exists, even if nothing is currently buffered.
664
+ # This prevents the false negative when no key is pressed.
665
+ _CAN_READ_INPUT = True
666
+ return True
667
+
668
+ except Exception as e:
669
+ # Catch errors in the kbhit check itself
670
+ logging.debug(f"msvcrt check failed: {e}")
671
+ pass # Fall through to the select check
672
+
673
+ # --- 2. POSIX/General Check (select) ---
674
+ # This block is reliable on Linux/macOS and other POSIX systems.
610
675
  try:
611
- # ERROR THIS IS NOT A BOOLEAN, IS RETURNS []
676
+ # _CAN_READ_INPUT is assigned the read-ready list ([] or [sys.stdin])
677
+ # The return value is then the boolean conversion of that list's truthiness.
678
+ # 1. select.select(...) returns a 3-element tuple.
679
+ # 2. [0] gets the read-ready list (rlist).
680
+ # 3. Wrapping the result in bool() converts the list's truth value:
681
+ # - [] becomes False
682
+ # - [sys.stdin] becomes True
612
683
  _CAN_READ_INPUT = select.select([sys.stdin], [], [], 0.1)[0]
613
- return _CAN_READ_INPUT
684
+ # Return the boolean value of the list: True if [sys.stdin], False if []
685
+ return bool(_CAN_READ_INPUT) # <--- Requied to convert list to boolean
614
686
  except ValueError:
615
687
  logging.debug("Input check failed: ValueError (invalid file descriptor)")
616
688
  _CAN_READ_INPUT = False
@@ -619,6 +691,9 @@ def can_read_input(override_known:bool=False)-> bool:
619
691
  logging.debug("Input check failed: OSError (likely I/O issue)")
620
692
  _CAN_READ_INPUT = False
621
693
  return False
694
+
695
+ # Final fallback: if nothing worked, assume False
696
+ return False
622
697
 
623
698
  # --- Browser Check ---
624
699
  def web_browser_is_available() -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.32
3
+ Version: 1.0.35
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
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyhabitat"
9
- version = "1.0.32"
9
+ version = "1.0.35"
10
10
  #dynamic = ["version"]
11
11
  authors = [
12
12
  { name="George Clayton Bennett", email="george.bennett@memphistn.gov" },
File without changes
File without changes
File without changes
File without changes
File without changes