pyhabitat 1.0.29__tar.gz → 1.0.32__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.29/pyhabitat.egg-info → pyhabitat-1.0.32}/PKG-INFO +6 -2
  2. {pyhabitat-1.0.29/pyhabitat-build → pyhabitat-1.0.32}/pyhabitat/__init__.py +4 -0
  3. {pyhabitat-1.0.29/pyhabitat-build → pyhabitat-1.0.32}/pyhabitat/cli.py +1 -1
  4. pyhabitat-1.0.32/pyhabitat/demo.py +0 -0
  5. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat/environment.py +110 -18
  6. pyhabitat-1.0.32/pyhabitat/report.py +0 -0
  7. {pyhabitat-1.0.29 → pyhabitat-1.0.32/pyhabitat-build}/pyhabitat/__init__.py +4 -0
  8. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat-build/pyhabitat/environment.py +24 -7
  9. {pyhabitat-1.0.29 → pyhabitat-1.0.32/pyhabitat.egg-info}/PKG-INFO +6 -2
  10. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat.egg-info/SOURCES.txt +2 -0
  11. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyproject.toml +15 -5
  12. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/LICENSE +0 -0
  13. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/README.md +0 -0
  14. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat/utils.py +0 -0
  15. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat-build/__main__.py +0 -0
  16. {pyhabitat-1.0.29 → pyhabitat-1.0.32/pyhabitat-build}/pyhabitat/cli.py +0 -0
  17. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat-build/pyhabitat/utils.py +0 -0
  18. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat.egg-info/dependency_links.txt +0 -0
  19. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat.egg-info/entry_points.txt +0 -0
  20. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/pyhabitat.egg-info/top_level.txt +0 -0
  21. {pyhabitat-1.0.29 → pyhabitat-1.0.32}/setup.cfg +0 -0
@@ -1,13 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.29
3
+ Version: 1.0.32
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
7
7
  Keywords: environment,os-detection,gui,build-system
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Classifier: Topic :: System :: Systems Administration
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Environment :: Console
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Utilities
11
15
  Requires-Python: >=3.7
12
16
  Description-Content-Type: text/markdown
13
17
  License-File: LICENSE
@@ -30,6 +30,8 @@ from .environment import (
30
30
  user_darrin_deyoung,
31
31
  can_read_input,
32
32
  can_spawn_shell,
33
+ is_ascii,
34
+ is_binary,
33
35
  )
34
36
 
35
37
  # Optional: Set __all__ for explicit documentation and cleaner imports
@@ -63,6 +65,8 @@ __all__ = [
63
65
  'user_darrin_deyoung',
64
66
  'can_read_input',
65
67
  'can_spawn_shell',
68
+ 'is_ascii',
69
+ 'is_binary',
66
70
  ]
67
71
 
68
72
  __version__ = get_version()
@@ -16,7 +16,7 @@ def run_cli():
16
16
  parser.add_argument(
17
17
  '-v', '--version',
18
18
  action='version',
19
- version=f'%(prog)s {current_version}'
19
+ version=f'PyHabitat {current_version}'
20
20
  )
21
21
  # Add the path argument
22
22
  parser.add_argument(
File without changes
@@ -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',
@@ -512,6 +518,19 @@ def is_python_script(path: Path | str | None = None, debug: bool = False, suppre
512
518
  return False
513
519
  return exec_path.suffix.lower() == '.py'
514
520
 
521
+ # --- File encoding check ---
522
+ def is_binary(path:str|Path|None=None)->bool:
523
+ """
524
+ Target file is encoded as binary.
525
+ """
526
+ pass
527
+
528
+ def is_ascii(path:str|Path|None=None)->bool:
529
+ """
530
+ Target file is encoded as ascii, plaintext.
531
+ """
532
+ pass
533
+
515
534
  # --- Interpreter Check ---
516
535
 
517
536
  def interp_path(debug: bool = False) -> str:
@@ -543,21 +562,46 @@ def interactive_terminal_is_available():
543
562
  then typer.prompt() or input() will work reliably,
544
563
  without getting lost in a log or lost entirely.
545
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.
546
576
  """
547
577
  # Address walmart demo unit edge case, fast check, though this might hamstring othwrwise successful processes
548
- if in_repl() and user_darrin_deyoung():
578
+ if user_darrin_deyoung():
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()):
549
584
  return False
550
- # A new shell can be launched to print stuff
585
+
586
+ # Check of a new shell can be launched to print stuff
551
587
  if not can_spawn_shell():
552
588
  return False
589
+
553
590
  # A user can interact with a console, providing input
554
- if not can_read_input():
555
- return False
556
- # Check if a tty is attached to stdin
591
+ #if not can_read_input():
592
+ # return False
593
+
557
594
  return sys.stdin.isatty() and sys.stdout.isatty()
558
595
 
559
596
  def user_darrin_deyoung():
560
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
561
605
  if not on_windows():
562
606
  return False
563
607
  username = getpass.getuser()
@@ -568,23 +612,33 @@ def can_spawn_shell(override_known:bool=False)->bool:
568
612
  global _CAN_SPAWN_SHELL
569
613
  if _CAN_SPAWN_SHELL is not None and override_known is False:
570
614
  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
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
+ )
627
+
628
+ _CAN_SPAWN_SHELL = result.returncode == 0
629
+ return _CAN_SPAWN_SHELL
630
+
577
631
  except subprocess.TimeoutExpired:
578
- logging.debug("Shell spawn failed: TimeoutExpired")
579
- _CAN_SPAWN_SHELL = False
580
- return False
632
+ print("Shell spawn failed: TimeoutExpired")
633
+ _CAN_SPAWN_SHELL = result.returncode == 0
634
+ return _CAN_SPAWN_SHELL
581
635
  except subprocess.SubprocessError:
582
- logging.debug("Shell spawn failed: SubprocessError")
636
+ print("Shell spawn failed: SubprocessError")
583
637
  _CAN_SPAWN_SHELL = False
584
638
  return False
585
639
  except OSError:
586
640
  _CAN_SPAWN_SHELL = False
587
- logging.debug("Shell spawn failed: OSError (likely permission or missing binary)")
641
+ print("Shell spawn failed: OSError (likely permission or missing binary)")
588
642
  return False
589
643
 
590
644
  def can_read_input(override_known:bool=False)-> bool:
@@ -592,9 +646,43 @@ def can_read_input(override_known:bool=False)-> bool:
592
646
  global _CAN_READ_INPUT
593
647
  if _CAN_READ_INPUT is not None and override_known is False:
594
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.
595
675
  try:
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
596
683
  _CAN_READ_INPUT = select.select([sys.stdin], [], [], 0.1)[0]
597
- 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
598
686
  except ValueError:
599
687
  logging.debug("Input check failed: ValueError (invalid file descriptor)")
600
688
  _CAN_READ_INPUT = False
@@ -603,6 +691,9 @@ def can_read_input(override_known:bool=False)-> bool:
603
691
  logging.debug("Input check failed: OSError (likely I/O issue)")
604
692
  _CAN_READ_INPUT = False
605
693
  return False
694
+
695
+ # Final fallback: if nothing worked, assume False
696
+ return False
606
697
 
607
698
  # --- Browser Check ---
608
699
  def web_browser_is_available() -> bool:
@@ -620,6 +711,7 @@ def web_browser_is_available() -> bool:
620
711
  if shutil.which("xdg-open"):
621
712
  return True
622
713
  return False
714
+
623
715
 
624
716
  # --- LAUNCH MECHANISMS BASED ON ENVIRONMENT ---
625
717
  def edit_textfile(path: Path | str | None = None) -> None:
@@ -899,7 +991,7 @@ def main(path=None, debug=False):
899
991
  # Keep window open. This iteration is non rigorous.
900
992
  # To address use pf Python launcher from Windows Store to launch downoaded .pyz, which closed quickly
901
993
  try:
902
- input("Press Return to Exit...")
994
+ input("Press Return to Continue...")
903
995
  except Exception as e:
904
996
  logging.debug("input() failed")
905
997
 
File without changes
@@ -30,6 +30,8 @@ from .environment import (
30
30
  user_darrin_deyoung,
31
31
  can_read_input,
32
32
  can_spawn_shell,
33
+ is_ascii,
34
+ is_binary,
33
35
  )
34
36
 
35
37
  # Optional: Set __all__ for explicit documentation and cleaner imports
@@ -63,6 +65,8 @@ __all__ = [
63
65
  'user_darrin_deyoung',
64
66
  'can_read_input',
65
67
  'can_spawn_shell',
68
+ 'is_ascii',
69
+ 'is_binary',
66
70
  ]
67
71
 
68
72
  __version__ = get_version()
@@ -512,6 +512,19 @@ def is_python_script(path: Path | str | None = None, debug: bool = False, suppre
512
512
  return False
513
513
  return exec_path.suffix.lower() == '.py'
514
514
 
515
+ # --- File encoding check ---
516
+ def is_binary(path:str|Path|None=None)->bool:
517
+ """
518
+ Target file is encoded as binary.
519
+ """
520
+ pass
521
+
522
+ def is_ascii(path:str|Path|None=None)->bool:
523
+ """
524
+ Target file is encoded as ascii, plaintext.
525
+ """
526
+ pass
527
+
515
528
  # --- Interpreter Check ---
516
529
 
517
530
  def interp_path(debug: bool = False) -> str:
@@ -545,14 +558,14 @@ def interactive_terminal_is_available():
545
558
 
546
559
  """
547
560
  # Address walmart demo unit edge case, fast check, though this might hamstring othwrwise successful processes
548
- if in_repl() and user_darrin_deyoung():
561
+ if user_darrin_deyoung():
549
562
  return False
550
- # A new shell can be launched to print stuff
563
+ # Check of a new shell can be launched to print stuff
551
564
  if not can_spawn_shell():
552
565
  return False
553
566
  # A user can interact with a console, providing input
554
- if not can_read_input():
555
- return False
567
+ #if not can_read_input():
568
+ # return False
556
569
  # Check if a tty is attached to stdin
557
570
  return sys.stdin.isatty() and sys.stdout.isatty()
558
571
 
@@ -572,12 +585,14 @@ def can_spawn_shell(override_known:bool=False)->bool:
572
585
  result = subprocess.run( ['echo', 'hello'],
573
586
  stdout=subprocess.PIPE, stderr=subprocess.PIPE,
574
587
  timeout=2 )
588
+
575
589
  _CAN_SPAWN_SHELL = True
590
+
576
591
  return result.returncode == 0
577
592
  except subprocess.TimeoutExpired:
578
593
  logging.debug("Shell spawn failed: TimeoutExpired")
579
- _CAN_SPAWN_SHELL = False
580
- return False
594
+ _CAN_SPAWN_SHELL = result.returncode == 0
595
+ return _CAN_SPAWN_SHELL
581
596
  except subprocess.SubprocessError:
582
597
  logging.debug("Shell spawn failed: SubprocessError")
583
598
  _CAN_SPAWN_SHELL = False
@@ -593,6 +608,7 @@ def can_read_input(override_known:bool=False)-> bool:
593
608
  if _CAN_READ_INPUT is not None and override_known is False:
594
609
  return _CAN_READ_INPUT
595
610
  try:
611
+ # ERROR THIS IS NOT A BOOLEAN, IS RETURNS []
596
612
  _CAN_READ_INPUT = select.select([sys.stdin], [], [], 0.1)[0]
597
613
  return _CAN_READ_INPUT
598
614
  except ValueError:
@@ -620,6 +636,7 @@ def web_browser_is_available() -> bool:
620
636
  if shutil.which("xdg-open"):
621
637
  return True
622
638
  return False
639
+
623
640
 
624
641
  # --- LAUNCH MECHANISMS BASED ON ENVIRONMENT ---
625
642
  def edit_textfile(path: Path | str | None = None) -> None:
@@ -899,7 +916,7 @@ def main(path=None, debug=False):
899
916
  # Keep window open. This iteration is non rigorous.
900
917
  # To address use pf Python launcher from Windows Store to launch downoaded .pyz, which closed quickly
901
918
  try:
902
- input("Press Return to Exit...")
919
+ input("Press Return to Continue...")
903
920
  except Exception as e:
904
921
  logging.debug("input() failed")
905
922
 
@@ -1,13 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.29
3
+ Version: 1.0.32
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
7
7
  Keywords: environment,os-detection,gui,build-system
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Classifier: Topic :: System :: Systems Administration
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Environment :: Console
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Utilities
11
15
  Requires-Python: >=3.7
12
16
  Description-Content-Type: text/markdown
13
17
  License-File: LICENSE
@@ -3,7 +3,9 @@ README.md
3
3
  pyproject.toml
4
4
  pyhabitat/__init__.py
5
5
  pyhabitat/cli.py
6
+ pyhabitat/demo.py
6
7
  pyhabitat/environment.py
8
+ pyhabitat/report.py
7
9
  pyhabitat/utils.py
8
10
  pyhabitat-build/__main__.py
9
11
  pyhabitat-build/pyhabitat/__init__.py
@@ -6,21 +6,26 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyhabitat"
9
- version = "1.0.29"
10
- #dynamic = ["version"] #
9
+ version = "1.0.32"
10
+ #dynamic = ["version"]
11
11
  authors = [
12
12
  { name="George Clayton Bennett", email="george.bennett@memphistn.gov" },
13
13
  ]
14
14
  description = "A lightweight library for detecting system environment, GUI, and build properties."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.7"
17
- license = "MIT"
17
+ license = "MIT"
18
18
  keywords = ["environment", "os-detection", "gui", "build-system"]
19
- classifiers = [
19
+ classifiers=[
20
20
  "Programming Language :: Python :: 3",
21
21
  "Operating System :: OS Independent",
22
- "Topic :: System :: Systems Administration",
22
+ "Intended Audience :: Developers",
23
+ "Environment :: Console",
24
+ "Topic :: Software Development :: Build Tools",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Utilities",
23
27
  ]
28
+
24
29
  dependencies = []
25
30
 
26
31
  [project.scripts]
@@ -30,6 +35,11 @@ pyhabitat = "pyhabitat.cli:run_cli"
30
35
  where = ["."]
31
36
  include = ["pyhabitat*"]
32
37
 
38
+ #[project.optional-dependencies]
39
+ #typer = ["click", "typer", "rich"]
40
+
41
+
42
+
33
43
  #[tool.setuptools_scm]
34
44
  # This tells setuptools_scm to look at the git history/tags for the version
35
45
  #write_to = "pyhabitat/_version.py"
File without changes
File without changes
File without changes