pyhabitat 1.0.15__tar.gz → 1.0.17__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.15
3
+ Version: 1.0.17
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
@@ -59,11 +59,11 @@ Ultimately, [City-of-Memphis-Wastewater](https://github.com/City-of-Memphis-Wast
59
59
 
60
60
  * **Definitive Environment Checks:** Rigorous checks catered to Termux and iSH (iOS Alpine). Accurate, typical modern detection for Windows, macOS (Apple), Linux, FreeBSD, Android.
61
61
  * **GUI Availability:** Rigorous, cached checks to determine if the environment supports a graphical popup window (Tkinter/Matplotlib TkAgg) or just headless image export (Matplotlib Agg).
62
- * **Build/Packaging Detection:** Reliable detection of standalone executables built by tools like PyInstaller, and, crucially, correct identification and exclusion of pipx-managed virtual environments, which also user binaries that could conflate the check.
63
- * **Executable Type Inspection:** Uses file magic numbers (ELF and MZ) to confirm if the running script is a monolithic, frozen binary (non-pipx).
62
+ * **Build/Packaging Detection:** Reliable detection of standalone executables (PyInstaller), Python zipapps (.pyz), Python source scripts (.py), and correct identification/exclusion of pipx-managed virtual environments.
63
+ * **Executable Type Inspection:** Uses file magic numbers (ELF, MZ, Mach-O) to confirm if the running script is a monolithic, frozen binary (non-pipx) or zipapp (.pyz).
64
64
 
65
65
  </details>
66
-
66
+
67
67
  ---
68
68
 
69
69
  <details>
@@ -75,24 +75,31 @@ Key question: "What is this running on?"
75
75
 
76
76
  | Function | Description |
77
77
  | :--- | :--- |
78
- | `is_windows()` | Returns `True` on Windows. |
79
- | `is_apple()` | Returns `True` on macOS (Darwin). |
80
- | `is_linux()` | Returns `True` on Linux in general. |
81
- | `is_termux()` | Returns `True` if running in the Termux Android environment. |
82
- | `is_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
83
- | `is_android()` | Returns `True` on any Android-based Linux environment. |
78
+ | `on_windows()` | Returns `True` on Windows. |
79
+ | `on_apple()` | Returns `True` on macOS (Darwin). |
80
+ | `on_linux()` | Returns `True` on Linux in general. |
81
+ | `on_termux()` | Returns `True` if running in the Termux Android environment. |
82
+ | `on_freebsd()` | Returns `True` on FreeBSD. |
83
+ | `on_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
84
+ | `on_android()` | Returns `True` on any Android-based Linux environment. |
85
+ | `in_repl()` | Returns `True` is the user is currently in a Python REPL; hasattr(sys,'ps1'). |
84
86
 
85
87
  ### Packaging and Build Checking
86
88
 
87
- Key question: "What is the character of my executable?"
89
+ Key question: "What is the character of my executable or my build state?"
90
+
91
+ These functions accept an optional path argument (Path or str), defaulting to sys.argv[0] (e.g., pyhabitat/__main__.py for python -m pyhabitat, empty in REPL). Path.resolve() is used for stability.
88
92
 
89
93
  | Function | Description |
90
94
  | :--- | :--- |
91
- | `is_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
92
- | `is_pipx()` | Returns `True` if running from a pipx managed virtual environment. |
93
- | `is_elf()` | Checks if the executable is an ELF binary (Linux standalone executable), excluding pipx. |
94
- | `is_windows_portable_executable()` | Checks if the executable is a Windows PE binary (MZ header), excluding pipx. |
95
- | `is_macos_executable()` | Checks if the executable is a macOS/Darwin Mach-O binary, excluding pipx. |
95
+ | `as_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
96
+ | `as_pyinstaller()` | Returns `True` if the script is frozen and generated by PyInstaller (has `_MEIPASS`). |
97
+ | `is_python_script(path=None)` | Returns `True` if the script or specified path is a Python source file (.py). |
98
+ | `is_pipx(path=None)` | Returns `True` if the script or specified path is from a pipx-managed virtual environment. |
99
+ | `is_elf(path=None)` | Returns `True` if the script or specified path is an ELF binary (Linux standalone executable, non-pipx). |
100
+ | `is_pyz(path=None)` | Returns `True` if the script or specified path is a Python zipapp (.pyz, non-pipx). |
101
+ | `is_windows_portable_executable(path=None)` | Returns `True` if the script or specified path is a Windows PE binary (MZ header, non-pipx). |
102
+ | `is_macos_executable(path=None)` | Returns `True` if the script or specified path is a macOS Mach-O binary (non-pipx). |
96
103
 
97
104
  ### Capability Checking
98
105
 
@@ -101,16 +108,18 @@ Key Question: "What could I do next?"
101
108
  | Function | Description |
102
109
  | :--- | :--- |
103
110
  | `tkinter_is_available()` | Checks if Tkinter is imported and can successfully create a window. |
104
- | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. |
111
+ | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. Set `termux_has_gui=True` for Termux with GUI support; defaults to `False`. |
105
112
  | `matplotlib_is_available_for_headless_image_export()` | Checks for Matplotlib and its Agg backend, required for saving images without a GUI. |
106
113
  | `interactive_terminal_is_available()` | Checks if standard input and output streams are connected to a TTY (allows safe use of interactive prompts). |
107
114
  | `web_browser_is_available()` | Check if a web browser can be launched in the current environment (allows safe use of web-based prompts and localhost plotting). |
108
115
 
109
- ### Actions
116
+ ### Utility
110
117
 
111
118
  | Function | Description |
112
119
  | :--- | :--- |
113
- | `open_text_file_for_editing()` | Smoothly opens a text file for editing (for configuration editing prompted by a CLI flag). |
120
+ | `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(print_path=False)` | Returns the path to the Python interpreter binary (sys.executable). Optionally prints the path. Returns empty string if unavailable. |
122
+ | `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. |
114
123
 
115
124
  </details>
116
125
 
@@ -121,39 +130,57 @@ Key Question: "What could I do next?"
121
130
 
122
131
  The module exposes all detection functions directly for easy access.
123
132
 
124
- ### 0\. Current Use
133
+ ### 0\.Example of PyHabitat in Action
125
134
 
126
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.
127
136
 
128
- ### 1\. Checking Environment and Build Type
137
+ ### 1\. Running the Environment Report
138
+
139
+ Run a comprehensive environment report from the command line or REPL to inspect the interpreter (sys.executable), running script (sys.argv[0]), build state, operating system, and capabilities.
140
+
141
+ ```bash
142
+ # In the terminal
143
+ python -m pyhabitat
144
+ ```
145
+
146
+ ```python
147
+ # In the Python REPL
148
+ import pyhabitat as ph
149
+ ph.main()
150
+ ```
151
+
152
+ ### 2\. Checking Environment and Build Type
129
153
 
130
154
  ```python
131
- from pyhabitat import is_termux, is_windows, is_pipx, is_frozen
155
+ from pyhabitat import on_termux, on_windows, is_pipx, is_python_script, as_frozen
132
156
 
133
157
  if is_pipx():
134
158
  print("Running inside a pipx virtual environment. This is not a standalone binary.")
135
159
 
136
- elif is_frozen():
160
+ if as_frozen():
137
161
  print("Running as a frozen executable (PyInstaller, cx_Freeze, etc.).")
138
162
 
139
- elif is_termux():
163
+ if is_python_script():
164
+ print("Running as a Python source script (.py).")
165
+
166
+ if on_termux():
140
167
  # Expected cases:
141
168
  #- pkg install python-numpy python-cryptography
142
- #- Avoiding matplotlib unless the user explicitly confirms that termux_has_gui=False in matplotlib_is_available_for_gui_plotting(termux_has_gui=False).
169
+ #- Avoiding matplotlib unless the user explicitly sets termux_has_gui=True in matplotlib_is_available_for_gui_plotting().
143
170
  #- Auto-selection of 'termux-open-url' and 'xdg-open' in logic.
144
171
  #- Installation on the system, like orchestrating the construction of Termux Widget entries in ~/.shortcuts.
145
172
  print("Running in the Termux environment on Android.")
146
173
 
147
- elif is_windows():
174
+ if on_windows():
148
175
  print("Running on Windows.")
149
176
  ```
150
177
 
151
- ### 2\. Checking GUI and Plotting Availability
178
+ ### 3\. Checking GUI and Plotting Availability
152
179
 
153
180
  Use these functions to determine if you can show an interactive plot or if you must save an image file.
154
181
 
155
182
  ```python
156
- from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export,
183
+ from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export
157
184
 
158
185
  if matplotlib_is_available_for_gui_plotting():
159
186
  # We can safely call plt.show()
@@ -171,13 +198,16 @@ else:
171
198
  print("Matplotlib is not installed or the environment is too restrictive for plotting.")
172
199
  ```
173
200
 
174
- ### 3\. Text Editing
201
+ ### 4\. Text Editing
175
202
 
176
- Use this function to smoothly open a text file for editing.
203
+ Use this function to open a text file for editing.
177
204
  Ideal use case: Edit a configuration file, if prompted by a CLI command like 'config --textedit'.
178
205
 
179
206
  ```python
180
- open_text_file_for_editing(filepath=Path('./config.json'))
207
+ from pathlib import Path
208
+ import pyhabitat as ph
209
+
210
+ ph.edit_textfile(path=Path('./config.json'))
181
211
  ```
182
212
  </details>
183
213
 
@@ -44,11 +44,11 @@ Ultimately, [City-of-Memphis-Wastewater](https://github.com/City-of-Memphis-Wast
44
44
 
45
45
  * **Definitive Environment Checks:** Rigorous checks catered to Termux and iSH (iOS Alpine). Accurate, typical modern detection for Windows, macOS (Apple), Linux, FreeBSD, Android.
46
46
  * **GUI Availability:** Rigorous, cached checks to determine if the environment supports a graphical popup window (Tkinter/Matplotlib TkAgg) or just headless image export (Matplotlib Agg).
47
- * **Build/Packaging Detection:** Reliable detection of standalone executables built by tools like PyInstaller, and, crucially, correct identification and exclusion of pipx-managed virtual environments, which also user binaries that could conflate the check.
48
- * **Executable Type Inspection:** Uses file magic numbers (ELF and MZ) to confirm if the running script is a monolithic, frozen binary (non-pipx).
47
+ * **Build/Packaging Detection:** Reliable detection of standalone executables (PyInstaller), Python zipapps (.pyz), Python source scripts (.py), and correct identification/exclusion of pipx-managed virtual environments.
48
+ * **Executable Type Inspection:** Uses file magic numbers (ELF, MZ, Mach-O) to confirm if the running script is a monolithic, frozen binary (non-pipx) or zipapp (.pyz).
49
49
 
50
50
  </details>
51
-
51
+
52
52
  ---
53
53
 
54
54
  <details>
@@ -60,24 +60,31 @@ Key question: "What is this running on?"
60
60
 
61
61
  | Function | Description |
62
62
  | :--- | :--- |
63
- | `is_windows()` | Returns `True` on Windows. |
64
- | `is_apple()` | Returns `True` on macOS (Darwin). |
65
- | `is_linux()` | Returns `True` on Linux in general. |
66
- | `is_termux()` | Returns `True` if running in the Termux Android environment. |
67
- | `is_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
68
- | `is_android()` | Returns `True` on any Android-based Linux environment. |
63
+ | `on_windows()` | Returns `True` on Windows. |
64
+ | `on_apple()` | Returns `True` on macOS (Darwin). |
65
+ | `on_linux()` | Returns `True` on Linux in general. |
66
+ | `on_termux()` | Returns `True` if running in the Termux Android environment. |
67
+ | `on_freebsd()` | Returns `True` on FreeBSD. |
68
+ | `on_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
69
+ | `on_android()` | Returns `True` on any Android-based Linux environment. |
70
+ | `in_repl()` | Returns `True` is the user is currently in a Python REPL; hasattr(sys,'ps1'). |
69
71
 
70
72
  ### Packaging and Build Checking
71
73
 
72
- Key question: "What is the character of my executable?"
74
+ Key question: "What is the character of my executable or my build state?"
75
+
76
+ These functions accept an optional path argument (Path or str), defaulting to sys.argv[0] (e.g., pyhabitat/__main__.py for python -m pyhabitat, empty in REPL). Path.resolve() is used for stability.
73
77
 
74
78
  | Function | Description |
75
79
  | :--- | :--- |
76
- | `is_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
77
- | `is_pipx()` | Returns `True` if running from a pipx managed virtual environment. |
78
- | `is_elf()` | Checks if the executable is an ELF binary (Linux standalone executable), excluding pipx. |
79
- | `is_windows_portable_executable()` | Checks if the executable is a Windows PE binary (MZ header), excluding pipx. |
80
- | `is_macos_executable()` | Checks if the executable is a macOS/Darwin Mach-O binary, excluding pipx. |
80
+ | `as_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
81
+ | `as_pyinstaller()` | Returns `True` if the script is frozen and generated by PyInstaller (has `_MEIPASS`). |
82
+ | `is_python_script(path=None)` | Returns `True` if the script or specified path is a Python source file (.py). |
83
+ | `is_pipx(path=None)` | Returns `True` if the script or specified path is from a pipx-managed virtual environment. |
84
+ | `is_elf(path=None)` | Returns `True` if the script or specified path is an ELF binary (Linux standalone executable, non-pipx). |
85
+ | `is_pyz(path=None)` | Returns `True` if the script or specified path is a Python zipapp (.pyz, non-pipx). |
86
+ | `is_windows_portable_executable(path=None)` | Returns `True` if the script or specified path is a Windows PE binary (MZ header, non-pipx). |
87
+ | `is_macos_executable(path=None)` | Returns `True` if the script or specified path is a macOS Mach-O binary (non-pipx). |
81
88
 
82
89
  ### Capability Checking
83
90
 
@@ -86,16 +93,18 @@ Key Question: "What could I do next?"
86
93
  | Function | Description |
87
94
  | :--- | :--- |
88
95
  | `tkinter_is_available()` | Checks if Tkinter is imported and can successfully create a window. |
89
- | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. |
96
+ | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. Set `termux_has_gui=True` for Termux with GUI support; defaults to `False`. |
90
97
  | `matplotlib_is_available_for_headless_image_export()` | Checks for Matplotlib and its Agg backend, required for saving images without a GUI. |
91
98
  | `interactive_terminal_is_available()` | Checks if standard input and output streams are connected to a TTY (allows safe use of interactive prompts). |
92
99
  | `web_browser_is_available()` | Check if a web browser can be launched in the current environment (allows safe use of web-based prompts and localhost plotting). |
93
100
 
94
- ### Actions
101
+ ### Utility
95
102
 
96
103
  | Function | Description |
97
104
  | :--- | :--- |
98
- | `open_text_file_for_editing()` | Smoothly opens a text file for editing (for configuration editing prompted by a CLI flag). |
105
+ | `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. |
106
+ | `interp_path(print_path=False)` | Returns the path to the Python interpreter binary (sys.executable). Optionally prints the path. Returns empty string if unavailable. |
107
+ | `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. |
99
108
 
100
109
  </details>
101
110
 
@@ -106,39 +115,57 @@ Key Question: "What could I do next?"
106
115
 
107
116
  The module exposes all detection functions directly for easy access.
108
117
 
109
- ### 0\. Current Use
118
+ ### 0\.Example of PyHabitat in Action
110
119
 
111
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.
112
121
 
113
- ### 1\. Checking Environment and Build Type
122
+ ### 1\. Running the Environment Report
123
+
124
+ Run a comprehensive environment report from the command line or REPL to inspect the interpreter (sys.executable), running script (sys.argv[0]), build state, operating system, and capabilities.
125
+
126
+ ```bash
127
+ # In the terminal
128
+ python -m pyhabitat
129
+ ```
130
+
131
+ ```python
132
+ # In the Python REPL
133
+ import pyhabitat as ph
134
+ ph.main()
135
+ ```
136
+
137
+ ### 2\. Checking Environment and Build Type
114
138
 
115
139
  ```python
116
- from pyhabitat import is_termux, is_windows, is_pipx, is_frozen
140
+ from pyhabitat import on_termux, on_windows, is_pipx, is_python_script, as_frozen
117
141
 
118
142
  if is_pipx():
119
143
  print("Running inside a pipx virtual environment. This is not a standalone binary.")
120
144
 
121
- elif is_frozen():
145
+ if as_frozen():
122
146
  print("Running as a frozen executable (PyInstaller, cx_Freeze, etc.).")
123
147
 
124
- elif is_termux():
148
+ if is_python_script():
149
+ print("Running as a Python source script (.py).")
150
+
151
+ if on_termux():
125
152
  # Expected cases:
126
153
  #- pkg install python-numpy python-cryptography
127
- #- Avoiding matplotlib unless the user explicitly confirms that termux_has_gui=False in matplotlib_is_available_for_gui_plotting(termux_has_gui=False).
154
+ #- Avoiding matplotlib unless the user explicitly sets termux_has_gui=True in matplotlib_is_available_for_gui_plotting().
128
155
  #- Auto-selection of 'termux-open-url' and 'xdg-open' in logic.
129
156
  #- Installation on the system, like orchestrating the construction of Termux Widget entries in ~/.shortcuts.
130
157
  print("Running in the Termux environment on Android.")
131
158
 
132
- elif is_windows():
159
+ if on_windows():
133
160
  print("Running on Windows.")
134
161
  ```
135
162
 
136
- ### 2\. Checking GUI and Plotting Availability
163
+ ### 3\. Checking GUI and Plotting Availability
137
164
 
138
165
  Use these functions to determine if you can show an interactive plot or if you must save an image file.
139
166
 
140
167
  ```python
141
- from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export,
168
+ from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export
142
169
 
143
170
  if matplotlib_is_available_for_gui_plotting():
144
171
  # We can safely call plt.show()
@@ -156,13 +183,16 @@ else:
156
183
  print("Matplotlib is not installed or the environment is too restrictive for plotting.")
157
184
  ```
158
185
 
159
- ### 3\. Text Editing
186
+ ### 4\. Text Editing
160
187
 
161
- Use this function to smoothly open a text file for editing.
188
+ Use this function to open a text file for editing.
162
189
  Ideal use case: Edit a configuration file, if prompted by a CLI command like 'config --textedit'.
163
190
 
164
191
  ```python
165
- open_text_file_for_editing(filepath=Path('./config.json'))
192
+ from pathlib import Path
193
+ import pyhabitat as ph
194
+
195
+ ph.edit_textfile(path=Path('./config.json'))
166
196
  ```
167
197
  </details>
168
198
 
@@ -174,4 +204,4 @@ Contributions are welcome\! If you find an environment or build system that is n
174
204
 
175
205
  ## 📄 License
176
206
 
177
- This project is licensed under the MIT License. See the LICENSE file for details.
207
+ This project is licensed under the MIT License. See the LICENSE file for details.
@@ -4,45 +4,54 @@ from .environment import (
4
4
  matplotlib_is_available_for_gui_plotting,
5
5
  matplotlib_is_available_for_headless_image_export,
6
6
  tkinter_is_available,
7
- is_termux,
8
- is_freebsd,
9
- is_linux,
10
- is_android,
11
- is_windows,
12
- is_apple,
13
- is_ish_alpine,
14
- pyinstaller,
15
- is_frozen,
7
+ in_repl,
8
+ on_termux,
9
+ on_freebsd,
10
+ on_linux,
11
+ on_android,
12
+ on_windows,
13
+ on_apple,
14
+ on_ish_alpine,
15
+ as_pyinstaller,
16
+ as_frozen,
16
17
  is_elf,
17
18
  is_pyz,
18
19
  is_windows_portable_executable,
19
20
  is_macos_executable,
20
21
  is_pipx,
22
+ is_python_script,
21
23
  interactive_terminal_is_available,
22
24
  web_browser_is_available,
23
- open_text_file_for_editing,
25
+ edit_textfile,
26
+ interp_path,
24
27
  )
25
28
 
29
+ from .__main__ import main
30
+
26
31
  # Optional: Set __all__ for explicit documentation and cleaner imports
27
32
  __all__ = [
28
33
  'matplotlib_is_available_for_gui_plotting',
29
34
  'matplotlib_is_available_for_headless_image_export',
30
35
  'tkinter_is_available',
31
- 'is_termux',
32
- 'is_freebsd',
33
- 'is_linux',
34
- 'is_android',
35
- 'is_windows',
36
- 'is_apple',
37
- 'is_ish_alpine',
38
- 'pyinstaller',
39
- 'is_frozen',
36
+ 'in_repl',
37
+ 'on_termux',
38
+ 'on_freebsd',
39
+ 'on_linux',
40
+ 'on_android',
41
+ 'on_windows',
42
+ 'on_apple',
43
+ 'on_ish_alpine',
44
+ 'as_pyinstaller',
45
+ 'as_frozen',
40
46
  'is_elf',
41
47
  'is_pyz',
42
48
  'is_windows_portable_executable',
43
49
  'is_macos_executable',
44
50
  'is_pipx',
51
+ 'is_python_script',
45
52
  'interactive_terminal_is_available',
46
53
  'web_browser_is_available',
47
- 'open_text_file_for_editing',
48
- ]
54
+ 'edit_textfile',
55
+ 'interp_path',
56
+ 'main',
57
+ ]
@@ -0,0 +1,69 @@
1
+ from .environment import (
2
+ in_repl,
3
+ on_termux,
4
+ on_windows,
5
+ on_apple,
6
+ on_linux,
7
+ on_ish_alpine,
8
+ on_android,
9
+ on_freebsd,
10
+ is_elf,
11
+ is_windows_portable_executable,
12
+ is_macos_executable,
13
+ is_pyz,
14
+ is_pipx,
15
+ is_python_script,
16
+ as_frozen,
17
+ as_pyinstaller,
18
+ interp_path,
19
+ tkinter_is_available,
20
+ matplotlib_is_available_for_gui_plotting,
21
+ matplotlib_is_available_for_headless_image_export,
22
+ web_browser_is_available,
23
+ interactive_terminal_is_available
24
+ )
25
+
26
+ def main():
27
+ print("PyHabitat Environment Report")
28
+ print("===========================")
29
+ print("\nInterpreter Checks // Based on sys.executable()")
30
+ print("-----------------------------")
31
+ print(f"interp_path(): {interp_path()}")
32
+ print(f"is_elf(interp_path()): {is_elf(interp_path())}")
33
+ print(f"is_windows_portable_executable(interp_path()): {is_windows_portable_executable(interp_path())}")
34
+ print(f"is_macos_executable(interp_path()): {is_macos_executable(interp_path())}")
35
+ print(f"is_pyz(interp_path()): {is_pyz(interp_path())}")
36
+ print(f"is_pipx(interp_path()): {is_pipx(interp_path())}")
37
+ print(f"is_python_script(interp_path()): {is_python_script(interp_path())}")
38
+ print("\nCurrent Environment Check // Based on sys.argv[0]")
39
+ print("-----------------------------")
40
+ print(f"is_elf(): {is_elf()}")
41
+ print(f"is_windows_portable_executable(): {is_windows_portable_executable()}")
42
+ print(f"is_macos_executable(): {is_macos_executable()}")
43
+ print(f"is_pyz(): {is_pyz()}")
44
+ print(f"is_pipx(): {is_pipx()}")
45
+ print(f"is_python_script(): {is_python_script()}")
46
+ print(f"\nCurrent Build Checks // Based on hasattr(sys,..) and getattr(sys,..)")
47
+ print("------------------------------")
48
+ print(f"in_repl(): {in_repl()}")
49
+ print(f"as_frozen(): {as_frozen()}")
50
+ print(f"as_pyinstaller(): {as_pyinstaller()}")
51
+ print("\nOperating System Checks // Based on platform.system()")
52
+ print("------------------------------")
53
+ print(f"on_termux(): {on_termux()}")
54
+ print(f"on_windows(): {on_windows()}")
55
+ print(f"on_apple(): {on_apple()}")
56
+ print(f"on_linux(): {on_linux()}")
57
+ print(f"on_ish_alpine(): {on_ish_alpine()}")
58
+ print(f"on_android(): {on_android()}")
59
+ print(f"on_freebsd(): {on_freebsd()}")
60
+ print("\nCapability Checks")
61
+ print("-------------------------")
62
+ print(f"tkinter_is_available(): {tkinter_is_available()}")
63
+ print(f"matplotlib_is_available_for_gui_plotting(): {matplotlib_is_available_for_gui_plotting()}")
64
+ print(f"matplotlib_is_available_for_headless_image_export(): {matplotlib_is_available_for_headless_image_export()}")
65
+ print(f"web_browser_is_available(): {web_browser_is_available()}")
66
+ print(f"interactive_terminal_is_available(): {interactive_terminal_is_available()}")
67
+
68
+ if __name__ == "__main__":
69
+ main()
@@ -14,6 +14,31 @@ import subprocess
14
14
  import io
15
15
  import zipfile
16
16
 
17
+ __all__ = [
18
+ 'matplotlib_is_available_for_gui_plotting',
19
+ 'matplotlib_is_available_for_headless_image_export',
20
+ 'tkinter_is_available',
21
+ 'on_termux',
22
+ 'on_freebsd',
23
+ 'on_linux',
24
+ 'on_android',
25
+ 'on_windows',
26
+ 'on_apple',
27
+ 'on_ish_alpine',
28
+ 'as_pyinstaller',
29
+ 'as_frozen',
30
+ 'is_elf',
31
+ 'is_pyz',
32
+ 'is_windows_portable_executable',
33
+ 'is_macos_executable',
34
+ 'is_pipx',
35
+ 'interactive_terminal_is_available',
36
+ 'web_browser_is_available',
37
+ 'edit_textfile',
38
+ 'in_repl',
39
+ 'interp_path',
40
+ ]
41
+
17
42
  # Global cache for tkinter and matplotlib (mpl) availability
18
43
  _TKINTER_AVAILABILITY: bool | None = None
19
44
  _MATPLOTLIB_EXPORT_AVAILABILITY: bool | None = None
@@ -29,7 +54,7 @@ def matplotlib_is_available_for_gui_plotting(termux_has_gui=False):
29
54
 
30
55
  # 1. Termux exclusion check (assume no X11/GUI)
31
56
  # Exclude Termux UNLESS the user explicitly provides termux_has_gui=True.
32
- if is_termux() and not termux_has_gui:
57
+ if on_termux() and not termux_has_gui:
33
58
  _MATPLOTLIB_WINDOWED_AVAILABILITY = False
34
59
  return False
35
60
 
@@ -118,7 +143,7 @@ def tkinter_is_available() -> bool:
118
143
  return False
119
144
 
120
145
  # --- ENVIRONMENT AND OPERATING SYSTEM CHECKS ---
121
- def is_termux() -> bool:
146
+ def on_termux() -> bool:
122
147
  """Detect if running in Termux environment on Android, based on Termux-specific environmental variables."""
123
148
 
124
149
  if platform.system() != 'Linux':
@@ -145,22 +170,22 @@ def is_termux() -> bool:
145
170
 
146
171
  return False
147
172
 
148
- def is_freebsd() -> bool:
173
+ def on_freebsd() -> bool:
149
174
  """Detect if running on FreeBSD."""
150
175
  return platform.system() == 'FreeBSD'
151
176
 
152
- def is_linux():
177
+ def on_linux():
153
178
  """Detect if running on Linux."""
154
179
  return platform.system() == 'Linux'
155
180
 
156
- def is_android() -> bool:
181
+ def on_android() -> bool:
157
182
  """
158
183
  Detect if running on Android.
159
184
 
160
- Note: The is_termux() function is more robust and safe for Termux.
161
- Checking for Termux with is_termux() does not require checking for Android with is_android().
185
+ Note: The on_termux() function is more robust and safe for Termux.
186
+ Checking for Termux with on_termux() does not require checking for Android with on_android().
162
187
 
163
- is_android() will be True on:
188
+ on_android() will be True on:
164
189
  - Sandboxed IDE's:
165
190
  - Pydroid3
166
191
  - QPython
@@ -170,7 +195,7 @@ def is_android() -> bool:
170
195
  - UserLand
171
196
  - AnLinux
172
197
 
173
- is_android() will be False on:
198
+ on_android() will be False on:
174
199
  - Full Virtual Machines:
175
200
  - VirtualBox
176
201
  - VMware
@@ -181,15 +206,15 @@ def is_android() -> bool:
181
206
  return False
182
207
  return "android" in platform.platform().lower()
183
208
 
184
- def is_windows() -> bool:
209
+ def on_windows() -> bool:
185
210
  """Detect if running on Windows."""
186
211
  return platform.system() == 'Windows'
187
212
 
188
- def is_apple() -> bool:
213
+ def on_apple() -> bool:
189
214
  """Detect if running on Apple."""
190
215
  return platform.system() == 'Darwin'
191
216
 
192
- def is_ish_alpine() -> bool:
217
+ def on_ish_alpine() -> bool:
193
218
  """Detect if running in iSH Alpine environment on iOS."""
194
219
  # platform.system() usually returns 'Linux' in iSH
195
220
 
@@ -208,16 +233,29 @@ def is_ish_alpine() -> bool:
208
233
 
209
234
  return False
210
235
 
236
+ def in_repl() -> bool:
237
+ """
238
+ Detects if the code is running in the Python interactive REPL (e.g., when 'python' is typed in a console).
239
+
240
+ This function specifically checks for the Python REPL by verifying the presence of the interactive
241
+ prompt (`sys.ps1`). It returns False for other interactive terminal scenarios, such as running a
242
+ PyInstaller binary in a console.
243
+
244
+ Returns:
245
+ bool: True if running in the Python REPL; False otherwise.
246
+ """
247
+ return hasattr(sys, 'ps1')
248
+
211
249
 
212
250
  # --- BUILD AND EXECUTABLE CHECKS ---
213
251
 
214
- def pyinstaller():
252
+ def as_pyinstaller():
215
253
  """Detects if the Python script is running as a 'frozen' in the course of generating a PyInstaller binary executable."""
216
254
  # If the app is frozen AND has the PyInstaller-specific temporary folder path
217
- return is_frozen() and hasattr(sys, '_MEIPASS')
255
+ return as_frozen() and hasattr(sys, '_MEIPASS')
218
256
 
219
257
  # The standard way to check for a frozen state:
220
- def is_frozen():
258
+ def as_frozen():
221
259
  """
222
260
  Detects if the Python script is running as a 'frozen' (standalone)
223
261
  executable created by a tool like PyInstaller, cx_Freeze, or Nuitka.
@@ -235,21 +273,26 @@ def is_frozen():
235
273
  """
236
274
  return getattr(sys, 'frozen', False)
237
275
 
238
- def is_elf(exec_path : Path = None, debug=False) -> bool:
276
+ # --- Binary Characteristic Checks ---
277
+ def is_elf(exec_path: Path | str | None = None, debug: bool = False) -> bool:
239
278
  """Checks if the currently running executable (sys.argv[0]) is a standalone PyInstaller-built ELF binary."""
240
279
  # If it's a pipx installation, it is not the monolithic binary we are concerned with here.
241
280
 
242
281
  if exec_path is None:
243
282
  exec_path = Path(sys.argv[0]).resolve()
283
+ else:
284
+ exec_path = Path(exec_path).resolve()
285
+
244
286
  if debug:
245
- print(f"exec_path = {exec_path}")
287
+ print(f"DEBUG: Checking executable path: {exec_path}")
288
+
246
289
  if is_pipx():
247
290
  return False
248
291
 
249
292
  # Check if the file exists and is readable
250
293
  if not exec_path.is_file():
251
- return False
252
-
294
+ if debug: print("DEBUG:False (Not a file)")
295
+ return False
253
296
  try:
254
297
  # Check the magic number: The first four bytes of an ELF file are 0x7f, 'E', 'L', 'F' (b'\x7fELF').
255
298
  # This is the most reliable way to determine if the executable is a native binary wrapper (like PyInstaller's).
@@ -261,13 +304,20 @@ def is_elf(exec_path : Path = None, debug=False) -> bool:
261
304
  # Handle exceptions like PermissionError, IsADirectoryError, etc.
262
305
  return False
263
306
 
264
- def is_pyz(exec_path: Path=None, debug=False) -> bool:
307
+ def is_pyz(exec_path: Path | str | None = None, debug: bool = False) -> bool:
265
308
  """Checks if the currently running executable (sys.argv[0]) is a PYZ zipapp ."""
266
309
  # If it's a pipx installation, it is not the monolithic binary we are concerned with here.
267
310
  if exec_path is None:
268
311
  exec_path = Path(sys.argv[0]).resolve()
312
+ else:
313
+ exec_path = Path(exec_path).resolve()
314
+
269
315
  if debug:
270
- print(f"exec_path = {exec_path}")
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
271
321
 
272
322
  if is_pipx():
273
323
  return False
@@ -276,10 +326,10 @@ def is_pyz(exec_path: Path=None, debug=False) -> bool:
276
326
  if not str(exec_path).endswith(".pyz"):
277
327
  return False
278
328
 
279
- if not _check_if_zip():
329
+ if not _check_if_zip(exec_path):
280
330
  return False
281
331
 
282
- def is_windows_portable_executable(exec_path: Path = None, debug=False) -> bool:
332
+ def is_windows_portable_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
283
333
  """
284
334
  Checks if the currently running executable (sys.argv[0]) is a
285
335
  Windows Portable Executable (PE) binary, and explicitly excludes
@@ -290,18 +340,20 @@ def is_windows_portable_executable(exec_path: Path = None, debug=False) -> bool:
290
340
  # 1. Determine execution path
291
341
  if exec_path is None:
292
342
  exec_path = Path(sys.argv[0]).resolve()
343
+ else:
344
+ exec_path = Path(exec_path).resolve()
293
345
 
294
346
  if debug:
295
347
  print(f"DEBUG: Checking executable path: {exec_path}")
296
348
 
297
349
  # 2. Exclude pipx environments immediately
298
350
  if is_pipx():
299
- if debug: print("DEBUG: is_exe_non_pipx: False (is_pipx is True)")
351
+ if debug: print("DEBUG: False (is_pipx is True)")
300
352
  return False
301
353
 
302
354
  # 3. Perform file checks
303
355
  if not exec_path.is_file():
304
- if debug: print("DEBUG: is_exe_non_pipx: False (Not a file)")
356
+ if debug: print("DEBUG:False (Not a file)")
305
357
  return False
306
358
 
307
359
  try:
@@ -314,30 +366,35 @@ def is_windows_portable_executable(exec_path: Path = None, debug=False) -> bool:
314
366
 
315
367
  if debug:
316
368
  print(f"DEBUG: Magic bytes: {magic_bytes}")
317
- print(f"DEBUG: is_exe_non_pipx: {is_pe} (Non-pipx check)")
369
+ print(f"DEBUG: {is_pe} (Non-pipx check)")
318
370
 
319
371
  return is_pe
320
372
 
321
373
  except Exception as e:
322
- if debug: print(f"DEBUG: is_exe_non_pipx: Error during file check: {e}")
374
+ if debug: print(f"DEBUG: Error during file check: {e}")
323
375
  # Handle exceptions like PermissionError, IsADirectoryError, etc.
324
376
  return False
325
377
 
326
- def is_macos_executable(exec_path: Path = None, debug=False) -> bool:
378
+ def is_macos_executable(exec_path: Path | str | None = None, debug: bool = False) -> bool:
327
379
  """
328
380
  Checks if the currently running executable is a macOS/Darwin Mach-O binary,
329
381
  and explicitly excludes pipx-managed environments.
330
382
  """
331
383
  if exec_path is None:
332
384
  exec_path = Path(sys.argv[0]).resolve()
333
-
385
+ else:
386
+ exec_path = Path(exec_path).resolve()
387
+ if debug:
388
+ print(f"DEBUG: Checking executable path: {exec_path}")
389
+
334
390
  if is_pipx():
335
391
  if debug: print("DEBUG: is_macos_executable: False (is_pipx is True)")
336
392
  return False
337
393
 
338
394
  if not exec_path.is_file():
395
+ if debug: print("DEBUG:False (Not a file)")
339
396
  return False
340
-
397
+
341
398
  try:
342
399
  # Check the magic number: Mach-O binaries start with specific 4-byte headers.
343
400
  # Common ones are: b'\xfe\xed\xfa\xce' (32-bit) or b'\xfe\xed\xfa\xcf' (64-bit)
@@ -362,15 +419,23 @@ def is_macos_executable(exec_path: Path = None, debug=False) -> bool:
362
419
  except Exception:
363
420
  return False
364
421
 
365
- def is_pipx(debug=False) -> bool:
422
+ def is_pipx(exec_path: Path | str | None = None, debug: bool = False) -> bool:
366
423
  """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)")
433
+ return False
367
434
  try:
368
435
  # Helper for case-insensitivity on Windows
369
436
  def normalize_path(p: Path) -> str:
370
437
  return str(p).lower()
371
438
 
372
- exec_path = Path(sys.argv[0]).resolve()
373
-
374
439
  # This is the path to the interpreter running the script (e.g., venv/bin/python)
375
440
  # In a pipx-managed execution, this is the venv python.
376
441
  interpreter_path = Path(sys.executable).resolve()
@@ -413,10 +478,54 @@ def is_pipx(debug=False) -> bool:
413
478
  except Exception:
414
479
  # Fallback for unexpected path errors
415
480
  return False
416
-
417
481
 
482
+ def is_python_script(path: Path | str | None = None, debug: bool = False) -> bool:
483
+ """
484
+ Checks if the specified path or running script is a Python source file (.py).
485
+
486
+ By default, checks the running script (`sys.argv[0]`). If a specific `path` is
487
+ provided, checks that path instead. Uses `Path.resolve()` for stable path handling.
488
+
489
+ Args:
490
+ path: Optional; path to the file to check (str or Path). If None, defaults to `sys.argv[0]`.
491
+ debug: If True, prints the path being checked.
492
+
493
+ Returns:
494
+ bool: True if the specified or default path is a Python source file (.py); False otherwise.
495
+ """
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():
503
+ return False
504
+ return exec_path.suffix.lower() == '.py'
505
+
506
+ # --- Interpreter Check ---
507
+
508
+ def interp_path(print_path: bool = False) -> str:
509
+ """
510
+ Returns the path to the Python interpreter binary and optionally prints it.
511
+
512
+ This function wraps `sys.executable` to provide the path to the interpreter
513
+ (e.g., '/data/data/com.termux/files/usr/bin/python3' in Termux or the embedded
514
+ interpreter in a frozen executable). If the path is empty (e.g., in some embedded
515
+ or sandboxed environments), an empty string is returned.
516
+
517
+ Args:
518
+ print_path: If True, prints the interpreter path to stdout.
519
+
520
+ Returns:
521
+ str: The path to the Python interpreter binary, or an empty string if unavailable.
522
+ """
523
+ path = sys.executable
524
+ if print_path:
525
+ print(f"Python interpreter path: {path}")
526
+ return path
418
527
 
419
- # --- TTY CHECK ---
528
+ # --- TTY Check ---
420
529
  def interactive_terminal_is_available():
421
530
  """
422
531
  Check if the script is running in an interactive terminal.
@@ -440,7 +549,7 @@ def web_browser_is_available() -> bool:
440
549
  except webbrowser.Error:
441
550
  # Fallback needed. Check for external launchers.
442
551
  # 2. Termux specific check
443
- if is_termux() and shutil.which("termux-open-url"):
552
+ if on_termux() and shutil.which("termux-open-url"):
444
553
  return True
445
554
  # 3. General Linux check
446
555
  if shutil.which("xdg-open"):
@@ -448,47 +557,61 @@ def web_browser_is_available() -> bool:
448
557
  return False
449
558
 
450
559
  # --- LAUNCH MECHANISMS BASED ON ENVIRONMENT ---
451
- def open_text_file_for_editing(filepath):
560
+ def edit_textfile(path: Path | str | None = None) -> None:
561
+ #def open_text_file_for_editing(path): # defunct function name as of 1.0.16
452
562
  """
453
563
  Opens a file with the environment's default application (Windows, Linux, macOS)
454
564
  or a guaranteed console editor (nano) in constrained environments (Termux, iSH)
455
565
  after ensuring line-ending compatibility.
566
+
567
+ This function is known to fail on PyDroid3, where on_linus() is True but xdg-open
568
+ is not available.
456
569
  """
457
- if is_windows():
458
- os.startfile(filepath)
459
- elif is_termux():
460
- # Install dependencies if missing (Termux pkg returns non-zero if already installed, so no check=True)
461
- subprocess.run(['pkg','install', 'dos2unix', 'nano'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
462
- _run_dos2unix(filepath)
463
- subprocess.run(['nano', filepath])
464
- elif is_ish_alpine():
465
- # Install dependencies if missing (apk returns 0 if already installed, so check=True is safe)
466
- subprocess.run(['apk','add', 'dos2unix'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
467
- subprocess.run(['apk','add', 'nano'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
468
- _run_dos2unix(filepath)
469
- subprocess.run(['nano', filepath])
470
- # --- Standard Unix-like Systems (Conversion + Default App) ---
471
- elif is_linux():
472
- _run_dos2unix(filepath) # Safety conversion for user-defined console apps
473
- subprocess.run(['xdg-open', filepath])
474
-
475
- elif is_apple():
476
- _run_dos2unix(filepath) # Safety conversion for user-defined console apps
477
- subprocess.run(['open', filepath])
478
- else:
479
- print("Unsupported operating system.")
570
+ if path is None:
571
+ return
480
572
 
573
+ path = Path(path).resolve()
574
+
575
+ try:
576
+ if on_windows():
577
+ os.startfile(path)
578
+ elif on_termux():
579
+ # Install dependencies if missing (Termux pkg returns non-zero if already installed, so no check=True)
580
+ subprocess.run(['pkg','install', 'dos2unix', 'nano'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
581
+ _run_dos2unix(path)
582
+ subprocess.run(['nano', path])
583
+ elif on_ish_alpine():
584
+ # Install dependencies if missing (apk returns 0 if already installed, so check=True is safe)
585
+ subprocess.run(['apk','add', 'dos2unix'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
586
+ subprocess.run(['apk','add', 'nano'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
587
+ _run_dos2unix(path)
588
+ subprocess.run(['nano', path])
589
+ # --- Standard Unix-like Systems (Conversion + Default App) ---
590
+ elif on_linux():
591
+ _run_dos2unix(path) # Safety conversion for user-defined console apps
592
+ subprocess.run(['xdg-open', path])
593
+ elif on_apple():
594
+ _run_dos2unix(path) # Safety conversion for user-defined console apps
595
+ subprocess.run(['open', path])
596
+ else:
597
+ print("Unsupported operating system.")
598
+ except Exception as e:
599
+ print("The file could not be opened for editing in the current environment. Known failure points: Pydroid3")
600
+
481
601
  """Why Not Use check=True on Termux:
482
602
  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.
483
603
  """
484
604
 
485
- def _run_dos2unix(filepath):
605
+ def _run_dos2unix(path: Path | str | None = None):
486
606
  """Attempt to run dos2unix, failing silently if not installed."""
607
+
608
+ path = Path(path).resolve()
609
+
487
610
  try:
488
611
  # We rely on shutil.which not being needed, as this is a robust built-in utility on most targets
489
612
  # The command won't raise an exception unless the process itself fails, not just if the utility isn't found.
490
613
  # We also don't use check=True here to allow silent failure if the utility is missing (e.g., minimalist Linux).
491
- subprocess.run(['dos2unix', filepath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
614
+ subprocess.run(['dos2unix', path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
492
615
  except FileNotFoundError:
493
616
  # This will be raised if 'dos2unix' is not on the system PATH
494
617
  pass
@@ -523,11 +646,15 @@ def _get_pipx_paths():
523
646
  return pipx_bin_path, pipx_venv_base.resolve()
524
647
 
525
648
 
526
- def _check_if_zip(file_path: str | Path) -> bool:
649
+ def _check_if_zip(path: Path | str | None) -> bool:
527
650
  """Checks if the file at the given path is a valid ZIP archive."""
651
+ if path is None:
652
+ return False
653
+ path = Path(path).resolve()
654
+
528
655
  try:
529
- return zipfile.is_zipfile(file_path)
656
+ return zipfile.is_zipfile(path)
530
657
  except Exception:
531
658
  # Handle cases where the path might be invalid, or other unexpected errors
532
659
  return False
533
-
660
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhabitat
3
- Version: 1.0.15
3
+ Version: 1.0.17
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
@@ -59,11 +59,11 @@ Ultimately, [City-of-Memphis-Wastewater](https://github.com/City-of-Memphis-Wast
59
59
 
60
60
  * **Definitive Environment Checks:** Rigorous checks catered to Termux and iSH (iOS Alpine). Accurate, typical modern detection for Windows, macOS (Apple), Linux, FreeBSD, Android.
61
61
  * **GUI Availability:** Rigorous, cached checks to determine if the environment supports a graphical popup window (Tkinter/Matplotlib TkAgg) or just headless image export (Matplotlib Agg).
62
- * **Build/Packaging Detection:** Reliable detection of standalone executables built by tools like PyInstaller, and, crucially, correct identification and exclusion of pipx-managed virtual environments, which also user binaries that could conflate the check.
63
- * **Executable Type Inspection:** Uses file magic numbers (ELF and MZ) to confirm if the running script is a monolithic, frozen binary (non-pipx).
62
+ * **Build/Packaging Detection:** Reliable detection of standalone executables (PyInstaller), Python zipapps (.pyz), Python source scripts (.py), and correct identification/exclusion of pipx-managed virtual environments.
63
+ * **Executable Type Inspection:** Uses file magic numbers (ELF, MZ, Mach-O) to confirm if the running script is a monolithic, frozen binary (non-pipx) or zipapp (.pyz).
64
64
 
65
65
  </details>
66
-
66
+
67
67
  ---
68
68
 
69
69
  <details>
@@ -75,24 +75,31 @@ Key question: "What is this running on?"
75
75
 
76
76
  | Function | Description |
77
77
  | :--- | :--- |
78
- | `is_windows()` | Returns `True` on Windows. |
79
- | `is_apple()` | Returns `True` on macOS (Darwin). |
80
- | `is_linux()` | Returns `True` on Linux in general. |
81
- | `is_termux()` | Returns `True` if running in the Termux Android environment. |
82
- | `is_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
83
- | `is_android()` | Returns `True` on any Android-based Linux environment. |
78
+ | `on_windows()` | Returns `True` on Windows. |
79
+ | `on_apple()` | Returns `True` on macOS (Darwin). |
80
+ | `on_linux()` | Returns `True` on Linux in general. |
81
+ | `on_termux()` | Returns `True` if running in the Termux Android environment. |
82
+ | `on_freebsd()` | Returns `True` on FreeBSD. |
83
+ | `on_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
84
+ | `on_android()` | Returns `True` on any Android-based Linux environment. |
85
+ | `in_repl()` | Returns `True` is the user is currently in a Python REPL; hasattr(sys,'ps1'). |
84
86
 
85
87
  ### Packaging and Build Checking
86
88
 
87
- Key question: "What is the character of my executable?"
89
+ Key question: "What is the character of my executable or my build state?"
90
+
91
+ These functions accept an optional path argument (Path or str), defaulting to sys.argv[0] (e.g., pyhabitat/__main__.py for python -m pyhabitat, empty in REPL). Path.resolve() is used for stability.
88
92
 
89
93
  | Function | Description |
90
94
  | :--- | :--- |
91
- | `is_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
92
- | `is_pipx()` | Returns `True` if running from a pipx managed virtual environment. |
93
- | `is_elf()` | Checks if the executable is an ELF binary (Linux standalone executable), excluding pipx. |
94
- | `is_windows_portable_executable()` | Checks if the executable is a Windows PE binary (MZ header), excluding pipx. |
95
- | `is_macos_executable()` | Checks if the executable is a macOS/Darwin Mach-O binary, excluding pipx. |
95
+ | `as_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
96
+ | `as_pyinstaller()` | Returns `True` if the script is frozen and generated by PyInstaller (has `_MEIPASS`). |
97
+ | `is_python_script(path=None)` | Returns `True` if the script or specified path is a Python source file (.py). |
98
+ | `is_pipx(path=None)` | Returns `True` if the script or specified path is from a pipx-managed virtual environment. |
99
+ | `is_elf(path=None)` | Returns `True` if the script or specified path is an ELF binary (Linux standalone executable, non-pipx). |
100
+ | `is_pyz(path=None)` | Returns `True` if the script or specified path is a Python zipapp (.pyz, non-pipx). |
101
+ | `is_windows_portable_executable(path=None)` | Returns `True` if the script or specified path is a Windows PE binary (MZ header, non-pipx). |
102
+ | `is_macos_executable(path=None)` | Returns `True` if the script or specified path is a macOS Mach-O binary (non-pipx). |
96
103
 
97
104
  ### Capability Checking
98
105
 
@@ -101,16 +108,18 @@ Key Question: "What could I do next?"
101
108
  | Function | Description |
102
109
  | :--- | :--- |
103
110
  | `tkinter_is_available()` | Checks if Tkinter is imported and can successfully create a window. |
104
- | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. |
111
+ | `matplotlib_is_available_for_gui_plotting(termux_has_gui=False)` | Checks for Matplotlib and its TkAgg backend, required for interactive plotting. Set `termux_has_gui=True` for Termux with GUI support; defaults to `False`. |
105
112
  | `matplotlib_is_available_for_headless_image_export()` | Checks for Matplotlib and its Agg backend, required for saving images without a GUI. |
106
113
  | `interactive_terminal_is_available()` | Checks if standard input and output streams are connected to a TTY (allows safe use of interactive prompts). |
107
114
  | `web_browser_is_available()` | Check if a web browser can be launched in the current environment (allows safe use of web-based prompts and localhost plotting). |
108
115
 
109
- ### Actions
116
+ ### Utility
110
117
 
111
118
  | Function | Description |
112
119
  | :--- | :--- |
113
- | `open_text_file_for_editing()` | Smoothly opens a text file for editing (for configuration editing prompted by a CLI flag). |
120
+ | `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(print_path=False)` | Returns the path to the Python interpreter binary (sys.executable). Optionally prints the path. Returns empty string if unavailable. |
122
+ | `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. |
114
123
 
115
124
  </details>
116
125
 
@@ -121,39 +130,57 @@ Key Question: "What could I do next?"
121
130
 
122
131
  The module exposes all detection functions directly for easy access.
123
132
 
124
- ### 0\. Current Use
133
+ ### 0\.Example of PyHabitat in Action
125
134
 
126
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.
127
136
 
128
- ### 1\. Checking Environment and Build Type
137
+ ### 1\. Running the Environment Report
138
+
139
+ Run a comprehensive environment report from the command line or REPL to inspect the interpreter (sys.executable), running script (sys.argv[0]), build state, operating system, and capabilities.
140
+
141
+ ```bash
142
+ # In the terminal
143
+ python -m pyhabitat
144
+ ```
145
+
146
+ ```python
147
+ # In the Python REPL
148
+ import pyhabitat as ph
149
+ ph.main()
150
+ ```
151
+
152
+ ### 2\. Checking Environment and Build Type
129
153
 
130
154
  ```python
131
- from pyhabitat import is_termux, is_windows, is_pipx, is_frozen
155
+ from pyhabitat import on_termux, on_windows, is_pipx, is_python_script, as_frozen
132
156
 
133
157
  if is_pipx():
134
158
  print("Running inside a pipx virtual environment. This is not a standalone binary.")
135
159
 
136
- elif is_frozen():
160
+ if as_frozen():
137
161
  print("Running as a frozen executable (PyInstaller, cx_Freeze, etc.).")
138
162
 
139
- elif is_termux():
163
+ if is_python_script():
164
+ print("Running as a Python source script (.py).")
165
+
166
+ if on_termux():
140
167
  # Expected cases:
141
168
  #- pkg install python-numpy python-cryptography
142
- #- Avoiding matplotlib unless the user explicitly confirms that termux_has_gui=False in matplotlib_is_available_for_gui_plotting(termux_has_gui=False).
169
+ #- Avoiding matplotlib unless the user explicitly sets termux_has_gui=True in matplotlib_is_available_for_gui_plotting().
143
170
  #- Auto-selection of 'termux-open-url' and 'xdg-open' in logic.
144
171
  #- Installation on the system, like orchestrating the construction of Termux Widget entries in ~/.shortcuts.
145
172
  print("Running in the Termux environment on Android.")
146
173
 
147
- elif is_windows():
174
+ if on_windows():
148
175
  print("Running on Windows.")
149
176
  ```
150
177
 
151
- ### 2\. Checking GUI and Plotting Availability
178
+ ### 3\. Checking GUI and Plotting Availability
152
179
 
153
180
  Use these functions to determine if you can show an interactive plot or if you must save an image file.
154
181
 
155
182
  ```python
156
- from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export,
183
+ from pyhabitat import matplotlib_is_available_for_gui_plotting, matplotlib_is_available_for_headless_image_export
157
184
 
158
185
  if matplotlib_is_available_for_gui_plotting():
159
186
  # We can safely call plt.show()
@@ -171,13 +198,16 @@ else:
171
198
  print("Matplotlib is not installed or the environment is too restrictive for plotting.")
172
199
  ```
173
200
 
174
- ### 3\. Text Editing
201
+ ### 4\. Text Editing
175
202
 
176
- Use this function to smoothly open a text file for editing.
203
+ Use this function to open a text file for editing.
177
204
  Ideal use case: Edit a configuration file, if prompted by a CLI command like 'config --textedit'.
178
205
 
179
206
  ```python
180
- open_text_file_for_editing(filepath=Path('./config.json'))
207
+ from pathlib import Path
208
+ import pyhabitat as ph
209
+
210
+ ph.edit_textfile(path=Path('./config.json'))
181
211
  ```
182
212
  </details>
183
213
 
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  pyhabitat/__init__.py
5
+ pyhabitat/__main__.py
5
6
  pyhabitat/environment.py
6
7
  pyhabitat.egg-info/PKG-INFO
7
8
  pyhabitat.egg-info/SOURCES.txt
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyhabitat"
9
- version = "1.0.15"
9
+ version = "1.0.17"
10
10
  #dynamic = ["version"] #
11
11
  authors = [
12
12
  { name="George Clayton Bennett", email="george.bennett@memphistn.gov" },
@@ -28,4 +28,4 @@ include = ["pyhabitat"] # Only include the main package
28
28
 
29
29
  [tool.setuptools_scm]
30
30
  # This tells setuptools_scm to look at your git history/tags for the version
31
- write_to = "pyhabitat/_version.py"
31
+ write_to = "pyhabitat/_version.py"
File without changes
File without changes