Skip to content

Commit

Permalink
v.1.1.0
Browse files Browse the repository at this point in the history
- Added ensure() and clean() functions
- More detailed examples and updated README.md
  • Loading branch information
Niko Pasanen committed Apr 11, 2020
1 parent b1484f6 commit da06d50
Show file tree
Hide file tree
Showing 11 changed files with 734 additions and 178 deletions.
173 changes: 47 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,149 +1,70 @@
## pathtub
## 🛁 pathtub 🐍

Simple python functions for reading and editing Windows PATH.
Simple python functions for reading and editing [Windows PATH variables](docs/path_variables.md).




### Features
- Uses Powershell commands under the hood
- `$Env:Path`
- `[Environment]::GetEnvironmentVariable(...)`
- `[Environment]::SetEnvironmentVariable(...)`
- Is not limited by the [1024 character limit](https://superuser.com/questions/387619/overcoming-the-1024-character-limit-with-setx).
&nbsp;&nbsp;&nbsp;&nbsp;**Ensuring** that an folder exists in Path. <br>
&nbsp;&nbsp;&nbsp;&nbsp;🧽 **Cleaning** the PATH (duplicates, removed folders, sorting) <br>
&nbsp;&nbsp;&nbsp;&nbsp;✏️ **Adding** or **removing** folders to/from Path (temporary or permanently) <br>



## Installing
### Option A: Install from PyPi
```
pip install pathtub
```

### Option B: Install from GitHub
- Download this package and run
```
pip install <this_folder_path>
```
where `<this_folder_path>` refers to the folder with the `setup.py`.

## Usage

# Usage
- [Getting path variables](#getting-path-variables)
- [Setting PATH (User) variables](#setting-path-user-variables)
- [Setting PATH (System/Machine) variables](#setting-path-systemmachine-variables)
- [Removing PATH (User) variables](#removing-path-user-variables)
- [Removing PATH (System/Machine) variables](#removing-path-systemmachine-variables)
- [Checking if folder is in PATH](#checking-if-folder-is-in-path)
- [Ensuring folder is in PATH](#%e2%9c%85-ensuring-folder-is-in-path)
- [Cleaning PATH](#%f0%9f%a7%bd-cleaning-path)
- [Rest of the docs](#rest-of-the-docs)

### ✅ Ensuring folder is in PATH
- It is safe to call `ensure()` every time you load your script, for example. It only does something if the dll_folder is not found in your process `PATH`.
- The last "trailing" backslash (if any) is ignored when comparing any two folders.

### Getting path variables
```python
from pathtub import get_path

# Reads $Env:Path
path = get_path()
# Reads [Environment]::GetEnvironmentVariable('Path', 'User')
path_user = get_path("user")
# Reads [Environment]::GetEnvironmentVariable('Path', 'Machine')
path_machine = get_path("machine")
from pathtub import ensure
dll_folder = r'C:\my favourite\dlls'
# 1) Check Process PATH, i.e. os.environ['PATH']
# 2) Add to Process PATH (temporary) if not found
ensure(dll_folder)
```
#### Example output
- You may also make the addition permanent (& visible to other processes).
- Also this is safe to call every time script is starting.
```python
In [1]: print(get_path('user')) # returns a str
C:\Python\Python37\Scripts\;C:\Python\Python37\;C:\Python\Python37-32\Scripts\;C:\Python\Python37-32\;C:\Users\USER\AppData\Roaming\npm;C:\Users\USER\AppData\Local\Microsoft\WindowsApps;C:\Program Files\Microsoft VS Code\bin;C:\Programs;C:\Programs\fciv;C:\texlive\2018\bin\win32;C:\Programs\apache-maven-3.6.2\bin;C:\Program Files\Java\jdk-13.0.1\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Programs\cloc;C:\Users\USER\AppData\Local\Programs\Microsoft VS Code\bin;
```
- **Note**: For some reason, the `$Env:Path` does not always update instantly (without reboot/relogin), even if the `[Environment]::GetEnvironmentVariable('Path', ...)` are updated.

### Setting PATH (User) variables

- User PATH before edits: [Screenshot](img/before-setting-user.png)
- Adding a folder to PATH

```python
In [1]: from pathtub import add_to_path

In [2]: added = add_to_path(r'C:\My new folder\added to user PATH')

In [3]: added
Out[3]: True

# There is protection against adding duplicate entries
In [4]: added = add_to_path(r'C:\My new folder\added to user PATH')

In [5]: added
Out[5]: False
```

- User PATH after edits: [Screenshot](img/after-setting-user.png)



### Setting PATH (System/Machine) variables
- Similar to setting User PATH variables
- Change mode to "machine" and *Run the script with Admin rights*.

from pathtub import ensure
dll_folder = r'C:\my favourite\dlls'
# 1) Check Process PATH
# 2) Add to Process PATH if not found
# 3) Add also to User PATH (permanent), if 2) happens
ensure(dll_folder, permanent=True)
```
from pathtub import add_to_path
added = add_to_path(r'C:\My new folder\added to machine PATH', mode='machine')
- The Process PATH is loaded from parent process or from the permanent (User/System) PATH when process is started. For more info, see: [Windows PATH variables](docs/path_variables.md).
- ["Real life" example using ensure](docs/example_ensure.md).
- Full documentation of `ensure()` is in the source code ([pathtools.py](pathtub/pathtools.py)).
### 🧽 Cleaning PATH
#### Cleaning paths means
1. Removing duplicates from the PATH (trailing backslash neglected)
2. Removing empty entries from PATH
3. Sorting alphabetically (optional, Default: True)
4. Removing folders that do not exist (optional, Default: True)
5. Removing from "User" list the ones that are in the "System" list (optional, default: True)

#### Screenshots of User PATH before and after clean:
![User PATH](img/user-before-after-clean.png)

#### Code example for clean
```
from pathtub import clean
clean()
### Removing PATH (User) variables

```python
In [1]: from pathtub import remove_from_path

In [2]: removed = remove_from_path(r'C:\My new folder\added to user PATH')

In [3]: removed
Out[3]: True

# Can only remove once. Safe to call multiple times.
In [4]: removed = remove_from_path(r'C:\My new folder\added to user PATH')

In [5]: removed
Out[5]: False
```


### Removing PATH (System/Machine) variables
- Similar to removing User PATH variables
- Change mode to "machine" and *Run the script with Admin rights*.

# possible parameters:
# clean(sort=True, remove_non_existent=True, remove_user_duplicates=True)
```
from pathtub import add_to_path
removed = remove_from_path(r'C:\My new folder\added to machine PATH', mode='machine')
```
### Checking if folder is in PATH
- **Note**: You don't have to worry if the saved PATH item ends with a backslash or not; both cases are checked.
```python
# Check the `$Env:Path` (Powershell) / `PATH` (cmd) variable
found = is_in_path() # same as is_in_path("path")

# Checks the `[Environment]::GetEnvironmentVariable('Path','User')`; The "User PATH"
found_user = is_in_path("user")

# Checks the `[Environment]::GetEnvironmentVariable('Path','Machine')`; The "System PATH"
found = is_in_path("machine")
```
#### Example
```python
In [1]: from pathtub import is_in_path

In [2]: found = is_in_path('C:\\Python\\Python37\\', 'user')

In [3]: found
Out[3]: True

# It does not matter if the seach folder or the folder saved
# to PATH has "\" as the last character.
In [4]: found = is_in_path(r'C:\Python\Python37', 'user')

In [5]: found
Out[5]: True

In [6]: found = is_in_path(r'C:\Nonexistent\path', 'user')
- For more detailed example, see [Full example of pathtub.clean](docs/example_clean.md)

In [7]: found
Out[7]: False
```
### Rest of the docs
Did not find what you were looking for? See the [Rest of the docs](docs/rest_of_the_docs.md).
73 changes: 73 additions & 0 deletions docs/example_clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
## Full example of `pathtub.clean`

- Running the code below two times: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Run #1**: ⛔👮 Without admin rights 👉 Only edit **User PATH**<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Run #2**: ✅👮 With admin rights 👉 Also edit **System PATH**

```python
from pathtub import clean
clean()
```

<details><summary>Output from Run #1 (⛔👮 Without admin rights)</summary>
<p>

```
Removing folder C:\Program Files\Sublime Text 3 from User Path (non-existing)
Removing folder C:\Programs\phantomjs-2.1.1-windows\bin from User Path (non-existing)
Removing folder C:\Programs\chromedriver_win32 from User Path (non-existing)
Removing folder C:\Program Files (x86)\Microsoft SQL Server\150\DTS\Binn from User Path (non-existing)
Removing folder C:\Python\Python37-32\Scripts from User Path (non-existing)
Removing folder C:\Python\Python37-32 from User Path (non-existing)
Removing folder C:\Program Files\Microsoft VS Code\bin from User Path (non-existing)
Removing folder C:\Program Files\Sublime Text 3 from System Path (non-existing)
Removing folder C:\Programs\phantomjs-2.1.1-windows\bin from System Path (non-existing)
Removing folder C:\Programs\chromedriver_win32 from System Path (non-existing)
Removing folder C:\Program Files (x86)\Microsoft SQL Server\150\DTS\Binn from System Path (non-existing)
Removing folder C:\Program Files (x86)\Common Files\Oracle\Java\javapath from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common from User Path (Defined in both; User and System PATH)
Removing folder C:\ProgramData\Oracle\Java\javapath from User Path (Defined in both; User and System PATH)
Removing folder C:\WINDOWS\system32 from User Path (Defined in both; User and System PATH)
Removing folder C:\WINDOWS from User Path (Defined in both; User and System PATH)
Removing folder C:\WINDOWS\System32\Wbem from User Path (Defined in both; User and System PATH)
Removing folder C:\WINDOWS\System32\WindowsPowerShell\v1.0 from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files (x86)\IVI Foundation\VISA\WinNT\Bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\IVI Foundation\VISA\Win64\Bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\Git\cmd from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit from User Path (Defined in both; User and System PATH)
Removing folder C:\PostgreSQL\pg96\bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\nodejs from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin from User Path (Defined in both; User and System PATH)
Removing folder C:\Users\USER\repos\git-subrepo\lib from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\MATLAB\R2018b\bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\TortoiseSVN\bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\dotnet from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files (x86)\PuTTY from User Path (Defined in both; User and System PATH)
Removing folder C:\WINDOWS\System32\OpenSSH from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\Intel\WiFi\bin from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\Common Files\Intel\WirelessCommon from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\CrashPlan\jre\bin\server from User Path (Defined in both; User and System PATH)
Removing folder C:\Program Files\CrashPlan\jre\bin from User Path (Defined in both; User and System PATH)
Could not clean the SYSTEM PATH! Needs Admin rights!
```
</p>
</details>

<details><summary>Output from Run #2 (✅👮 With admin rights)</summary>
<p>

```
Removing folder C:\Program Files\Sublime Text 3 from System Path (non-existing)
Removing folder C:\Programs\phantomjs-2.1.1-windows\bin from System Path (non-existing)
Removing folder C:\Programs\chromedriver_win32 from System Path (non-existing)
Removing folder C:\Program Files (x86)\Microsoft SQL Server\150\DTS\Binn from System Path (non-existing)
```
</p>
</details>
<br>

- Screenshots of User PATH before and after clean:
![User PATH](../img/user-before-after-clean.png)
- Screenshots of System PATH before and after clean (with admin rights):
![System PATH](../img/system-before-after-clean.png)
85 changes: 85 additions & 0 deletions docs/example_ensure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Example of using `pathtub.ensure`

## Description of problem
- Want to use [pyusb](https://github.com/pyusb/pyusb), and need to ensure that it finds the [libusb-1.0.dll](https://github.com/libusb/libusb/releases).
- Therefore, a folder containing the `libusb-1.0.dll` must be added to the PATH.

### Trying without adding DLL folder

- `get_backend()` returns no backends, since `libusb-1.0.dll` is not in the PATH
```python
import usb.backend.libusb1

#True
usb.backend.libusb1.get_backend() is None
```
- Also this throws a `LibraryNotFoundException`
```python
usb.backend.libusb1._load_library()
```

```
# ---------------------------------------------------------------------------
# LibraryNotFoundException Traceback (most recent call last)
# ...
# C:\Python\Python37\lib\site-packages\usb\libloader.py in load_locate_library(candidates, cygwin_lib, name, win_cls, cygwin_cls, others_cls, find_library, check_symbols)
# 171 else:
# 172 _LOGGER.error('%r could not be found', (name or candidates))
# --> 173 raise LibraryNotFoundException(name)
# 174 else:
# 175 raise NoLibraryCandidatesException(name)
# LibraryNotFoundException: Libusb 1
```

### Adding DLL folder to PATH (current process)

- Running now `pathtub.ensure` before calling the `usb.backend.libusb1` functions, and the `libusb-1.0.dll` is found correctly.
- It is safe to leave the `ensure` command to the script. Running it multiple times does not do any harm; it just checks the `os.environ['PATH']`.
```python
from pathtub import ensure
import usb.backend.libusb1

# Folder that contains libusb-1.0.dll
dll_folder = r'C:\My favourite folder\libusb\dll'

ensure(dll_folder)

# <usb.backend.libusb1._LibUSB at 0x23abb47bb38>
usb.backend.libusb1.get_backend()

#<WinDLL 'C:\My favourite folder\libusb\dll\libusb-1.0.dll', handle 7fffe9240000 at 0x23abb05e470>
usb.backend.libusb1._load_library()

```


### Adding DLL folder to PATH (permanently)

- By default, the `ensure` adds the DLL folder to the Process PATH, which means *only current python process will be able to see it*.
- To add the DLL folder to the `User PATH` (see: [Windows PATH variables](path_variables.md).), and make `libusb-1.0.dll` available for all processes spawned afterwards, we use the `permanent` parameter.
- It is safe to leave the `ensure(dll_folder, permanent=True)` command to the script. Running it multiple times does not do any harm; it just checks the `os.environ['PATH']`.

```python
from pathtub import ensure
import usb.backend.libusb1

# Folder that contains libusb-1.0.dll
dll_folder = r'C:\My favourite folder\libusb\dll'

# Addition: permanent=True
ensure(dll_folder, permanent=True)

# <usb.backend.libusb1._LibUSB at 0x23abb47bb38>
usb.backend.libusb1.get_backend()

#<WinDLL 'C:\My favourite folder\libusb\dll\libusb-1.0.dll', handle 7fffe9240000 at 0x23abb05e470>
usb.backend.libusb1._load_library()

```
- **Note**: If you have the DLL_folder already in the Process PATH of the current process, then `ensure(dll_folder, permanent=True)` will skip adding it to the (permanent) User Path. You may also use
- `ensure(dll_folder, permanent=True, force=True)` or
- `pathtub.add_to_path(dll_folder, 'user')`
- Note also that forcing adding to the permanent path each time a script is ran is not recommended since it takes some time. Consider the above as one-time-script.

Loading

0 comments on commit da06d50

Please sign in to comment.