pyhabitat 1.0.18__py3-none-any.whl → 1.0.20__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 +167 -61
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.20.dist-info}/METADATA +4 -2
- pyhabitat-1.0.20.dist-info/RECORD +11 -0
- pyhabitat-1.0.18.dist-info/RECORD +0 -11
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.20.dist-info}/WHEEL +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.20.dist-info}/entry_points.txt +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.20.dist-info}/licenses/LICENSE +0 -0
- {pyhabitat-1.0.18.dist-info → pyhabitat-1.0.20.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,67 @@ 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' or 'wsl' string (Fallback) ---
|
|
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
|
+
try:
|
|
234
|
+
with open("/proc/version") as f:
|
|
235
|
+
version_info = f.read().lower()
|
|
236
|
+
if "microsoft" in version_info or "wsl" in version_info:
|
|
237
|
+
return True
|
|
238
|
+
except (IOError, OSError):
|
|
239
|
+
# This block would catch the PermissionError!
|
|
240
|
+
# It would simply 'pass' and move on.
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# Check for WSL-specific mounts (fallback)
|
|
245
|
+
"""
|
|
246
|
+
/proc/sys/kernel/osrelease
|
|
247
|
+
Purpose: Contains the kernel release string. In WSL, it usually contains "microsoft" (WSL2) or "microsoft-standard" (WSL1).
|
|
248
|
+
Very reliable for detecting WSL1 and WSL2 unless someone compiled a custom kernel and removed the microsoft string.
|
|
249
|
+
|
|
250
|
+
False negative risk:
|
|
251
|
+
If /proc/sys/kernel/osrelease cannot be read due to permissions, a containerized WSL distro, or some sandboxed environment.
|
|
252
|
+
# False negative likelihood: Very low.
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
with open("/proc/sys/kernel/osrelease") as f:
|
|
256
|
+
osrelease = f.read().lower()
|
|
257
|
+
if "microsoft" in osrelease:
|
|
258
|
+
return True
|
|
259
|
+
except (IOError, OSError):
|
|
260
|
+
# This block would catch the PermissionError, an FileNotFound
|
|
261
|
+
pass
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
def on_pydroid():
|
|
265
|
+
"""Return True if running under Pydroid 3 (Android app)."""
|
|
266
|
+
if not on_android():
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
exe = (sys.executable or "").lower()
|
|
270
|
+
if "pydroid" in exe or "ru.iiec.pydroid3" in exe:
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
return any("pydroid" in p.lower() for p in sys.path)
|
|
274
|
+
|
|
212
275
|
def on_windows() -> bool:
|
|
213
276
|
"""Detect if running on Windows."""
|
|
214
277
|
return platform.system() == 'Windows'
|
|
@@ -277,31 +340,29 @@ def as_frozen():
|
|
|
277
340
|
return getattr(sys, 'frozen', False)
|
|
278
341
|
|
|
279
342
|
# --- Binary Characteristic Checks ---
|
|
280
|
-
def is_elf(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
343
|
+
def is_elf(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
281
344
|
"""Checks if the currently running executable (sys.argv[0]) is a standalone PyInstaller-built ELF binary."""
|
|
282
345
|
# 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)
|
|
346
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
284
347
|
if not is_valid:
|
|
285
348
|
return False
|
|
286
349
|
|
|
287
350
|
try:
|
|
288
351
|
# Check the magic number: The first four bytes of an ELF file are 0x7f, 'E', 'L', 'F' (b'\x7fELF').
|
|
289
352
|
# 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}")
|
|
353
|
+
magic_bytes = read_magic_bytes(exec_path, 4, debug and not suppress_debug)
|
|
354
|
+
|
|
294
355
|
return magic_bytes == b'\x7fELF'
|
|
295
356
|
except Exception:
|
|
296
357
|
if debug:
|
|
297
358
|
logging.debug("False (Exception during file check)")
|
|
298
359
|
return False
|
|
299
360
|
|
|
300
|
-
def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
361
|
+
def is_pyz(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
301
362
|
"""Checks if the currently running executable (sys.argv[0]) is a PYZ zipapp ."""
|
|
302
363
|
|
|
303
364
|
# 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)
|
|
365
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
305
366
|
if not is_valid:
|
|
306
367
|
return False
|
|
307
368
|
|
|
@@ -319,40 +380,34 @@ def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
319
380
|
return True
|
|
320
381
|
|
|
321
382
|
|
|
322
|
-
def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
383
|
+
def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
323
384
|
"""
|
|
324
385
|
Checks if the specified path or sys.argv[0] is a Windows Portable Executable (PE) binary.
|
|
325
386
|
Windows Portable Executables include .exe, .dll, and other binaries.
|
|
326
387
|
The standard way to check for a PE is to look for the MZ magic number at the very beginning of the file.
|
|
327
388
|
"""
|
|
328
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
389
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
329
390
|
if not is_valid:
|
|
330
391
|
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:
|
|
392
|
+
magic_bytes = read_magic_bytes(exec_path, 2, debug and not suppress_debug)
|
|
393
|
+
result = magic_bytes.startswith(b"MZ")
|
|
394
|
+
|
|
395
|
+
return result
|
|
396
|
+
|
|
397
|
+
def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
343
398
|
"""
|
|
344
399
|
Checks if the currently running executable is a macOS/Darwin Mach-O binary,
|
|
345
400
|
and explicitly excludes pipx-managed environments.
|
|
346
401
|
"""
|
|
347
|
-
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
402
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug)
|
|
348
403
|
if not is_valid:
|
|
349
404
|
return False
|
|
350
405
|
|
|
351
406
|
try:
|
|
352
407
|
# Check the magic number: Mach-O binaries start with specific 4-byte headers.
|
|
353
408
|
# Common ones are: b'\xfe\xed\xfa\xce' (32-bit) or b'\xfe\xed\xfa\xcf' (64-bit)
|
|
354
|
-
|
|
355
|
-
|
|
409
|
+
|
|
410
|
+
magic_bytes = read_magic_bytes(exec_path, 4, debug and not suppress_debug)
|
|
356
411
|
|
|
357
412
|
# Common Mach-O magic numbers (including their reversed-byte counterparts)
|
|
358
413
|
MACHO_MAGIC = {
|
|
@@ -364,8 +419,6 @@ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False
|
|
|
364
419
|
|
|
365
420
|
is_macho = magic_bytes in MACHO_MAGIC
|
|
366
421
|
|
|
367
|
-
if debug:
|
|
368
|
-
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
369
422
|
|
|
370
423
|
return is_macho
|
|
371
424
|
|
|
@@ -374,9 +427,9 @@ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False
|
|
|
374
427
|
logging.debug("False (Exception during file check)")
|
|
375
428
|
return False
|
|
376
429
|
|
|
377
|
-
def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
430
|
+
def is_pipx(exec_path: Path | str | None = None, debug: bool = False, suppress_debug: bool = True) -> bool:
|
|
378
431
|
"""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)
|
|
432
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug and not suppress_debug, check_pipx=False)
|
|
380
433
|
if not is_valid:
|
|
381
434
|
return False
|
|
382
435
|
|
|
@@ -392,8 +445,11 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
392
445
|
logging.debug(f"INTERP_PATH: {interpreter_path}")
|
|
393
446
|
logging.debug(f"PIPX_BIN_PATH: {pipx_bin_path}")
|
|
394
447
|
logging.debug(f"PIPX_VENV_BASE: {pipx_venv_base_path}")
|
|
395
|
-
|
|
396
|
-
|
|
448
|
+
is_in_pipx_venv_base = norm_interp_path.startswith(str(pipx_venv_base_path).lower())
|
|
449
|
+
logging.debug(f"Interpreter path resides somewhere within the pipx venv base hierarchy: {is_in_pipx_venv_base}")
|
|
450
|
+
logging.debug(
|
|
451
|
+
f"This determines whether the current interpreter is managed by pipx: {is_in_pipx_venv_base}"
|
|
452
|
+
)
|
|
397
453
|
if "pipx/venvs" in norm_exec_path or "pipx/venvs" in norm_interp_path:
|
|
398
454
|
if debug:
|
|
399
455
|
logging.debug("True (Signature Check)")
|
|
@@ -409,8 +465,6 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
409
465
|
logging.debug("True (Executable Base Check)")
|
|
410
466
|
return True
|
|
411
467
|
|
|
412
|
-
if debug:
|
|
413
|
-
logging.debug("False")
|
|
414
468
|
return False
|
|
415
469
|
except Exception:
|
|
416
470
|
if debug:
|
|
@@ -445,7 +499,8 @@ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
|
445
499
|
if debug:
|
|
446
500
|
logging.debug("False (Exception during pipx check)")
|
|
447
501
|
return False
|
|
448
|
-
|
|
502
|
+
|
|
503
|
+
def is_python_script(path: Path | str | None = None, debug: bool = False, suppress_debug: bool =False) -> bool:
|
|
449
504
|
"""
|
|
450
505
|
Checks if the specified path or running script is a Python source file (.py).
|
|
451
506
|
|
|
@@ -459,14 +514,14 @@ def is_python_script(path: Path | str | None = None, debug: bool = False) -> boo
|
|
|
459
514
|
Returns:
|
|
460
515
|
bool: True if the specified or default path is a Python source file (.py); False otherwise.
|
|
461
516
|
"""
|
|
462
|
-
exec_path, is_valid = _check_executable_path(path, debug, check_pipx=False)
|
|
517
|
+
exec_path, is_valid = _check_executable_path(path, debug and not suppress_debug, check_pipx=False)
|
|
463
518
|
if not is_valid:
|
|
464
519
|
return False
|
|
465
520
|
return exec_path.suffix.lower() == '.py'
|
|
466
521
|
|
|
467
522
|
# --- Interpreter Check ---
|
|
468
523
|
|
|
469
|
-
def interp_path(
|
|
524
|
+
def interp_path(debug: bool = False) -> str:
|
|
470
525
|
"""
|
|
471
526
|
Returns the path to the Python interpreter binary and optionally prints it.
|
|
472
527
|
|
|
@@ -482,8 +537,8 @@ def interp_path(print_path: bool = False) -> str:
|
|
|
482
537
|
str: The path to the Python interpreter binary, or an empty string if unavailable.
|
|
483
538
|
"""
|
|
484
539
|
path = sys.executable
|
|
485
|
-
if
|
|
486
|
-
|
|
540
|
+
if debug:
|
|
541
|
+
logging.debug(f"Python interpreter path: {path}")
|
|
487
542
|
return path
|
|
488
543
|
|
|
489
544
|
# --- TTY Check ---
|
|
@@ -580,7 +635,20 @@ def _run_dos2unix(path: Path | str | None = None):
|
|
|
580
635
|
except Exception:
|
|
581
636
|
# Catch other subprocess errors (e.g. permission issues)
|
|
582
637
|
pass
|
|
583
|
-
|
|
638
|
+
|
|
639
|
+
def read_magic_bytes(path: str, length: int = 4, debug: bool = False) -> bytes:
|
|
640
|
+
"""Return the first few bytes of a file for type detection."""
|
|
641
|
+
try:
|
|
642
|
+
with open(path, "rb") as f:
|
|
643
|
+
magic = f.read(length)
|
|
644
|
+
if debug:
|
|
645
|
+
logging.debug(f"Magic bytes: {magic!r}")
|
|
646
|
+
return magic
|
|
647
|
+
except Exception as e:
|
|
648
|
+
if debug:
|
|
649
|
+
logging.debug(f"False (Error during file check: {e})")
|
|
650
|
+
return False
|
|
651
|
+
|
|
584
652
|
def _get_pipx_paths():
|
|
585
653
|
"""
|
|
586
654
|
Returns the configured/default pipx binary and home directories.
|
|
@@ -659,21 +727,26 @@ def main(path=None, debug=False):
|
|
|
659
727
|
if debug:
|
|
660
728
|
logging.basicConfig(level=logging.DEBUG)
|
|
661
729
|
logging.getLogger('matplotlib').setLevel(logging.WARNING) # Suppress matplotlib debug logs
|
|
662
|
-
print("
|
|
663
|
-
print("
|
|
664
|
-
print("
|
|
730
|
+
print("================================")
|
|
731
|
+
print("======= PyHabitat Report =======")
|
|
732
|
+
print("================================")
|
|
733
|
+
print("\nCurrent Build Checks ")
|
|
734
|
+
print("# // Based on hasattr(sys,..) and getattr(sys,..)")
|
|
665
735
|
print("------------------------------")
|
|
666
736
|
print(f"in_repl(): {in_repl()}")
|
|
667
737
|
print(f"as_frozen(): {as_frozen()}")
|
|
668
738
|
print(f"as_pyinstaller(): {as_pyinstaller()}")
|
|
669
|
-
print("\nOperating System Checks
|
|
739
|
+
print("\nOperating System Checks")
|
|
740
|
+
print("# // Based on platform.system()")
|
|
670
741
|
print("------------------------------")
|
|
671
|
-
print(f"on_termux(): {on_termux()}")
|
|
672
742
|
print(f"on_windows(): {on_windows()}")
|
|
673
743
|
print(f"on_apple(): {on_apple()}")
|
|
674
744
|
print(f"on_linux(): {on_linux()}")
|
|
675
|
-
print(f"
|
|
745
|
+
print(f"on_wsl(): {on_wsl()}")
|
|
676
746
|
print(f"on_android(): {on_android()}")
|
|
747
|
+
print(f"on_termux(): {on_termux()}")
|
|
748
|
+
print(f"on_pydroid(): {on_pydroid()}")
|
|
749
|
+
print(f"on_ish_alpine(): {on_ish_alpine()}")
|
|
677
750
|
print(f"on_freebsd(): {on_freebsd()}")
|
|
678
751
|
print("\nCapability Checks")
|
|
679
752
|
print("-------------------------")
|
|
@@ -682,16 +755,27 @@ def main(path=None, debug=False):
|
|
|
682
755
|
print(f"matplotlib_is_available_for_headless_image_export(): {matplotlib_is_available_for_headless_image_export()}")
|
|
683
756
|
print(f"web_browser_is_available(): {web_browser_is_available()}")
|
|
684
757
|
print(f"interactive_terminal_is_available(): {interactive_terminal_is_available()}")
|
|
685
|
-
print("\nInterpreter Checks
|
|
758
|
+
print("\nInterpreter Checks")
|
|
759
|
+
print("# // Based on sys.executable()")
|
|
686
760
|
print("-----------------------------")
|
|
687
761
|
print(f"interp_path(): {interp_path()}")
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
762
|
+
if debug:
|
|
763
|
+
# Do these debug prints once to avoid redundant prints
|
|
764
|
+
# Supress redundant prints explicity using suppress_debug=True,
|
|
765
|
+
# so that only unique information gets printed for each check,
|
|
766
|
+
# even when more than one use the same functions which include debugging logs.
|
|
767
|
+
#print(f"_check_executable_path(interp_path(), debug=True)")
|
|
768
|
+
_check_executable_path(interp_path(), debug=debug)
|
|
769
|
+
#print(f"read_magic_bites(interp_path(), debug=True)")
|
|
770
|
+
read_magic_bytes(interp_path(), debug=debug)
|
|
771
|
+
print(f"is_elf(interp_path()): {is_elf(interp_path(), debug=debug, suppress_debug=True)}")
|
|
772
|
+
print(f"is_windows_portable_executable(interp_path()): {is_windows_portable_executable(interp_path(), debug=debug, suppress_debug=True)}")
|
|
773
|
+
print(f"is_macos_executable(interp_path()): {is_macos_executable(interp_path(), debug=debug, suppress_debug=True)}")
|
|
774
|
+
print(f"is_pyz(interp_path()): {is_pyz(interp_path(), debug=debug, suppress_debug=True)}")
|
|
775
|
+
print(f"is_pipx(interp_path()): {is_pipx(interp_path(), debug=debug, suppress_debug=True)}")
|
|
776
|
+
print(f"is_python_script(interp_path()): {is_python_script(interp_path(), debug=debug, suppress_debug=True)}")
|
|
777
|
+
print("\nCurrent Environment Check")
|
|
778
|
+
print("# // Based on sys.argv[0]")
|
|
695
779
|
print("-----------------------------")
|
|
696
780
|
inspect_path = path if path is not None else (None if sys.argv[0] == '-c' else sys.argv[0])
|
|
697
781
|
logging.debug(f"Inspecting path: {inspect_path}")
|
|
@@ -706,13 +790,35 @@ def main(path=None, debug=False):
|
|
|
706
790
|
script_path = None
|
|
707
791
|
if path or (sys.argv[0] and sys.argv[0] != '-c'):
|
|
708
792
|
script_path = Path(path or sys.argv[0]).resolve()
|
|
709
|
-
|
|
793
|
+
print(f"sys.argv[0] = {str(sys.argv[0])}")
|
|
710
794
|
if script_path is not None:
|
|
711
|
-
print(f"
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
795
|
+
print(f"script_path = {script_path}")
|
|
796
|
+
if debug:
|
|
797
|
+
# Do these debug prints once to avoid redundant prints
|
|
798
|
+
# Supress redundant prints explicity using suppress_debug=True,
|
|
799
|
+
# so that only unique information gets printed for each check,
|
|
800
|
+
# even when more than one use the same functions which include debugging logs.
|
|
801
|
+
p#rint(f"_check_executable_path(script_path, debug=True)")
|
|
802
|
+
_check_executable_path(script_path, debug=debug)
|
|
803
|
+
#print(f"read_magic_bites(script_path, debug=True)")
|
|
804
|
+
read_magic_bytes(script_path, debug=debug)
|
|
805
|
+
print(f"is_elf(): {is_elf(script_path, debug=debug, suppress_debug=True)}")
|
|
806
|
+
print(f"is_windows_portable_executable(): {is_windows_portable_executable(script_path, debug=debug, suppress_debug=True)}")
|
|
807
|
+
print(f"is_macos_executable(): {is_macos_executable(script_path, debug=debug, suppress_debug=True)}")
|
|
808
|
+
print(f"is_pyz(): {is_pyz(script_path, debug=debug, suppress_debug=True)}")
|
|
809
|
+
print(f"is_pipx(): {is_pipx(script_path, debug=debug, suppress_debug=True)}")
|
|
810
|
+
print(f"is_python_script(): {is_python_script(script_path, debug=debug, suppress_debug=True)}")
|
|
717
811
|
else:
|
|
718
|
-
print("
|
|
812
|
+
print("Skipping: ")
|
|
813
|
+
print(" is_elf(), ")
|
|
814
|
+
print(" is_windows_portable_executable(), ")
|
|
815
|
+
print(" is_macos_executable(), ")
|
|
816
|
+
print(" is_pyz(), ")
|
|
817
|
+
print(" is_pipx(), ")
|
|
818
|
+
print(" is_python_script(), ")
|
|
819
|
+
print("All False, script_path is None.")
|
|
820
|
+
print("")
|
|
821
|
+
print("=================================")
|
|
822
|
+
print("=== PyHabitat Report Complete ===")
|
|
823
|
+
print("=================================")
|
|
824
|
+
print("")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyhabitat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.20
|
|
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=YlrcmovQBa7JapQLCqG9R6pjmYElL-8oVUHVSXH4MLw,33007
|
|
6
|
+
pyhabitat-1.0.20.dist-info/licenses/LICENSE,sha256=D4fg30ctUGnCJlWu3ONv5-V8JE1v3ctakoJTcVjsJlg,1072
|
|
7
|
+
pyhabitat-1.0.20.dist-info/METADATA,sha256=1MXhoZ1tOLB_tm1JHkH21-vubm3QWtlSW4os7GTB_WE,10900
|
|
8
|
+
pyhabitat-1.0.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
pyhabitat-1.0.20.dist-info/entry_points.txt,sha256=409xZ-BrarQJJLtO-aActCGkL0FMhNVi9wsq3u7tRHM,52
|
|
10
|
+
pyhabitat-1.0.20.dist-info/top_level.txt,sha256=zXYK44Qu8EqxUETREvd2diMUaB5JiGRErkwFaoLQnnI,10
|
|
11
|
+
pyhabitat-1.0.20.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
|