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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.17
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.8
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,4 @@
1
+ from .cli import run_cli
2
+
3
+ if __name__ == "__main__":
4
+ run_cli()
@@ -1,3 +1,5 @@
1
+ #from .cli import run_cli
2
+
1
3
  from .environment import (
2
4
  in_repl,
3
5
  on_termux,
@@ -67,3 +69,4 @@ def main():
67
69
 
68
70
  if __name__ == "__main__":
69
71
  main()
72
+ #run_cli()
@@ -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 exec_path is None:
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
- # Handle exceptions like PermissionError, IsADirectoryError, etc.
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
- if debug:
316
- print(f"DEBUG: Checking executable path: {exec_path}")
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 currently running executable (sys.argv[0]) is a
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
- # 1. Determine execution path
341
- if exec_path is None:
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
- is_pe = magic_bytes == b'MZ'
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: print(f"DEBUG: Error during file check: {e}")
375
- # Handle exceptions like PermissionError, IsADirectoryError, etc.
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
- if exec_path is None:
384
- exec_path = Path(sys.argv[0]).resolve()
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
- print(f"DEBUG: is_macos_executable: {is_macho} (Non-pipx check)")
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
- if exec_path is None:
425
- exec_path = Path(sys.argv[0]).resolve()
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 = normalize_path(exec_path)
445
- norm_interp_path = normalize_path(interpreter_path)
387
+ norm_exec_path = str(exec_path).lower()
388
+ norm_interp_path = str(interpreter_path).lower()
446
389
 
447
390
  if debug:
448
- # --- DEBUGGING OUTPUT ---
449
- print(f"DEBUG: EXEC_PATH: {exec_path}")
450
- print(f"DEBUG: INTERP_PATH: {interpreter_path}")
451
- print(f"DEBUG: PIPX_BIN_PATH: {pipx_bin_path}")
452
- print(f"DEBUG: PIPX_VENV_BASE: {pipx_venv_base_path}")
453
- print(f"DEBUG: Check B result: {normalize_path(interpreter_path).startswith(normalize_path(pipx_venv_base_path))}")
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: print("is_pipx: True (Signature Check)")
398
+ if debug:
399
+ logging.debug("True (Signature Check)")
461
400
  return True
462
401
 
463
- # 2. Targeted Venv Check: The interpreter's path starts with the PIPX venv base.
464
- # This is a canonical check if the signature check is somehow missed.
465
- if norm_interp_path.startswith(normalize_path(pipx_venv_base_path)):
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 debug: print("is_pipx: False")
476
- return False
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
- # Fallback for unexpected path errors
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
- if path is None:
497
- exec_path = Path(sys.argv[0]).resolve()
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.17
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.8
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
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyhabitat = pyhabitat.cli:run_cli
@@ -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.17"
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.8"
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"] # Only include the main package
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