Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-latest, macOS-latest, windows-latest]

steps:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# system files
\.DS_Store
.idea

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.asdf_finder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.asdf_finder module
=================================

.. automodule:: pythonfinder.finders.asdf_finder
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.base_finder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.base_finder module
==================================

.. automodule:: pythonfinder.finders.base_finder
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.path_finder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.path_finder module
==================================

.. automodule:: pythonfinder.finders.path_finder
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.pyenv_finder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.pyenv_finder module
==================================

.. automodule:: pythonfinder.finders.pyenv_finder
:members:
:undoc-members:
:show-inheritance:
19 changes: 19 additions & 0 deletions docs/pythonfinder.finders.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pythonfinder.finders package
=========================

.. automodule:: pythonfinder.finders
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

.. toctree::

pythonfinder.finders.base_finder
pythonfinder.finders.path_finder
pythonfinder.finders.system_finder
pythonfinder.finders.pyenv_finder
pythonfinder.finders.asdf_finder
pythonfinder.finders.windows_registry
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.system_finder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.system_finder module
===================================

.. automodule:: pythonfinder.finders.system_finder
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/pythonfinder.finders.windows_registry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.finders.windows_registry module
=====================================

.. automodule:: pythonfinder.finders.windows_registry
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/pythonfinder.models.python_info.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.models.python_info module
==================================

.. automodule:: pythonfinder.models.python_info
:members:
:undoc-members:
:show-inheritance:
5 changes: 1 addition & 4 deletions docs/pythonfinder.models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,4 @@ Submodules

.. toctree::

pythonfinder.models.mixins
pythonfinder.models.path
pythonfinder.models.python
pythonfinder.models.windows
pythonfinder.models.python_info
3 changes: 2 additions & 1 deletion docs/pythonfinder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Subpackages
.. toctree::

pythonfinder.models
pythonfinder.finders
pythonfinder.utils

Submodules
----------
Expand All @@ -22,4 +24,3 @@ Submodules
pythonfinder.environment
pythonfinder.exceptions
pythonfinder.pythonfinder
pythonfinder.utils
7 changes: 7 additions & 0 deletions docs/pythonfinder.utils.path_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.utils.path_utils module
================================

.. automodule:: pythonfinder.utils.path_utils
:members:
:undoc-members:
:show-inheritance:
12 changes: 10 additions & 2 deletions docs/pythonfinder.utils.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
pythonfinder.utils module
=========================
pythonfinder.utils package
========================

.. automodule:: pythonfinder.utils
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

.. toctree::

pythonfinder.utils.path_utils
pythonfinder.utils.version_utils
7 changes: 7 additions & 0 deletions docs/pythonfinder.utils.version_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pythonfinder.utils.version_utils module
===================================

.. automodule:: pythonfinder.utils.version_utils
:members:
:undoc-members:
:show-inheritance:
41 changes: 19 additions & 22 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,34 @@ Install from `Github`_:
Usage
******

Using PythonFinder is easy. Simply import it and ask for a python:
Using PythonFinder is easy. Simply import it and ask for a python:

.. code-block:: pycon

>>> from pythonfinder.pythonfinder import PythonFinder
>>> PythonFinder.from_line('python3')
'/home/techalchemy/.pyenv/versions/3.6.5/python3'

>>> from pythonfinder import Finder
>>> f = Finder()
>>> f.find_python_version(3, minor=6)
PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.6.5/bin/python'), _children={}, is_root=False, only_python=False, py_version=PythonVersion(major=3, minor=6, patch=5, is_prerelease=False, is_postrelease=False, is_devrelease=False, version=<Version('3.6.5')>, architecture='64bit', comes_from=PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.6.5/bin/python'), _children={}, is_root=True, only_python=False, py_version=None, pythons=None), executable=None), pythons=None)
PythonInfo(path=PosixPath('/home/user/.pyenv/versions/3.6.5/bin/python'), version_str='3.6.5', major=3, minor=6, patch=5, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.6.5')>, architecture='64bit', company='PythonCore', name='python', executable='/home/user/.pyenv/versions/3.6.5/bin/python')

>>> f.find_python_version(2)
PathEntry(path=PosixPath('/home/hawk/.pyenv/shims/python2'), ...py_version=PythonVersion(major=2, minor=7, patch=15, is_prerelease=False, is_postrelease=False, is_devrelease=False, version=<Version('2.7.15')>, architecture='64bit', comes_from=PathEntry(path=PosixPath('/home/hawk/.pyenv/shims/python2'), _children={}, is_root=True, only_python=False, py_version=None, pythons=None), executable=None), pythons=None)
>>> f.find_python_version("anaconda3-5.3.0")
PythonInfo(path=PosixPath('/home/user/.pyenv/versions/2.7.15/bin/python'), version_str='2.7.15', major=2, minor=7, patch=15, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('2.7.15')>, architecture='64bit', company='PythonCore', name='python', executable='/home/user/.pyenv/versions/2.7.15/bin/python')

Find a named distribution, such as ``anaconda3-5.3.0``:

.. code-block:: pycon

PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/anaconda3-5.3.0/bin/python3.7m'), _children={'/home/hawk/.pyenv/versions/anaconda3-5.3.0/bin/python3.7m': ...}, only_python=False, name='anaconda3-5.3.0', _py_version=PythonVersion(major=3, minor=7, patch=0, is_prerelease=False, is_postrelease=False, is_devrelease=False,...))
>>> f.find_python_version("anaconda3-5.3.0")
PythonInfo(path=PosixPath('/home/user/.pyenv/versions/anaconda3-5.3.0/bin/python'), version_str='3.7.0', major=3, minor=7, patch=0, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.7.0')>, architecture='64bit', company='Anaconda', name='anaconda3-5.3.0', executable='/home/user/.pyenv/versions/anaconda3-5.3.0/bin/python')

PythonFinder can even find beta releases:

.. code-block:: pycon

>>> f.find_python_version(3, minor=7)
PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.7.0b1/bin/python'), _children={}, is_root=False, only_python=False, py_version=PythonVersion(major=3, minor=7, patch=0, is_prerelease=True, is_postrelease=False, is_devrelease=False, version=<Version('3.7.0b1')>, architecture='64bit', comes_from=PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.7.0b1/bin/python'), _children={}, is_root=True, only_python=False, py_version=None, pythons=None), executable=None), pythons=None)
>>> f.find_python_version(3, minor=7, pre=True)
PythonInfo(path=PosixPath('/home/user/.pyenv/versions/3.7.0b1/bin/python'), version_str='3.7.0b1', major=3, minor=7, patch=0, is_prerelease=True, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.7.0b1')>, architecture='64bit', company='PythonCore', name='python', executable='/home/user/.pyenv/versions/3.7.0b1/bin/python')

>>> f.which('python')
PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.6.5/bin/python'), _children={}, is_root=False, only_python=False, py_version=PythonVersion(major=3, minor=6, patch=5, is_prerelease=False, is_postrelease=False, is_devrelease=False, version=<Version('3.6.5')>, architecture='64bit', comes_from=PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.6.5/bin/python'), _children={}, is_root=True, only_python=False, py_version=None, pythons=None), executable=None), pythons=None)
PosixPath('/home/user/.pyenv/versions/3.6.5/bin/python')


Windows Support
Expand All @@ -85,13 +82,13 @@ PythonFinder natively supports windows via both the *PATH* environment variable
>>> from pythonfinder import Finder
>>> f = Finder()
>>> f.find_python_version(3, minor=6)
PythonVersion(major=3, minor=6, patch=4, is_prerelease=False, is_postrelease=False, is_devrelease=False, version=<Version('3.6.4')>, architecture='64bit', comes_from=PathEntry(path=WindowsPath('C:/Program Files/Python36/python.exe'), _children={}, is_root=False, only_python=True, py_version=None, pythons=None), executable=WindowsPath('C:/Program Files/Python36/python.exe'))
PythonInfo(path=WindowsPath('C:/Program Files/Python36/python.exe'), version_str='3.6.4', major=3, minor=6, patch=4, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.6.4')>, architecture='64bit', company='PythonCore', name='python', executable='C:/Program Files/Python36/python.exe')

>>> f.find_python_version(3, minor=7, pre=True)
PythonVersion(major=3, minor=7, patch=0, is_prerelease=True, is_postrelease=False, is_devrelease=False, version=<Version('3.7.0b5')>, architecture='64bit', comes_from=PathEntry(path=WindowsPath('C:/Program Files/Python37/python.exe'), _children={}, is_root=False, only_python=True, py_version=None, pythons=None), executable=WindowsPath('C:/Program Files/Python37/python.exe'))
PythonInfo(path=WindowsPath('C:/Program Files/Python37/python.exe'), version_str='3.7.0b5', major=3, minor=7, patch=0, is_prerelease=True, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.7.0b5')>, architecture='64bit', company='PythonCore', name='python', executable='C:/Program Files/Python37/python.exe')

>>> f.which('python')
PathEntry(path=WindowsPath('C:/Python27/python.exe'), _children={}, is_root=False, only_python=False, py_version=None, pythons=None)
WindowsPath('C:/Python27/python.exe')

Finding Executables
///////////////////
Expand All @@ -101,16 +98,16 @@ PythonFinder also provides **which** functionality across platforms, and it uses
.. code-block:: pycon

>>> f.which('cmd')
PathEntry(path=WindowsPath('C:/windows/system32/cmd.exe'), _children={}, is_root=False, only_python=False, py_version=None, pythons=None)
WindowsPath('C:/windows/system32/cmd.exe')

>>> f.which('code')
PathEntry(path=WindowsPath('C:/Program Files/Microsoft VS Code/bin/code'), _children={}, is_root=False, only_python=False, py_version=None, pythons=None)
WindowsPath('C:/Program Files/Microsoft VS Code/bin/code')

>>> f.which('vim')
PathEntry(path=PosixPath('/usr/bin/vim'), _children={}, is_root=False, only_python=False, py_version=None, pythons=None)
PosixPath('/usr/bin/vim')

>>> f.which('inv')
PathEntry(path=PosixPath('/home/hawk/.pyenv/versions/3.6.5/bin/inv'), _children={}, is_root=False, only_python=False, py_version=None, pythons=None)
PosixPath('/home/user/.pyenv/versions/3.6.5/bin/inv')


Architecture support
Expand All @@ -121,7 +118,7 @@ PythonFinder supports architecture specific lookups on all platforms:
.. code-block:: pycon

>>> f.find_python_version(3, minor=6, arch="64")
PathEntry(path=PosixPath('/usr/bin/python3'), _children={'/usr/bin/python3': ...}, only_python=False, name='python3', _py_version=PythonVersion(major=3, minor=6, patch=7, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.6.7')>, architecture='64bit', comes_from=..., executable='/usr/bin/python3', name='python3'), _pythons=defaultdict(None, {}), is_root=False)
PythonInfo(path=PosixPath('/usr/bin/python3'), version_str='3.6.7', major=3, minor=6, patch=7, is_prerelease=False, is_postrelease=False, is_devrelease=False, is_debug=False, version=<Version('3.6.7')>, architecture='64bit', company='PythonCore', name='python3', executable='/usr/bin/python3')


Integrations
Expand All @@ -134,6 +131,6 @@ Integrations
* `Pipenv <https://pipenv.org>`_


.. click:: pythonfinder:cli
:prog: pyfinder
.. click:: pythonfinder.cli:cli
:prog: pythonfinder
:show-nested:
46 changes: 46 additions & 0 deletions news/rewrite-3.0.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Refactor pythonfinder for improved efficiency and PEP 514 support
Summary

This PR completely refactors the pythonfinder module to improve efficiency, reduce logical errors, and fix support for PEP 514 (Python registration in the Windows registry). The refactoring replaces the complex object hierarchy with a more modular, composition-based approach that is easier to maintain and extend.
Motivation

The original pythonfinder implementation had several issues:

Complex object wrapping with paths as objects, leading to excessive recursion
Tight coupling between classes making the code difficult to follow and maintain
Broken Windows registry support (PEP 514)
Performance issues due to redundant path scanning and inefficient caching

Changes

Architecture: Replaced inheritance-heavy design with a composition-based approach using specialized finders
Data Model: Simplified the data model with a clean PythonInfo dataclass
Windows Support: Implemented proper PEP 514 support for Windows registry
Performance: Improved caching and reduced redundant operations
Error Handling: Added more specific exceptions and better error handling

Features

The refactored implementation continues to support all required features:

System and user PATH searches
pyenv installations
asdf installations
Windows registry (PEP 514) - now working correctly

Implementation Details

The new implementation is organized into three main components:

Finders: Specialized classes for finding Python in different locations
SystemFinder: Searches in the system PATH
PyenvFinder: Searches in pyenv installations
AsdfFinder: Searches in asdf installations
WindowsRegistryFinder: Implements PEP 514 for Windows registry

Models: Simple data classes for storing Python information
PythonInfo: Stores information about a Python installation

Utils: Utility functions for path and version handling
path_utils.py: Path-related utility functions
version_utils.py: Version-related utility functions
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
Expand All @@ -38,7 +39,7 @@ dependencies = [
]

[project.optional-dependencies]
cli = ["click"]
cli = ["click", "colorama"]
tests = [
"pytest",
"pytest-timeout",
Expand Down
9 changes: 4 additions & 5 deletions src/pythonfinder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from __future__ import annotations

from .exceptions import InvalidPythonVersion
from .models import SystemPath
from .exceptions import InvalidPythonVersion, PythonNotFound
from .models.python_info import PythonInfo
from .pythonfinder import Finder

__version__ = "2.1.1.dev0"
__version__ = "3.0.0"


__all__ = ["Finder", "SystemPath", "InvalidPythonVersion"]
__all__ = ["Finder", "PythonInfo", "InvalidPythonVersion", "PythonNotFound"]
Loading
Loading