# pyhabitat 🧭

## An Introspection Library for Python Environments and Builds

**`pyhabitat`** is a **lightweight library for Python build and environment introspection**. It accurately and securely determines the execution context of a running script by providing definitive checks for:

* **OS and Environments:** Operating Systems and common container/emulation environments (e.g., Termux, iSH).
* **Build States:** Application build systems (e.g., PyInstaller, pipx).
* **GUI Backends:** Availability of graphical toolkits (e.g., Matplotlib, Tkinter).

Stop writing verbose `sys.platform` and environment variable checks. Use **`pyhabitat`** to implement clean, **architectural logic** based on the execution habitat.

---

Read the code on [github](https://github.com/City-of-Memphis-Wastewater/pyhabitat/blob/main/pyhabitat/environment.py). 🌐

<p align="center">
  <img src="https://raw.githubusercontent.com/City-of-Memphis-Wastewater/pyhabitat/main/assets/pyhabitat-ico-alpha.png" width="256px">
</p>
<!--p align="center">
  <img src="https://raw.githubusercontent.com/City-of-Memphis-Wastewater/pyhabitat/main/assets/pyhabitat-ico_256x256.ico" width="200" alt="ICO Version" />
</p-->

---

## 📦 Installation

```bash
pip install pyhabitat
```

---

<details>
<summary> 🧠 Motivation </summary>

This library is especially useful for **leveraging Python in mobile environments** (`Termux` on Android and `iSH` on iOS), which often have particular limitations and require special handling. For example, projects use pyhabitat in their logic to trigger **localhost plotting** when a GUI is not available. You can set different behaviors for pyhabitat.on_wsl(), pyhabitat.on_termux(), pyhabitat.on_windows(), and pyhabitat.on_linux(). 

Our team is fundamentally driven by enabling mobile computing for true utility applications.
We liked it when our CLI's and our servers run on every device in the drawer.

This project has a `pipx` installable **CLI**

Ultimately, [City-of-Memphis-Wastewater](https://github.com/City-of-Memphis-Wastewater) aims to produce **reference-quality code** for the documented proper approach. We recognize that many people (and bots) are searching for ideal solutions, and our functions are built upon extensive research and testing to go **beyond simple `platform.system()` checks**.

</details>

---

<details>
<summary> 🚀 Features </summary>

  * **Definitive Environment Checks:** Rigorous checks catered to Termux and iSH (iOS Alpine). Accurate, typical modern detection for Windows, macOS (Apple), Linux, FreeBSD, Android.
  * **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).
  * **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.
  * **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).

</details>

---

<details>
<summary> 📚 Function Reference </summary>

### OS and Environment Checking

Key question: "What is this running on?"

| Function | Description |
| :--- | :--- |
| `on_windows()` | Returns `True` on Windows. |
| `on_macos()` | Returns `True` on macOS (Darwin). |
| `on_linux()` | Returns `True` on Linux in general. |
| `on_wsl()` | Returns `True` if running inside Windows Subsystem for Linux (WSL or WSL2). |
| `on_termux()` | Returns `True` if running in the Termux Android environment. |
| `on_freebsd()` | Returns `True` on FreeBSD. |
| `on_ish_alpine()` | Returns `True` if running in the iSH Alpine Linux iOS emulator. |
| `on_android()` | Returns `True` on any Android-based Linux environment. |
| `on_pydroid()` | Returns `True` Return True if running under the Pydroid 3 Android app (other versions untested). |
| `in_repl()` | Returns `True` is the user is currently in a Python REPL; hasattr(sys,'ps1'). |

### Packaging and Build Checking

Key question: "What is the character of my executable or my build state?"

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.

| Function | Description |
| :--- | :--- |
| `as_frozen()` | Returns `True` if the script is running as a standalone executable (any bundler). |
| `as_pyinstaller()` | Returns `True` if the script is frozen and generated by PyInstaller (has `_MEIPASS`). |
| `is_python_script(path=None)` | Returns `True` if the script or specified path is a Python source file (.py). |
| `is_pipx(path=None)` | Returns `True` if the script or specified path is from a pipx-managed virtual environment. |
| `is_elf(path=None)` | Returns `True` if the script or specified path is an ELF binary (Linux standalone executable, non-pipx). |
| `is_pyz(path=None)` | Returns `True` if the script or specified path is a Python zipapp (.pyz, non-pipx). |
| `is_windows_portable_executable(path=None)` | Returns `True` if the script or specified path is a Windows PE binary (MZ header, non-pipx). |
| `is_msix()` | Returns `True` if the currently running software or the target path is an MSIX package, like distributed from the Microsoft Store. |
| `is_macos_executable(path=None)` | Returns `True` if the script or specified path is a macOS Mach-O binary (non-pipx). |

### Capability Checking

Key Question: "What could I do next?"

| Function | Description |
| :--- | :--- |
| `tkinter_is_available()` | Checks if Tkinter is imported and can successfully create a window. |
| `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`. |
| `matplotlib_is_available_for_headless_image_export()` | Checks for Matplotlib and its Agg backend, required for saving images without a GUI. |
| `interactive_terminal_is_available()` | Checks if standard input and output streams are connected to a TTY (allows safe use of interactive prompts). |
| `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). 	|

### Utility

| Function | Description |
| :--- | :--- |
| `edit_textfile(path)` | Opens a text file for editing using the default editor (Windows, Linux, macOS) or nano in Termux/iSH. Can be called from REPL mode. Path argument (str or Path) uses Path.resolve() for stability. |
| `show_system_explorer(path)` | Launches the appropriate view of the folder based on system. Defaults to Path.cwd(). |
| `interp_path()` | Returns the path to the Python interpreter binary (sys.executable). Returns empty string if unavailable. |
| `report()` | 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. |

</details>

---

<details>
<summary> 💻 Usage Examples </summary>

The module exposes all detection functions directly for easy access.

### 0\. Example of PyHabitat in Action

The `pyhabitat` library is used extensively in [PDF Link Check](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/pdflinkcheck/blob/main/pyproject.toml).

### 1\. Running the Environment Report

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.

```bash
# In the terminal
python -m pyhabitat
```

```python
# In the Python REPL
import pyhabitat as ph
ph.report()
```

### Text Editing

Use this function to open a text file for editing. 
Ideal use case: Edit a configuration file, if prompted by a CLI command like 'config --textedit'.

```python
from pathlib import Path
import pyhabitat as ph

ph.edit_textfile(path=Path('./config.json'))
```
</details>

---

<details> <summary>🏗️ Build Instructions</summary>

### Build Options

You can build PyHabitat in two ways:

| Output  | Command                      | Notes                                                                 |
|---------|------------------------------|-------------------------|
| PYZ     | `python build_pyz.py`        | Cross-platform zipapp   |
| EXE/ELF | `python build_executable.py` | PyInstaller executable  |


✅ Notes:

.pyz is cross-platform but requires Python on the host system.

</details>

---


🤝 Contributing


Contributions are welcome\! If there is an environment or build system that is not correctly detected, or that you would like to have added, please open an issue or submit a pull request with the relevant detection logic.

## 📄 License

This project is licensed under the MIT License. See the LICENSE file for details.

---

