pyhabitat 1.0.17__tar.gz → 1.0.18__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.
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/PKG-INFO +3 -3
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/README.md +1 -1
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat/__init__.py +1 -2
- pyhabitat-1.0.18/pyhabitat/__main__.py +4 -0
- pyhabitat-1.0.17/pyhabitat/__main__.py → pyhabitat-1.0.18/pyhabitat/__main__stable.py +3 -0
- pyhabitat-1.0.18/pyhabitat/cli.py +22 -0
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat/environment.py +188 -130
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat.egg-info/PKG-INFO +3 -3
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat.egg-info/SOURCES.txt +3 -0
- pyhabitat-1.0.18/pyhabitat.egg-info/entry_points.txt +2 -0
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyproject.toml +8 -4
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/LICENSE +0 -0
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat.egg-info/dependency_links.txt +0 -0
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/pyhabitat.egg-info/top_level.txt +0 -0
- {pyhabitat-1.0.17 → pyhabitat-1.0.18}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyhabitat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.18
|
|
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
|
|
@@ -8,7 +8,7 @@ Keywords: environment,os-detection,gui,build-system
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Topic :: System :: Systems Administration
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Dynamic: license-file
|
|
@@ -130,7 +130,7 @@ Key Question: "What could I do next?"
|
|
|
130
130
|
|
|
131
131
|
The module exposes all detection functions directly for easy access.
|
|
132
132
|
|
|
133
|
-
### 0\.Example of PyHabitat in Action
|
|
133
|
+
### 0\. Example of PyHabitat in Action
|
|
134
134
|
|
|
135
135
|
The `pipeline-eds` package uses the `pyhabitat` library to handle [configuration](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/security_and_config.py) and [plotting](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/cli.py), among other things.
|
|
136
136
|
|
|
@@ -115,7 +115,7 @@ Key Question: "What could I do next?"
|
|
|
115
115
|
|
|
116
116
|
The module exposes all detection functions directly for easy access.
|
|
117
117
|
|
|
118
|
-
### 0\.Example of PyHabitat in Action
|
|
118
|
+
### 0\. Example of PyHabitat in Action
|
|
119
119
|
|
|
120
120
|
The `pipeline-eds` package uses the `pyhabitat` library to handle [configuration](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/security_and_config.py) and [plotting](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/cli.py), among other things.
|
|
121
121
|
|
|
@@ -24,10 +24,9 @@ from .environment import (
|
|
|
24
24
|
web_browser_is_available,
|
|
25
25
|
edit_textfile,
|
|
26
26
|
interp_path,
|
|
27
|
+
main,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
|
-
from .__main__ import main
|
|
30
|
-
|
|
31
30
|
# Optional: Set __all__ for explicit documentation and cleaner imports
|
|
32
31
|
__all__ = [
|
|
33
32
|
'matplotlib_is_available_for_gui_plotting',
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from . import main
|
|
4
|
+
|
|
5
|
+
def run_cli():
|
|
6
|
+
"""Parse CLI arguments and run the pyhabitat environment report."""
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
description="PyHabitat: Python environment and build introspection"
|
|
9
|
+
)
|
|
10
|
+
parser.add_argument(
|
|
11
|
+
"--path",
|
|
12
|
+
type=str,
|
|
13
|
+
default=None,
|
|
14
|
+
help="Path to a script or binary to inspect (defaults to sys.argv[0])",
|
|
15
|
+
)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"--debug",
|
|
18
|
+
action="store_true",
|
|
19
|
+
help="Enable verbose debug output",
|
|
20
|
+
)
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
main(path=Path(args.path) if args.path else None, debug=args.debug)
|
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
import subprocess
|
|
14
14
|
import io
|
|
15
15
|
import zipfile
|
|
16
|
+
import logging
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
'matplotlib_is_available_for_gui_plotting',
|
|
@@ -37,6 +38,7 @@ __all__ = [
|
|
|
37
38
|
'edit_textfile',
|
|
38
39
|
'in_repl',
|
|
39
40
|
'interp_path',
|
|
41
|
+
'main',
|
|
40
42
|
]
|
|
41
43
|
|
|
42
44
|
# Global cache for tkinter and matplotlib (mpl) availability
|
|
@@ -44,6 +46,7 @@ _TKINTER_AVAILABILITY: bool | None = None
|
|
|
44
46
|
_MATPLOTLIB_EXPORT_AVAILABILITY: bool | None = None
|
|
45
47
|
_MATPLOTLIB_WINDOWED_AVAILABILITY: bool | None = None
|
|
46
48
|
|
|
49
|
+
|
|
47
50
|
# --- GUI CHECKS ---
|
|
48
51
|
def matplotlib_is_available_for_gui_plotting(termux_has_gui=False):
|
|
49
52
|
"""Check if Matplotlib is available AND can use a GUI backend for a popup window."""
|
|
@@ -277,122 +280,72 @@ def as_frozen():
|
|
|
277
280
|
def is_elf(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
278
281
|
"""Checks if the currently running executable (sys.argv[0]) is a standalone PyInstaller-built ELF binary."""
|
|
279
282
|
# If it's a pipx installation, it is not the monolithic binary we are concerned with here.
|
|
280
|
-
|
|
281
|
-
if
|
|
282
|
-
exec_path = Path(sys.argv[0]).resolve()
|
|
283
|
-
else:
|
|
284
|
-
exec_path = Path(exec_path).resolve()
|
|
285
|
-
|
|
286
|
-
if debug:
|
|
287
|
-
print(f"DEBUG: Checking executable path: {exec_path}")
|
|
288
|
-
|
|
289
|
-
if is_pipx():
|
|
283
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
284
|
+
if not is_valid:
|
|
290
285
|
return False
|
|
291
286
|
|
|
292
|
-
# Check if the file exists and is readable
|
|
293
|
-
if not exec_path.is_file():
|
|
294
|
-
if debug: print("DEBUG:False (Not a file)")
|
|
295
|
-
return False
|
|
296
287
|
try:
|
|
297
288
|
# Check the magic number: The first four bytes of an ELF file are 0x7f, 'E', 'L', 'F' (b'\x7fELF').
|
|
298
289
|
# This is the most reliable way to determine if the executable is a native binary wrapper (like PyInstaller's).
|
|
299
290
|
with open(exec_path, 'rb') as f:
|
|
300
291
|
magic_bytes = f.read(4)
|
|
301
|
-
|
|
292
|
+
if debug:
|
|
293
|
+
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
302
294
|
return magic_bytes == b'\x7fELF'
|
|
303
295
|
except Exception:
|
|
304
|
-
|
|
296
|
+
if debug:
|
|
297
|
+
logging.debug("False (Exception during file check)")
|
|
305
298
|
return False
|
|
306
299
|
|
|
307
300
|
def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
308
301
|
"""Checks if the currently running executable (sys.argv[0]) is a PYZ zipapp ."""
|
|
309
|
-
# If it's a pipx installation, it is not the monolithic binary we are concerned with here.
|
|
310
|
-
if exec_path is None:
|
|
311
|
-
exec_path = Path(sys.argv[0]).resolve()
|
|
312
|
-
else:
|
|
313
|
-
exec_path = Path(exec_path).resolve()
|
|
314
302
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if not exec_path.is_file():
|
|
319
|
-
if debug: print("DEBUG:False (Not a file)")
|
|
320
|
-
return False
|
|
321
|
-
|
|
322
|
-
if is_pipx():
|
|
303
|
+
# 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)
|
|
305
|
+
if not is_valid:
|
|
323
306
|
return False
|
|
324
307
|
|
|
325
308
|
# Check if the extension is PYZ
|
|
326
309
|
if not str(exec_path).endswith(".pyz"):
|
|
310
|
+
if debug:
|
|
311
|
+
logging.debug("False (Not a .pyz file)")
|
|
327
312
|
return False
|
|
328
|
-
|
|
313
|
+
|
|
329
314
|
if not _check_if_zip(exec_path):
|
|
315
|
+
if debug:
|
|
316
|
+
logging.debug("False (Not a valid ZIP file)")
|
|
330
317
|
return False
|
|
331
318
|
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
|
|
332
322
|
def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
333
323
|
"""
|
|
334
|
-
Checks if the
|
|
335
|
-
Windows Portable Executable (PE) binary, and explicitly excludes
|
|
336
|
-
pipx-managed environments.
|
|
324
|
+
Checks if the specified path or sys.argv[0] is a Windows Portable Executable (PE) binary.
|
|
337
325
|
Windows Portable Executables include .exe, .dll, and other binaries.
|
|
338
326
|
The standard way to check for a PE is to look for the MZ magic number at the very beginning of the file.
|
|
339
327
|
"""
|
|
340
|
-
|
|
341
|
-
if
|
|
342
|
-
exec_path = Path(sys.argv[0]).resolve()
|
|
343
|
-
else:
|
|
344
|
-
exec_path = Path(exec_path).resolve()
|
|
345
|
-
|
|
346
|
-
if debug:
|
|
347
|
-
print(f"DEBUG: Checking executable path: {exec_path}")
|
|
348
|
-
|
|
349
|
-
# 2. Exclude pipx environments immediately
|
|
350
|
-
if is_pipx():
|
|
351
|
-
if debug: print("DEBUG: False (is_pipx is True)")
|
|
352
|
-
return False
|
|
353
|
-
|
|
354
|
-
# 3. Perform file checks
|
|
355
|
-
if not exec_path.is_file():
|
|
356
|
-
if debug: print("DEBUG:False (Not a file)")
|
|
328
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
329
|
+
if not is_valid:
|
|
357
330
|
return False
|
|
358
|
-
|
|
359
331
|
try:
|
|
360
|
-
# Check the magic number: All Windows PE files (EXE, DLL, etc.)
|
|
361
|
-
# start with the two-byte header b'MZ' (for Mark Zbikowski).
|
|
362
332
|
with open(exec_path, 'rb') as f:
|
|
363
333
|
magic_bytes = f.read(2)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if debug:
|
|
368
|
-
print(f"DEBUG: Magic bytes: {magic_bytes}")
|
|
369
|
-
print(f"DEBUG: {is_pe} (Non-pipx check)")
|
|
370
|
-
|
|
371
|
-
return is_pe
|
|
372
|
-
|
|
334
|
+
if debug:
|
|
335
|
+
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
336
|
+
return magic_bytes == b'MZ'
|
|
373
337
|
except Exception as e:
|
|
374
|
-
if debug:
|
|
375
|
-
|
|
338
|
+
if debug:
|
|
339
|
+
logging.debug(f"False (Error during file check: {e})")
|
|
376
340
|
return False
|
|
377
|
-
|
|
341
|
+
|
|
378
342
|
def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
379
343
|
"""
|
|
380
344
|
Checks if the currently running executable is a macOS/Darwin Mach-O binary,
|
|
381
345
|
and explicitly excludes pipx-managed environments.
|
|
382
346
|
"""
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
else:
|
|
386
|
-
exec_path = Path(exec_path).resolve()
|
|
387
|
-
if debug:
|
|
388
|
-
print(f"DEBUG: Checking executable path: {exec_path}")
|
|
389
|
-
|
|
390
|
-
if is_pipx():
|
|
391
|
-
if debug: print("DEBUG: is_macos_executable: False (is_pipx is True)")
|
|
392
|
-
return False
|
|
393
|
-
|
|
394
|
-
if not exec_path.is_file():
|
|
395
|
-
if debug: print("DEBUG:False (Not a file)")
|
|
347
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug)
|
|
348
|
+
if not is_valid:
|
|
396
349
|
return False
|
|
397
350
|
|
|
398
351
|
try:
|
|
@@ -412,73 +365,86 @@ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False
|
|
|
412
365
|
is_macho = magic_bytes in MACHO_MAGIC
|
|
413
366
|
|
|
414
367
|
if debug:
|
|
415
|
-
|
|
368
|
+
logging.debug(f"Magic bytes: {magic_bytes}")
|
|
416
369
|
|
|
417
370
|
return is_macho
|
|
418
371
|
|
|
419
372
|
except Exception:
|
|
373
|
+
if debug:
|
|
374
|
+
logging.debug("False (Exception during file check)")
|
|
420
375
|
return False
|
|
421
376
|
|
|
422
377
|
def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
|
|
423
378
|
"""Checks if the executable is running from a pipx managed environment."""
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
else:
|
|
427
|
-
exec_path = Path(exec_path).resolve()
|
|
428
|
-
if debug:
|
|
429
|
-
print(f"DEBUG: Checking executable path: {exec_path}")
|
|
430
|
-
|
|
431
|
-
if not exec_path.is_file():
|
|
432
|
-
if debug: print("DEBUG:False (Not a file)")
|
|
379
|
+
exec_path, is_valid = _check_executable_path(exec_path, debug, check_pipx=False)
|
|
380
|
+
if not is_valid:
|
|
433
381
|
return False
|
|
382
|
+
|
|
434
383
|
try:
|
|
435
|
-
# Helper for case-insensitivity on Windows
|
|
436
|
-
def normalize_path(p: Path) -> str:
|
|
437
|
-
return str(p).lower()
|
|
438
|
-
|
|
439
|
-
# This is the path to the interpreter running the script (e.g., venv/bin/python)
|
|
440
|
-
# In a pipx-managed execution, this is the venv python.
|
|
441
384
|
interpreter_path = Path(sys.executable).resolve()
|
|
442
385
|
pipx_bin_path, pipx_venv_base_path = _get_pipx_paths()
|
|
443
386
|
# Normalize paths for comparison
|
|
444
|
-
norm_exec_path =
|
|
445
|
-
norm_interp_path =
|
|
387
|
+
norm_exec_path = str(exec_path).lower()
|
|
388
|
+
norm_interp_path = str(interpreter_path).lower()
|
|
446
389
|
|
|
447
390
|
if debug:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# ------------------------
|
|
455
|
-
|
|
456
|
-
# 1. Signature Check (Most Robust): Look for the unique 'pipx/venvs' string.
|
|
457
|
-
# This is a strong check for both the executable path (your discovery)
|
|
458
|
-
# and the interpreter path (canonical venv location).
|
|
391
|
+
logging.debug(f"EXEC_PATH: {exec_path}")
|
|
392
|
+
logging.debug(f"INTERP_PATH: {interpreter_path}")
|
|
393
|
+
logging.debug(f"PIPX_BIN_PATH: {pipx_bin_path}")
|
|
394
|
+
logging.debug(f"PIPX_VENV_BASE: {pipx_venv_base_path}")
|
|
395
|
+
logging.debug(f"Check B result: {norm_interp_path.startswith(str(pipx_venv_base_path).lower())}")
|
|
396
|
+
|
|
459
397
|
if "pipx/venvs" in norm_exec_path or "pipx/venvs" in norm_interp_path:
|
|
460
|
-
if debug:
|
|
398
|
+
if debug:
|
|
399
|
+
logging.debug("True (Signature Check)")
|
|
461
400
|
return True
|
|
462
401
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if debug: print("is_pipx: True (Interpreter Base Check)")
|
|
402
|
+
if norm_interp_path.startswith(str(pipx_venv_base_path).lower()):
|
|
403
|
+
if debug:
|
|
404
|
+
logging.debug("True (Interpreter Base Check)")
|
|
467
405
|
return True
|
|
468
|
-
|
|
469
|
-
# 3. Targeted Executable Check: The executable's resolved path starts with the PIPX venv base.
|
|
470
|
-
# This is your key Termux discovery, confirming the shim resolves into the venv.
|
|
471
|
-
if norm_exec_path.startswith(normalize_path(pipx_venv_base_path)):
|
|
472
|
-
if debug: print("is_pipx: True (Executable Base Check)")
|
|
473
|
-
return True
|
|
474
406
|
|
|
475
|
-
if
|
|
476
|
-
|
|
407
|
+
if norm_exec_path.startswith(str(pipx_venv_base_path).lower()):
|
|
408
|
+
if debug:
|
|
409
|
+
logging.debug("True (Executable Base Check)")
|
|
410
|
+
return True
|
|
477
411
|
|
|
412
|
+
if debug:
|
|
413
|
+
logging.debug("False")
|
|
414
|
+
return False
|
|
478
415
|
except Exception:
|
|
479
|
-
|
|
416
|
+
if debug:
|
|
417
|
+
logging.debug("False (Exception during pipx check)")
|
|
480
418
|
return False
|
|
419
|
+
if debug:
|
|
420
|
+
logging.debug(f"EXEC_PATH: {exec_path}")
|
|
421
|
+
logging.debug(f"INTERP_PATH: {interpreter_path}")
|
|
422
|
+
logging.debug(f"PIPX_BIN_PATH: {pipx_bin_path}")
|
|
423
|
+
logging.debug(f"PIPX_VENV_BASE: {pipx_venv_base_path}")
|
|
424
|
+
logging.debug(f"Check B result: {norm_interp_path.startswith(str(pipx_venv_base_path).lower())}")
|
|
425
|
+
|
|
426
|
+
if "pipx/venvs" in norm_exec_path or "pipx/venvs" in norm_interp_path:
|
|
427
|
+
if debug:
|
|
428
|
+
logging.debug("True (Signature Check)")
|
|
429
|
+
return True
|
|
430
|
+
|
|
431
|
+
if norm_interp_path.startswith(str(pipx_venv_base_path).lower()):
|
|
432
|
+
if debug:
|
|
433
|
+
logging.debug("True (Interpreter Base Check)")
|
|
434
|
+
return True
|
|
435
|
+
|
|
436
|
+
if norm_exec_path.startswith(str(pipx_venv_base_path).lower()):
|
|
437
|
+
if debug:
|
|
438
|
+
logging.debug("True (Executable Base Check)")
|
|
439
|
+
return True
|
|
481
440
|
|
|
441
|
+
if debug:
|
|
442
|
+
logging.debug("False")
|
|
443
|
+
return False
|
|
444
|
+
except Exception:
|
|
445
|
+
if debug:
|
|
446
|
+
logging.debug("False (Exception during pipx check)")
|
|
447
|
+
return False
|
|
482
448
|
def is_python_script(path: Path | str | None = None, debug: bool = False) -> bool:
|
|
483
449
|
"""
|
|
484
450
|
Checks if the specified path or running script is a Python source file (.py).
|
|
@@ -493,13 +459,8 @@ def is_python_script(path: Path | str | None = None, debug: bool = False) -> boo
|
|
|
493
459
|
Returns:
|
|
494
460
|
bool: True if the specified or default path is a Python source file (.py); False otherwise.
|
|
495
461
|
"""
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
else:
|
|
499
|
-
exec_path = Path(path).resolve()
|
|
500
|
-
if debug:
|
|
501
|
-
print(f"Checking Python script for path: {exec_path}")
|
|
502
|
-
if not exec_path.is_file():
|
|
462
|
+
exec_path, is_valid = _check_executable_path(path, debug, check_pipx=False)
|
|
463
|
+
if not is_valid:
|
|
503
464
|
return False
|
|
504
465
|
return exec_path.suffix.lower() == '.py'
|
|
505
466
|
|
|
@@ -601,7 +562,8 @@ def edit_textfile(path: Path | str | None = None) -> None:
|
|
|
601
562
|
"""Why Not Use check=True on Termux:
|
|
602
563
|
The pkg utility in Termux is a wrapper around Debian's apt. When you run pkg install <package>, if the package is already installed, the utility often returns an exit code of 100 (or another non-zero value) to indicate that no changes were made because the package was already present.
|
|
603
564
|
"""
|
|
604
|
-
|
|
565
|
+
|
|
566
|
+
# --- Helper Functions ---
|
|
605
567
|
def _run_dos2unix(path: Path | str | None = None):
|
|
606
568
|
"""Attempt to run dos2unix, failing silently if not installed."""
|
|
607
569
|
|
|
@@ -657,4 +619,100 @@ def _check_if_zip(path: Path | str | None) -> bool:
|
|
|
657
619
|
except Exception:
|
|
658
620
|
# Handle cases where the path might be invalid, or other unexpected errors
|
|
659
621
|
return False
|
|
660
|
-
|
|
622
|
+
|
|
623
|
+
def _check_executable_path(exec_path: Path | str | None, debug: bool = False, check_pipx: bool = True) -> tuple[Path | None, bool]:
|
|
624
|
+
"""Helper function to resolve executable path and perform common checks."""
|
|
625
|
+
if exec_path is None:
|
|
626
|
+
exec_path = Path(sys.argv[0]).resolve() if sys.argv[0] and sys.argv[0] != '-c' else None
|
|
627
|
+
else:
|
|
628
|
+
exec_path = Path(exec_path).resolve()
|
|
629
|
+
|
|
630
|
+
if debug:
|
|
631
|
+
logging.debug(f"Checking executable path: {exec_path}")
|
|
632
|
+
|
|
633
|
+
if exec_path is None:
|
|
634
|
+
if debug:
|
|
635
|
+
logging.debug("False (No valid path)")
|
|
636
|
+
return None, False
|
|
637
|
+
|
|
638
|
+
if check_pipx and is_pipx(exec_path, debug):
|
|
639
|
+
if debug:
|
|
640
|
+
logging.debug("False (is_pipx is True)")
|
|
641
|
+
return exec_path, False
|
|
642
|
+
|
|
643
|
+
if not exec_path.is_file():
|
|
644
|
+
if debug:
|
|
645
|
+
logging.debug("False (Not a file)")
|
|
646
|
+
return exec_path, False
|
|
647
|
+
|
|
648
|
+
return exec_path, True
|
|
649
|
+
|
|
650
|
+
# --- Main Function for report and CLI compatibility ---
|
|
651
|
+
|
|
652
|
+
def main(path=None, debug=False):
|
|
653
|
+
"""Print a comprehensive environment report.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
path (Path | str | None): Path to inspect (defaults to sys.argv[0]).
|
|
657
|
+
debug (bool): Enable verbose debug output.
|
|
658
|
+
"""
|
|
659
|
+
if debug:
|
|
660
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
661
|
+
logging.getLogger('matplotlib').setLevel(logging.WARNING) # Suppress matplotlib debug logs
|
|
662
|
+
print("PyHabitat Environment Report")
|
|
663
|
+
print("===========================")
|
|
664
|
+
print("\nCurrent Build Checks // Based on hasattr(sys,..) and getattr(sys,..)")
|
|
665
|
+
print("------------------------------")
|
|
666
|
+
print(f"in_repl(): {in_repl()}")
|
|
667
|
+
print(f"as_frozen(): {as_frozen()}")
|
|
668
|
+
print(f"as_pyinstaller(): {as_pyinstaller()}")
|
|
669
|
+
print("\nOperating System Checks // Based on platform.system()")
|
|
670
|
+
print("------------------------------")
|
|
671
|
+
print(f"on_termux(): {on_termux()}")
|
|
672
|
+
print(f"on_windows(): {on_windows()}")
|
|
673
|
+
print(f"on_apple(): {on_apple()}")
|
|
674
|
+
print(f"on_linux(): {on_linux()}")
|
|
675
|
+
print(f"on_ish_alpine(): {on_ish_alpine()}")
|
|
676
|
+
print(f"on_android(): {on_android()}")
|
|
677
|
+
print(f"on_freebsd(): {on_freebsd()}")
|
|
678
|
+
print("\nCapability Checks")
|
|
679
|
+
print("-------------------------")
|
|
680
|
+
print(f"tkinter_is_available(): {tkinter_is_available()}")
|
|
681
|
+
print(f"matplotlib_is_available_for_gui_plotting(): {matplotlib_is_available_for_gui_plotting()}")
|
|
682
|
+
print(f"matplotlib_is_available_for_headless_image_export(): {matplotlib_is_available_for_headless_image_export()}")
|
|
683
|
+
print(f"web_browser_is_available(): {web_browser_is_available()}")
|
|
684
|
+
print(f"interactive_terminal_is_available(): {interactive_terminal_is_available()}")
|
|
685
|
+
print("\nInterpreter Checks // Based on sys.executable()")
|
|
686
|
+
print("-----------------------------")
|
|
687
|
+
print(f"interp_path(): {interp_path()}")
|
|
688
|
+
print(f"is_elf(interp_path()): {is_elf(interp_path(), debug=debug)}")
|
|
689
|
+
print(f"is_windows_portable_executable(interp_path()): {is_windows_portable_executable(interp_path(), debug=debug)}")
|
|
690
|
+
print(f"is_macos_executable(interp_path()): {is_macos_executable(interp_path(), debug=debug)}")
|
|
691
|
+
print(f"is_pyz(interp_path()): {is_pyz(interp_path(), debug=debug)}")
|
|
692
|
+
print(f"is_pipx(interp_path()): {is_pipx(interp_path(), debug=debug)}")
|
|
693
|
+
print(f"is_python_script(interp_path()): {is_python_script(interp_path(), debug=debug)}")
|
|
694
|
+
print("\nCurrent Environment Check // Based on sys.argv[0]")
|
|
695
|
+
print("-----------------------------")
|
|
696
|
+
inspect_path = path if path is not None else (None if sys.argv[0] == '-c' else sys.argv[0])
|
|
697
|
+
logging.debug(f"Inspecting path: {inspect_path}")
|
|
698
|
+
# Early validation of path
|
|
699
|
+
if path is not None:
|
|
700
|
+
path_obj = Path(path)
|
|
701
|
+
if not path_obj.is_file():
|
|
702
|
+
print(f"Error: '{path}' is not a valid file or does not exist.")
|
|
703
|
+
if debug:
|
|
704
|
+
logging.error(f"Invalid path: '{path}' is not a file or does not exist.")
|
|
705
|
+
raise SystemExit(1)
|
|
706
|
+
script_path = None
|
|
707
|
+
if path or (sys.argv[0] and sys.argv[0] != '-c'):
|
|
708
|
+
script_path = Path(path or sys.argv[0]).resolve()
|
|
709
|
+
logging.debug(f"Script path resolved: {script_path}")
|
|
710
|
+
if script_path is not None:
|
|
711
|
+
print(f"is_elf(): {is_elf(script_path, debug=debug)}")
|
|
712
|
+
print(f"is_windows_portable_executable(): {is_windows_portable_executable(script_path, debug=debug)}")
|
|
713
|
+
print(f"is_macos_executable(): {is_macos_executable(script_path, debug=debug)}")
|
|
714
|
+
print(f"is_pyz(): {is_pyz(script_path, debug=debug)}")
|
|
715
|
+
print(f"is_pipx(): {is_pipx(script_path, debug=debug)}")
|
|
716
|
+
print(f"is_python_script(): {is_python_script(script_path, debug=debug)}")
|
|
717
|
+
else:
|
|
718
|
+
print("script_path is None")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyhabitat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.18
|
|
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
|
|
@@ -8,7 +8,7 @@ Keywords: environment,os-detection,gui,build-system
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Topic :: System :: Systems Administration
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Dynamic: license-file
|
|
@@ -130,7 +130,7 @@ Key Question: "What could I do next?"
|
|
|
130
130
|
|
|
131
131
|
The module exposes all detection functions directly for easy access.
|
|
132
132
|
|
|
133
|
-
### 0\.Example of PyHabitat in Action
|
|
133
|
+
### 0\. Example of PyHabitat in Action
|
|
134
134
|
|
|
135
135
|
The `pipeline-eds` package uses the `pyhabitat` library to handle [configuration](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/security_and_config.py) and [plotting](https://github.com/City-of-Memphis-Wastewater/pipeline/blob/main/src/pipeline/cli.py), among other things.
|
|
136
136
|
|
|
@@ -3,8 +3,11 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
pyhabitat/__init__.py
|
|
5
5
|
pyhabitat/__main__.py
|
|
6
|
+
pyhabitat/__main__stable.py
|
|
7
|
+
pyhabitat/cli.py
|
|
6
8
|
pyhabitat/environment.py
|
|
7
9
|
pyhabitat.egg-info/PKG-INFO
|
|
8
10
|
pyhabitat.egg-info/SOURCES.txt
|
|
9
11
|
pyhabitat.egg-info/dependency_links.txt
|
|
12
|
+
pyhabitat.egg-info/entry_points.txt
|
|
10
13
|
pyhabitat.egg-info/top_level.txt
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# pyproject.toml
|
|
2
2
|
|
|
3
3
|
[build-system]
|
|
4
|
-
requires = ["setuptools>=61.0.0"]
|
|
4
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
5
5
|
build-backend = "setuptools.build_meta"
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyhabitat"
|
|
9
|
-
version = "1.0.
|
|
9
|
+
version = "1.0.18"
|
|
10
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
|
-
requires-python = ">=3.
|
|
16
|
+
requires-python = ">=3.7"
|
|
17
17
|
license = "MIT"
|
|
18
18
|
keywords = ["environment", "os-detection", "gui", "build-system"]
|
|
19
19
|
classifiers = [
|
|
@@ -21,10 +21,14 @@ classifiers = [
|
|
|
21
21
|
"Operating System :: OS Independent",
|
|
22
22
|
"Topic :: System :: Systems Administration",
|
|
23
23
|
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
pyhabitat = "pyhabitat.cli:run_cli"
|
|
24
28
|
|
|
25
29
|
[tool.setuptools.packages.find]
|
|
26
30
|
where = ["."]
|
|
27
|
-
include = ["pyhabitat"]
|
|
31
|
+
include = ["pyhabitat*"]
|
|
28
32
|
|
|
29
33
|
[tool.setuptools_scm]
|
|
30
34
|
# This tells setuptools_scm to look at your git history/tags for the version
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|