pyhabitat 1.0.32__py3-none-any.whl → 1.0.35__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-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/METADATA +1 -1
- {pyhabitat-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/RECORD +7 -7
- pyhabitat-build/pyhabitat/environment.py +88 -13
- {pyhabitat-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/WHEEL +0 -0
- {pyhabitat-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/entry_points.txt +0 -0
- {pyhabitat-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/licenses/LICENSE +0 -0
- {pyhabitat-1.0.32.dist-info → pyhabitat-1.0.35.dist-info}/top_level.txt +0 -0
|
@@ -4,14 +4,14 @@ pyhabitat/demo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
4
4
|
pyhabitat/environment.py,sha256=zUlZlB9GGbZviEeOkV3yk7GceqUtbfZYXloeRyOK6vU,40221
|
|
5
5
|
pyhabitat/report.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
pyhabitat/utils.py,sha256=2IGLKGAvDFGQgIzlId4ZGGTJRGG0MXR9UVH3nlQKmHg,988
|
|
7
|
-
pyhabitat-1.0.
|
|
7
|
+
pyhabitat-1.0.35.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
|
|
8
8
|
pyhabitat-build/__main__.py,sha256=6wcF1BhvoRQe4iwq1Px0GNughRXGFRBufWRdaZePu1s,99
|
|
9
9
|
pyhabitat-build/pyhabitat/__init__.py,sha256=7XNuHIDBXvjn_EORWZZUNsBS3qpvWRUha1NHwC3d6bw,1539
|
|
10
10
|
pyhabitat-build/pyhabitat/cli.py,sha256=L7VA-OOust2g6_MxedvvOr4RdnS_-5ZGP2Aj4VtdqAg,2221
|
|
11
|
-
pyhabitat-build/pyhabitat/environment.py,sha256=
|
|
11
|
+
pyhabitat-build/pyhabitat/environment.py,sha256=zUlZlB9GGbZviEeOkV3yk7GceqUtbfZYXloeRyOK6vU,40221
|
|
12
12
|
pyhabitat-build/pyhabitat/utils.py,sha256=2IGLKGAvDFGQgIzlId4ZGGTJRGG0MXR9UVH3nlQKmHg,988
|
|
13
|
-
pyhabitat-1.0.
|
|
14
|
-
pyhabitat-1.0.
|
|
15
|
-
pyhabitat-1.0.
|
|
16
|
-
pyhabitat-1.0.
|
|
17
|
-
pyhabitat-1.0.
|
|
13
|
+
pyhabitat-1.0.35.dist-info/METADATA,sha256=1dpi0OLV7wFvGCooNpCwxaUL8rhTFi_avWVTkVgqffY,11086
|
|
14
|
+
pyhabitat-1.0.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
pyhabitat-1.0.35.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
|
|
16
|
+
pyhabitat-1.0.35.dist-info/top_level.txt,sha256=zI4aHwfZxhq45fnyLM34qDaerg2Iyb4mcsOP-itrJPY,26
|
|
17
|
+
pyhabitat-1.0.35.dist-info/RECORD,,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
628
|
+
_CAN_SPAWN_SHELL = result.returncode == 0
|
|
629
|
+
return _CAN_SPAWN_SHELL
|
|
630
|
+
|
|
592
631
|
except subprocess.TimeoutExpired:
|
|
593
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|