Skip to content

Commit

Permalink
added option to install to usercustomize.py
Browse files Browse the repository at this point in the history
  • Loading branch information
mbway committed Jun 16, 2024
1 parent 0866441 commit e473b82
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 45 deletions.
62 changes: 47 additions & 15 deletions src/maturin_import_hook/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import json
import platform
import shutil
import site
import subprocess
from pathlib import Path

from maturin_import_hook._building import get_default_build_dir
from maturin_import_hook._site import (
get_sitecustomize_path,
get_usercustomize_path,
has_automatic_installation,
insert_automatic_installation,
remove_automatic_installation,
Expand Down Expand Up @@ -80,25 +82,30 @@ def _action_cache_clear(interactive: bool) -> None:

def _action_site_info(format_name: str) -> None:
sitecustomize_path = get_sitecustomize_path()
usercustomize_path = get_usercustomize_path()

_print_info(
{
"sitecustomize_exists": sitecustomize_path.exists(),
"sitecustomize_path": str(sitecustomize_path),
"import_hook_installed": has_automatic_installation(sitecustomize_path),
"sitecustomize_exists": sitecustomize_path.exists(),
"sitecustomize_import_hook_installed": has_automatic_installation(sitecustomize_path),
"user_site_enabled": str(site.ENABLE_USER_SITE),
"usercustomize_path": str(usercustomize_path),
"usercustomize_exists": usercustomize_path.exists(),
"usercustomize_import_hook_installed": has_automatic_installation(usercustomize_path),
},
format_name,
)


def _action_site_install(preset_name: str, force: bool) -> None:
sitecustomize_path = get_sitecustomize_path()
insert_automatic_installation(sitecustomize_path, preset_name, force)
def _action_site_install(*, user: bool, preset_name: str, force: bool) -> None:
module_path = get_usercustomize_path() if user else get_sitecustomize_path()
insert_automatic_installation(module_path, preset_name, force)


def _action_site_uninstall() -> None:
sitecustomize_path = get_sitecustomize_path()
remove_automatic_installation(sitecustomize_path)
def _action_site_uninstall(*, user: bool) -> None:
module_path = get_usercustomize_path() if user else get_sitecustomize_path()
remove_automatic_installation(module_path)


def _ask_yes_no(question: str) -> bool:
Expand Down Expand Up @@ -149,31 +156,56 @@ def _main() -> None:

site_action = subparsers.add_parser(
"site",
help="manage installation of the import hook into site-packages/sitecustomize.py (so it starts automatically)",
help=(
"manage installation of the import hook into site-packages/sitecustomize.py "
"or usercustomize.py (so it starts automatically)"
),
)
site_sub_actions = site_action.add_subparsers(dest="sub_action")
site_info = site_sub_actions.add_parser(
"info", help="information about the current status of installation into sitecustomize"
"info", help="information about the current status of installation into sitecustomize/usercustomize"
)
site_info.add_argument(
"-f", "--format", choices=["text", "json"], default="text", help="the format to output the data in"
)

install = site_sub_actions.add_parser(
"install", help="install the import hook into site-packages/sitecustomize.py so that it starts automatically"
"install",
help=(
"install the import hook into site-packages/sitecustomize.py "
"or usercustomize.py so that it starts automatically"
),
)
install.add_argument(
"-f",
"--force",
action="store_true",
help="whether to overwrite any existing managed import hook installation in sitecustomize.py",
help="whether to overwrite any existing managed import hook installation",
)
install.add_argument(
"--preset",
default="debug",
choices=["debug", "release"],
help="the settings preset for the import hook to use when building packages. Defaults to 'debug'.",
)
site_sub_actions.add_parser("uninstall", help="uninstall the import hook from site-packages/sitecustomize.py")
install.add_argument(
"--user",
action="store_true",
help="whether to install into usercustomize.py instead of sitecustomize.py. "
"Note that usercustomize.py is shared between virtualenvs of the same interpreter version and is not loaded "
"unless the virtualenv is created with the `--system-site-packages` argument. Use `site info` to check "
"whether usercustomize.py is loaded the current interpreter.",
)

uninstall = site_sub_actions.add_parser(
"uninstall",
help="uninstall the import hook from site-packages/sitecustomize.py or site-packages/usercustomize.py",
)
uninstall.add_argument(
"--user",
action="store_true",
help="whether to uninstall from usercustomize.py instead of sitecustomize.py",
)

args = parser.parse_args()

Expand All @@ -192,9 +224,9 @@ def _main() -> None:
if args.sub_action == "info":
_action_site_info(args.format)
elif args.sub_action == "install":
_action_site_install(args.preset, args.force)
_action_site_install(user=args.user, preset_name=args.preset, force=args.force)
elif args.sub_action == "uninstall":
_action_site_uninstall()
_action_site_uninstall(user=args.user)
else:
site_action.print_help()
else:
Expand Down
63 changes: 40 additions & 23 deletions src/maturin_import_hook/_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@

MANAGED_INSTALLATION_PRESETS = {
"debug": dedent("""\
import maturin_import_hook
maturin_import_hook.install()
try:
import maturin_import_hook
except ImportError:
pass
else:
maturin_import_hook.install()
"""),
"release": dedent("""\
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
maturin_import_hook.install(MaturinSettings(release=True))
try:
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
except ImportError:
pass
else:
maturin_import_hook.install(MaturinSettings(release=True))
"""),
}

Expand All @@ -36,54 +44,62 @@ def get_sitecustomize_path() -> Path:
return Path(site_packages[0]) / "sitecustomize.py"


def has_automatic_installation(sitecustomize: Path) -> bool:
if not sitecustomize.is_file():
def get_usercustomize_path() -> Path:
user_site_packages = site.getusersitepackages()
if user_site_packages is None:
msg = "could not find usercustomize.py (user site-packages not found)"
raise FileNotFoundError(msg)
return Path(user_site_packages) / "usercustomize.py"


def has_automatic_installation(module_path: Path) -> bool:
if not module_path.is_file():
return False
code = sitecustomize.read_text()
code = module_path.read_text()
return MANAGED_INSTALL_START in code


def remove_automatic_installation(sitecustomize: Path) -> None:
logger.info(f"removing automatic activation from '{sitecustomize}'")
if not has_automatic_installation(sitecustomize):
def remove_automatic_installation(module_path: Path) -> None:
logger.info(f"removing automatic activation from '{module_path}'")
if not has_automatic_installation(module_path):
logger.info("no installation found")
return

code = sitecustomize.read_text()
code = module_path.read_text()
managed_start = code.find(MANAGED_INSTALL_START)
if managed_start == -1:
msg = f"failed to find managed install start marker in '{sitecustomize}'"
msg = f"failed to find managed install start marker in '{module_path}'"
raise RuntimeError(msg)
managed_end = code.find(MANAGED_INSTALL_END)
if managed_end == -1:
msg = f"failed to find managed install start marker in '{sitecustomize}'"
msg = f"failed to find managed install start marker in '{module_path}'"
raise RuntimeError(msg)
code = code[:managed_start] + code[managed_end + len(MANAGED_INSTALL_END) :]

if code.strip():
sitecustomize.write_text(code)
module_path.write_text(code)
else:
logger.info("module is now empty. Removing file.")
sitecustomize.unlink(missing_ok=True)
module_path.unlink(missing_ok=True)


def insert_automatic_installation(sitecustomize: Path, preset_name: str, force: bool) -> None:
def insert_automatic_installation(module_path: Path, preset_name: str, force: bool) -> None:
if preset_name not in MANAGED_INSTALLATION_PRESETS:
msg = f"Unknown managed installation preset name: '{preset_name}'"
raise ValueError(msg)

logger.info(f"installing automatic activation into '{sitecustomize}'")
if has_automatic_installation(sitecustomize):
logger.info(f"installing automatic activation into '{module_path}'")
if has_automatic_installation(module_path):
if force:
logger.info("already installed, but force=True. Overwriting...")
remove_automatic_installation(sitecustomize)
remove_automatic_installation(module_path)
else:
logger.info("already installed. Aborting install")
return

parts = []
if sitecustomize.exists():
parts.append(sitecustomize.read_text())
if module_path.exists():
parts.append(module_path.read_text())
parts.append("\n")
parts.extend([
MANAGED_INSTALL_START,
Expand All @@ -92,4 +108,5 @@ def insert_automatic_installation(sitecustomize: Path, preset_name: str, force:
MANAGED_INSTALL_END,
])
code = "".join(parts)
sitecustomize.write_text(code)
module_path.parent.mkdir(parents=True, exist_ok=True)
module_path.write_text(code)
4 changes: 4 additions & 0 deletions src/maturin_import_hook/project_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,7 @@ def uninstall() -> None:
with contextlib.suppress(ValueError):
sys.meta_path.remove(IMPORTER)
IMPORTER = None


def is_installed() -> bool:
return IMPORTER is not None and IMPORTER in sys.meta_path
4 changes: 4 additions & 0 deletions src/maturin_import_hook/rust_file_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,3 +420,7 @@ def uninstall() -> None:
with contextlib.suppress(ValueError):
sys.meta_path.remove(IMPORTER)
IMPORTER = None


def is_installed() -> bool:
return IMPORTER is not None and IMPORTER in sys.meta_path
26 changes: 19 additions & 7 deletions tests/test_import_hook/test_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ def test_automatic_site_installation(tmp_path: Path) -> None:
# <maturin_import_hook>
# the following commands install the maturin import hook during startup.
# see: `python -m maturin_import_hook site`
import maturin_import_hook
maturin_import_hook.install()
try:
import maturin_import_hook
except ImportError:
pass
else:
maturin_import_hook.install()
# </maturin_import_hook>
""")

Expand Down Expand Up @@ -99,9 +103,13 @@ def test_automatic_site_installation_force_overwrite(tmp_path: Path) -> None:
# <maturin_import_hook>
# the following commands install the maturin import hook during startup.
# see: `python -m maturin_import_hook site`
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
maturin_import_hook.install(MaturinSettings(release=True))
try:
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
except ImportError:
pass
else:
maturin_import_hook.install(MaturinSettings(release=True))
# </maturin_import_hook>
""")

Expand All @@ -124,8 +132,12 @@ def test_automatic_site_installation_empty(tmp_path: Path) -> None:
# <maturin_import_hook>
# the following commands install the maturin import hook during startup.
# see: `python -m maturin_import_hook site`
import maturin_import_hook
maturin_import_hook.install()
try:
import maturin_import_hook
except ImportError:
pass
else:
maturin_import_hook.install()
# </maturin_import_hook>
""")

Expand Down

0 comments on commit e473b82

Please sign in to comment.