pyhabitat 1.0.18__py3-none-any.whl → 1.0.19__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 +5 -1
- pyhabitat/__main__.py +1 -1
- pyhabitat/cli.py +1 -1
- pyhabitat/environment.py +164 -61
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.19.dist-info}/METADATA +4 -2
- pyhabitat-1.0.19.dist-info/RECORD +11 -0
- pyhabitat-1.0.18.dist-info/RECORD +0 -11
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.19.dist-info}/WHEEL +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.19.dist-info}/entry_points.txt +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.19.dist-info}/licenses/LICENSE +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.19.dist-info}/top_level.txt +0 -0
pyhabitat/__init__.py
CHANGED
|
@@ -5,12 +5,14 @@ from .environment import (
|
|
|
5
5
|
matplotlib_is_available_for_headless_image_export,
|
|
6
6
|
tkinter_is_available,
|
|
7
7
|
in_repl,
|
|
8
|
-
on_termux,
|
|
9
8
|
on_freebsd,
|
|
10
9
|
on_linux,
|
|
11
10
|
on_android,
|
|
12
11
|
on_windows,
|
|
12
|
+
on_wsl,
|
|
13
13
|
on_apple,
|
|
14
|
+
on_termux,
|
|
15
|
+
on_pydroid,
|
|
14
16
|
on_ish_alpine,
|
|
15
17
|
as_pyinstaller,
|
|
16
18
|
as_frozen,
|
|
@@ -34,6 +36,8 @@ __all__ = [
|
|
|
34
36
|
'tkinter_is_available',
|
|
35
37
|
'in_repl',
|
|
36
38
|
'on_termux',
|
|
39
|
+
'on_pydroid',
|
|
40
|
+
'on_wsl',
|
|
37
41
|
'on_freebsd',
|
|
38
42
|
'on_linux',
|
|
39
43
|
'on_android',
|
pyhabitat/__main__.py
CHANGED
pyhabitat/cli.py
CHANGED
pyhabitat/environment.py
CHANGED
|
@@ -22,8 +22,10 @@ __all__ = [
|
|
|
22
22
|
'on_termux',
|
|
23
23
|
'on_freebsd',
|
|
24
24
|
'on_linux',
|
|
25
|
+
'on_pydroid',
|
|
25
26
|
'on_android',
|
|
26
27
|
'on_windows',
|
|
28
|
+
'on_wsl',
|
|
27
29
|
'on_apple',
|
|
28
30
|
'on_ish_alpine',
|
|
29
31
|
'as_pyinstaller',
|
|
@@ -209,6 +211,64 @@ def on_android() -> bool:
|
|
|
209
211
|
return False
|
|
210
212
|
return "android" in platform.platform().lower()
|
|
211
213
|
|
|
214
|
+
|
|
215
|
+
def on_wsl():
|
|
216
|
+
"""Return True if running inside Windows Subsystem for Linux (WSL or WSL2)."""
|
|
217
|
+
# Must look like Linux, not Windows
|
|
218
|
+
if platform.system() != "Linux":
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# --- Check environment variables for WSL2 ---
|
|
223
|
+
# False negative risk:
|
|
224
|
+
# Environment variables may be absent in older WSL1 installs.
|
|
225
|
+
# False negative likelihood: low.
|
|
226
|
+
if "WSL_DISTRO_NAME" in os.environ or "WSL_INTEROP" in os.environ:
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
# --- Check kernel info for 'microsoft' string ---
|
|
230
|
+
# False negative risk:
|
|
231
|
+
# Custom kernels, future Windows versions, or minimal WSL distros may omit 'microsoft' in strings.
|
|
232
|
+
# False negative likelihood: Very low to moderate.
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
with open("/proc/version") as f:
|
|
236
|
+
if "microsoft" in version_info or "wsl" in version_info:
|
|
237
|
+
return True
|
|
238
|
+
except FileNotFoundError:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
# Check for WSL-specific mounts (fallback)
|
|
242
|
+
"""
|
|
243
|
+
/proc/sys/kernel/osrelease
|
|
244
|
+
Purpose: Contains the kernel release string. In WSL, it usually contains "microsoft" (WSL2) or "microsoft-standard" (WSL1).
|
|
245
|
+
Very reliable for detecting WSL1 and WSL2 unless someone compiled a custom kernel and removed the microsoft string.
|
|
246
|
+
|
|
247
|
+
False negative risk:
|
|
248
|
+
If /proc/version or /proc/sys/kernel/osrelease cannot be read due to permissions, a containerized WSL distro, or some sandboxed environment.
|
|
249
|
+
# False negative likelihood: Very low.
|
|
250
|
+
"""
|
|
251
|
+
if Path("/proc/sys/kernel/osrelease").exists():
|
|
252
|
+
try:
|
|
253
|
+
with open("/proc/sys/kernel/osrelease") as f:
|
|
254
|
+
osrelease = f.read().lower()
|
|
255
|
+
if "microsoft" in osrelease:
|
|
256
|
+
return True
|
|
257
|
+
except Exception:
|
|
258
|
+
pass
|
|
259
|
+
return False
|
|
260
|
+
|
|
261
|
+
def on_pydroid():
|
|
262
|
+
"""Return True if running under Pydroid 3 (Android app)."""
|
|
263
|
+
if not on_android():
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
exe = (sys.executable or "").lower()
|
|
267
|
+
if "pydroid" in exe or "ru.iiec.pydroid3" in exe:
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
return any("pydroid" in p.lower() for p in sys.path)
|
|
271
|
+
|
|
212
272
|
def on_windows() -> bool:
|
|
213
273
|
"""Detect if running on Windows."""
|
|
214
274
|
return platform.system() == 'Windows'
|
|
@@ -277,31 +337,29 @@ def as_frozen():
|
|
|
277
337
|
return getattr(sys, 'frozen', False)
|
|
278
338
|
|
|
279
339
|
# --- Binary Characteristic Checks ---
|
|
280
|
-
def is_elf(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
340
|
+
def is_elf(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
281
341
|
"""Checks if the currently running executable (sys.argv[0]) is a standalone PyInstaller-built ELF binary."""
|
|
282
342
|
# If it's a pipx installation, it is not the monolithic binary we are concerned with here.
|
|
283
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
343
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
284
344
|
if not is_valid:
|
|
285
345
|
return False
|
|
286
346
|
|
|
287
347
|
try:
|
|
288
348
|
# Check the magic number: The first four bytes of an ELF file are 0x7f, 'E', 'L', 'F' (b'\x7fELF').
|
|
289
349
|
# This is the most reliable way to determine if the executable is a native binary wrapper (like PyInstaller's).
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if debug:
|
|
293
|
-
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
350
|
+
magic_bytes = read_magic_bytes(exec_path, 4, debug and not suppress_debug)
|
|
351
|
+
|
|
294
352
|
return magic_bytes == b'\x7fELF'
|
|
295
353
|
except Exception:
|
|
296
354
|
if debug:
|
|
297
355
|
logging.debug("False (Exception during file check)")
|
|
298
356
|
return False
|
|
299
357
|
|
|
300
|
-
def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
358
|
+
def is_pyz(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
301
359
|
"""Checks if the currently running executable (sys.argv[0]) is a PYZ zipapp ."""
|
|
302
360
|
|
|
303
361
|
# If it's a pipx installation, it is not the monolithic binary we are concerned with here.
|
|
304
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
362
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
305
363
|
if not is_valid:
|
|
306
364
|
return False
|
|
307
365
|
|
|
@@ -319,40 +377,34 @@ def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
319
377
|
return True
|
|
320
378
|
|
|
321
379
|
|
|
322
|
-
def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
380
|
+
def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
323
381
|
"""
|
|
324
382
|
Checks if the specified path or sys.argv[0] is a Windows Portable Executable (PE) binary.
|
|
325
383
|
Windows Portable Executables include .exe, .dll, and other binaries.
|
|
326
384
|
The standard way to check for a PE is to look for the MZ magic number at the very beginning of the file.
|
|
327
385
|
"""
|
|
328
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
386
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
329
387
|
if not is_valid:
|
|
330
388
|
return False
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
except Exception as e:
|
|
338
|
-
if debug:
|
|
339
|
-
logging.debug(f"False (Error during file check: {e})")
|
|
340
|
-
return False
|
|
341
|
-
|
|
342
|
-
def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
389
|
+
magic_bytes = read_magic_bytes(exec_path, 2, debug and not suppress_debug)
|
|
390
|
+
result = magic_bytes.startswith(b"MZ")
|
|
391
|
+
|
|
392
|
+
return result
|
|
393
|
+
|
|
394
|
+
def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
343
395
|
"""
|
|
344
396
|
Checks if the currently running executable is a macOS/Darwin Mach-O binary,
|
|
345
397
|
and explicitly excludes pipx-managed environments.
|
|
346
398
|
"""
|
|
347
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
399
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
348
400
|
if not is_valid:
|
|
349
401
|
return False
|
|
350
402
|
|
|
351
403
|
try:
|
|
352
404
|
# Check the magic number: Mach-O binaries start with specific 4-byte headers.
|
|
353
405
|
# Common ones are: b'\xfe\xed\xfa\xce' (32-bit) or b'\xfe\xed\xfa\xcf' (64-bit)
|
|
354
|
-
|
|
355
|
-
|
|
406
|
+
|
|
407
|
+
magic_bytes = read_magic_bytes(exec_path, 4, debug and not suppress_debug)
|
|
356
408
|
|
|
357
409
|
# Common Mach-O magic numbers (including their reversed-byte counterparts)
|
|
358
410
|
MACHO_MAGIC = {
|
|
@@ -364,8 +416,6 @@ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False
|
|
|
364
416
|
|
|
365
417
|
is_macho = magic_bytes in MACHO_MAGIC
|
|
366
418
|
|
|
367
|
-
if debug:
|
|
368
|
-
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
369
419
|
|
|
370
420
|
return is_macho
|
|
371
421
|
|
|
@@ -374,9 +424,9 @@ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False
|
|
|
374
424
|
logging.debug("False (Exception during file check)")
|
|
375
425
|
return False
|
|
376
426
|
|
|
377
|
-
def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
427
|
+
def is_pipx(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool = True) -> bool:
|
|
378
428
|
"""Checks if the executable is running from a pipx managed environment."""
|
|
379
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug, check_pipx=False)
|
|
429
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug, check_pipx=False)
|
|
380
430
|
if not is_valid:
|
|
381
431
|
return False
|
|
382
432
|
|
|
@@ -392,8 +442,11 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
392
442
|
logging.debug(f"INTERP_PATH: {interpreter_path}")
|
|
393
443
|
logging.debug(f"PIPX_BIN_PATH: {pipx_bin_path}")
|
|
394
444
|
logging.debug(f"PIPX_VENV_BASE: {pipx_venv_base_path}")
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
is_in_pipx_venv_base = norm_interp_path.startswith(str(pipx_venv_base_path).lower())
|
|
446
|
+
logging.debug(f"Interpreter path resides somewhere within the pipx venv base hierarchy: {is_in_pipx_venv_base}")
|
|
447
|
+
logging.debug(
|
|
448
|
+
f"This determines whether the current interpreter is managed by pipx: {is_in_pipx_venv_base}"
|
|
449
|
+
)
|
|
397
450
|
if "pipx/venvs" in norm_exec_path or "pipx/venvs" in norm_interp_path:
|
|
398
451
|
if debug:
|
|
399
452
|
logging.debug("True (Signature Check)")
|
|
@@ -409,8 +462,6 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
409
462
|
logging.debug("True (Executable Base Check)")
|
|
410
463
|
return True
|
|
411
464
|
|
|
412
|
-
if debug:
|
|
413
|
-
logging.debug("False")
|
|
414
465
|
return False
|
|
415
466
|
except Exception:
|
|
416
467
|
if debug:
|
|
@@ -445,7 +496,8 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
445
496
|
if debug:
|
|
446
497
|
logging.debug("False (Exception during pipx check)")
|
|
447
498
|
return False
|
|
448
|
-
|
|
499
|
+
|
|
500
|
+
def is_python_script(path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
449
501
|
"""
|
|
450
502
|
Checks if the specified path or running script is a Python source file (.py).
|
|
451
503
|
|
|
@@ -459,14 +511,14 @@ def is_python_script(path: Path | str | None = None, debug: bool = False) -> boo
|
|
|
459
511
|
Returns:
|
|
460
512
|
bool: True if the specified or default path is a Python source file (.py); False otherwise.
|
|
461
513
|
"""
|
|
462
|
-
exec_path, is_valid = _check_executable_path(path, debug, check_pipx=False)
|
|
514
|
+
exec_path, is_valid = _check_executable_path(path, debug and not suppress_debug, check_pipx=False)
|
|
463
515
|
if not is_valid:
|
|
464
516
|
return False
|
|
465
517
|
return exec_path.suffix.lower() == '.py'
|
|
466
518
|
|
|
467
519
|
# --- Interpreter Check ---
|
|
468
520
|
|
|
469
|
-
def interp_path(
|
|
521
|
+
def interp_path(debug: bool = False) -> str:
|
|
470
522
|
"""
|
|
471
523
|
Returns the path to the Python interpreter binary and optionally prints it.
|
|
472
524
|
|
|
@@ -482,8 +534,8 @@ def interp_path(print_path: bool = False) -> str:
|
|
|
482
534
|
str: The path to the Python interpreter binary, or an empty string if unavailable.
|
|
483
535
|
"""
|
|
484
536
|
path = sys.executable
|
|
485
|
-
if
|
|
486
|
-
|
|
537
|
+
if debug:
|
|
538
|
+
logging.debug(f"Python interpreter path: {path}")
|
|
487
539
|
return path
|
|
488
540
|
|
|
489
541
|
# --- TTY Check ---
|
|
@@ -580,7 +632,20 @@ def _run_dos2unix(path: Path | str | None = None):
|
|
|
580
632
|
except Exception:
|
|
581
633
|
# Catch other subprocess errors (e.g. permission issues)
|
|
582
634
|
pass
|
|
583
|
-
|
|
635
|
+
|
|
636
|
+
def read_magic_bytes(path: str, length: int = 4, debug: bool = False) -> bytes:
|
|
637
|
+
"""Return the first few bytes of a file for type detection."""
|
|
638
|
+
try:
|
|
639
|
+
with open(path, "rb") as f:
|
|
640
|
+
magic = f.read(length)
|
|
641
|
+
if debug:
|
|
642
|
+
logging.debug(f"Magic bytes: {magic!r}")
|
|
643
|
+
return magic
|
|
644
|
+
except Exception as e:
|
|
645
|
+
if debug:
|
|
646
|
+
logging.debug(f"False (Error during file check: {e})")
|
|
647
|
+
return False
|
|
648
|
+
|
|
584
649
|
def _get_pipx_paths():
|
|
585
650
|
"""
|
|
586
651
|
Returns the configured/default pipx binary and home directories.
|
|
@@ -659,21 +724,26 @@ def main(path=None, debug=False):
|
|
|
659
724
|
if debug:
|
|
660
725
|
logging.basicConfig(level=logging.DEBUG)
|
|
661
726
|
logging.getLogger('matplotlib').setLevel(logging.WARNING) # Suppress matplotlib debug logs
|
|
662
|
-
print("
|
|
663
|
-
print("
|
|
664
|
-
print("
|
|
727
|
+
print("================================")
|
|
728
|
+
print("======= PyHabitat Report =======")
|
|
729
|
+
print("================================")
|
|
730
|
+
print("\nCurrent Build Checks ")
|
|
731
|
+
print("# // Based on hasattr(sys,..) and getattr(sys,..)")
|
|
665
732
|
print("------------------------------")
|
|
666
733
|
print(f"in_repl(): {in_repl()}")
|
|
667
734
|
print(f"as_frozen(): {as_frozen()}")
|
|
668
735
|
print(f"as_pyinstaller(): {as_pyinstaller()}")
|
|
669
|
-
print("\nOperating System Checks
|
|
736
|
+
print("\nOperating System Checks")
|
|
737
|
+
print("# // Based on platform.system()")
|
|
670
738
|
print("------------------------------")
|
|
671
|
-
print(f"on_termux(): {on_termux()}")
|
|
672
739
|
print(f"on_windows(): {on_windows()}")
|
|
673
740
|
print(f"on_apple(): {on_apple()}")
|
|
674
741
|
print(f"on_linux(): {on_linux()}")
|
|
675
|
-
print(f"
|
|
742
|
+
print(f"on_wsl(): {on_wsl()}")
|
|
676
743
|
print(f"on_android(): {on_android()}")
|
|
744
|
+
print(f"on_termux(): {on_termux()}")
|
|
745
|
+
print(f"on_pydroid(): {on_pydroid()}")
|
|
746
|
+
print(f"on_ish_alpine(): {on_ish_alpine()}")
|
|
677
747
|
print(f"on_freebsd(): {on_freebsd()}")
|
|
678
748
|
print("\nCapability Checks")
|
|
679
749
|
print("-------------------------")
|
|
@@ -682,16 +752,27 @@ def main(path=None, debug=False):
|
|
|
682
752
|
print(f"matplotlib_is_available_for_headless_image_export(): {matplotlib_is_available_for_headless_image_export()}")
|
|
683
753
|
print(f"web_browser_is_available(): {web_browser_is_available()}")
|
|
684
754
|
print(f"interactive_terminal_is_available(): {interactive_terminal_is_available()}")
|
|
685
|
-
print("\nInterpreter Checks
|
|
755
|
+
print("\nInterpreter Checks")
|
|
756
|
+
print("# // Based on sys.executable()")
|
|
686
757
|
print("-----------------------------")
|
|
687
758
|
print(f"interp_path(): {interp_path()}")
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
759
|
+
if debug:
|
|
760
|
+
# Do these debug prints once to avoid redundant prints
|
|
761
|
+
# Supress redundant prints explicity using suppress_debug=True,
|
|
762
|
+
# so that only unique information gets printed for each check,
|
|
763
|
+
# even when more than one use the same functions which include debugging logs.
|
|
764
|
+
#print(f"_check_executable_path(interp_path(), debug=True)")
|
|
765
|
+
_check_executable_path(interp_path(), debug=debug)
|
|
766
|
+
#print(f"read_magic_bites(interp_path(), debug=True)")
|
|
767
|
+
read_magic_bytes(interp_path(), debug=debug)
|
|
768
|
+
print(f"is_elf(interp_path()): {is_elf(interp_path(), debug=debug, suppress_debug=True)}")
|
|
769
|
+
print(f"is_windows_portable_executable(interp_path()): {is_windows_portable_executable(interp_path(), debug=debug, suppress_debug=True)}")
|
|
770
|
+
print(f"is_macos_executable(interp_path()): {is_macos_executable(interp_path(), debug=debug, suppress_debug=True)}")
|
|
771
|
+
print(f"is_pyz(interp_path()): {is_pyz(interp_path(), debug=debug, suppress_debug=True)}")
|
|
772
|
+
print(f"is_pipx(interp_path()): {is_pipx(interp_path(), debug=debug, suppress_debug=True)}")
|
|
773
|
+
print(f"is_python_script(interp_path()): {is_python_script(interp_path(), debug=debug, suppress_debug=True)}")
|
|
774
|
+
print("\nCurrent Environment Check")
|
|
775
|
+
print("# // Based on sys.argv[0]")
|
|
695
776
|
print("-----------------------------")
|
|
696
777
|
inspect_path = path if path is not None else (None if sys.argv[0] == '-c' else sys.argv[0])
|
|
697
778
|
logging.debug(f"Inspecting path: {inspect_path}")
|
|
@@ -706,13 +787,35 @@ def main(path=None, debug=False):
|
|
|
706
787
|
script_path = None
|
|
707
788
|
if path or (sys.argv[0] and sys.argv[0] != '-c'):
|
|
708
789
|
script_path = Path(path or sys.argv[0]).resolve()
|
|
709
|
-
|
|
790
|
+
print(f"sys.argv[0] = {str(sys.argv[0])}")
|
|
710
791
|
if script_path is not None:
|
|
711
|
-
print(f"
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
792
|
+
print(f"script_path = {script_path}")
|
|
793
|
+
if debug:
|
|
794
|
+
# Do these debug prints once to avoid redundant prints
|
|
795
|
+
# Supress redundant prints explicity using suppress_debug=True,
|
|
796
|
+
# so that only unique information gets printed for each check,
|
|
797
|
+
# even when more than one use the same functions which include debugging logs.
|
|
798
|
+
p#rint(f"_check_executable_path(script_path, debug=True)")
|
|
799
|
+
_check_executable_path(script_path, debug=debug)
|
|
800
|
+
#print(f"read_magic_bites(script_path, debug=True)")
|
|
801
|
+
read_magic_bytes(script_path, debug=debug)
|
|
802
|
+
print(f"is_elf(): {is_elf(script_path, debug=debug, suppress_debug=True)}")
|
|
803
|
+
print(f"is_windows_portable_executable(): {is_windows_portable_executable(script_path, debug=debug, suppress_debug=True)}")
|
|
804
|
+
print(f"is_macos_executable(): {is_macos_executable(script_path, debug=debug, suppress_debug=True)}")
|
|
805
|
+
print(f"is_pyz(): {is_pyz(script_path, debug=debug, suppress_debug=True)}")
|
|
806
|
+
print(f"is_pipx(): {is_pipx(script_path, debug=debug, suppress_debug=True)}")
|
|
807
|
+
print(f"is_python_script(): {is_python_script(script_path, debug=debug, suppress_debug=True)}")
|
|
717
808
|
else:
|
|
718
|
-
print("
|
|
809
|
+
print("Skipping: ")
|
|
810
|
+
print(" is_elf(), ")
|
|
811
|
+
print(" is_windows_portable_executable(), ")
|
|
812
|
+
print(" is_macos_executable(), ")
|
|
813
|
+
print(" is_pyz(), ")
|
|
814
|
+
print(" is_pipx(), ")
|
|
815
|
+
print(" is_python_script(), ")
|
|
816
|
+
print("All False, script_path is None.")
|
|
817
|
+
print("")
|
|
818
|
+
print("=================================")
|
|
819
|
+
print("=== PyHabitat Report Complete ===")
|
|
820
|
+
print("=================================")
|
|
821
|
+
print("")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyhabitat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.19
|
|
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
|
|
@@ -78,10 +78,12 @@ Key question: "What is this running on?"
|
|
|
78
78
|
| `on_windows()` | Returns `True` on Windows. |
|
|
79
79
|
| `on_apple()` | Returns `True` on macOS (Darwin). |
|
|
80
80
|
| `on_linux()` | Returns `True` on Linux in general. |
|
|
81
|
+
| `on_wsl()` | Returns `True` if running inside Windows Subsystem for Linux (WSL or WSL2). |
|
|
81
82
|
| `on_termux()` | Returns `True` if running in the Termux Android environment. |
|
|
82
83
|
| `on_freebsd()` | Returns `True` on FreeBSD. |
|
|
83
84
|
| `on_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
|
|
84
85
|
| `on_android()` | Returns `True` on any Android-based Linux environment. |
|
|
86
|
+
| `on_pydroid()` | Returns `True` Return True if running under the Pydroid 3 Android app (other versions untested). |
|
|
85
87
|
| `in_repl()` | Returns `True` is the user is currently in a Python REPL; hasattr(sys,'ps1'). |
|
|
86
88
|
|
|
87
89
|
### Packaging and Build Checking
|
|
@@ -118,7 +120,7 @@ Key Question: "What could I do next?"
|
|
|
118
120
|
| Function | Description |
|
|
119
121
|
| :--- | :--- |
|
|
120
122
|
| `edit_textfile(path)` | Opens a text file for editing using the default editor (Windows, Linux, macOS) or nano in Termux/iSH. In REPL mode, prints an error. Path argument (str or Path) uses Path.resolve() for stability. |
|
|
121
|
-
| `interp_path(
|
|
123
|
+
| `interp_path()` | Returns the path to the Python interpreter binary (sys.executable). Returns empty string if unavailable. |
|
|
122
124
|
| `main()` | Prints a comprehensive environment report with sections: Interpreter Checks (sys.executable), Current Environment Check (sys.argv[0]), Current Build Checks (sys attributes), Operating System Checks (platform.system()), and Capability Checks. Run via `python -m pyhabitat` or `import pyhabitat; pyhabitat.main()` in the REPL. |
|
|
123
125
|
|
|
124
126
|
</details>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pyhabitat/__init__.py,sha256=B_yS2yBG2kjHkte_xissSG8aJ15af-soBo3Cy7caiEc,1288
|
|
2
|
+
pyhabitat/__main__.py,sha256=qlOKShd_Xk0HGpYOySXQSQe3K60a9wwrstCAceJkKIs,76
|
|
3
|
+
pyhabitat/__main__stable.py,sha256=UACpHLrr_Rmf0L5dJCEae6kFzLn7dqCqIri68IBnb10,2910
|
|
4
|
+
pyhabitat/cli.py,sha256=ZlY6v_IT7Mw-ekR3WPT8NLsqMQ_RXI-cckVq9oLT4AU,683
|
|
5
|
+
pyhabitat/environment.py,sha256=2wyjdWNRSv8VxaXkjoQz285ATAlhddrY_dor45mMnQ4,32871
|
|
6
|
+
pyhabitat-1.0.19.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
|
|
7
|
+
pyhabitat-1.0.19.dist-info/METADATA,sha256=N8o0nM2KOq7X4B-JNUMcePtqjinqsdqrqZ11Ct8qsRY,10900
|
|
8
|
+
pyhabitat-1.0.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
pyhabitat-1.0.19.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
|
|
10
|
+
pyhabitat-1.0.19.dist-info/top_level.txt,sha256=zXYK44Qu8EqxUETREvd2diMUaB5JiGRErkwFaoLQnnI,10
|
|
11
|
+
pyhabitat-1.0.19.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
pyhabitat/__init__.py,sha256=GcpHBpfEer9O9dKBUREU1RTd5w0im9ph5ew4bs7jOvY,1228
|
|
2
|
-
pyhabitat/__main__.py,sha256=hhH17lkw-ZalKp9NolnPGwW0KYxbXirspWvhBKNyBks,67
|
|
3
|
-
pyhabitat/__main__stable.py,sha256=UACpHLrr_Rmf0L5dJCEae6kFzLn7dqCqIri68IBnb10,2910
|
|
4
|
-
pyhabitat/cli.py,sha256=vuRczazuumIaJl4Td2VvCR5lXcKKNH90V8Yx0KZUJck,663
|
|
5
|
-
pyhabitat/environment.py,sha256=7yXMCHvTRkfwwQnsgzrDXPDG9m8h8yhI5oCNlJTHWUA,28026
|
|
6
|
-
pyhabitat-1.0.18.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
|
|
7
|
-
pyhabitat-1.0.18.dist-info/METADATA,sha256=Xk4blTODZGW6NiuqN5e33SuePkTb-PnNU5pGGzeHXvs,10733
|
|
8
|
-
pyhabitat-1.0.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
pyhabitat-1.0.18.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
|
|
10
|
-
pyhabitat-1.0.18.dist-info/top_level.txt,sha256=zXYK44Qu8EqxUETREvd2diMUaB5JiGRErkwFaoLQnnI,10
|
|
11
|
-
pyhabitat-1.0.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|