Skip to content

Commit

Permalink
Merge pull request #4 from Sachaa-Thanasius/change-shim-mechanisms
Browse files Browse the repository at this point in the history
Change shim mechanisms
  • Loading branch information
Sachaa-Thanasius authored Oct 3, 2024
2 parents 34f0da9 + 14d011c commit efc9e7e
Show file tree
Hide file tree
Showing 12 changed files with 464 additions and 724 deletions.
209 changes: 105 additions & 104 deletions README.rst

Large diffs are not rendered by default.

97 changes: 49 additions & 48 deletions bench/bench_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"""Simple benchark script for comparing the import time of the Python standard library when using regular imports,
defer_imports-influence imports, and slothy-influenced imports.
The sample scripts being imported are generated with benchmark/generate_samples.py.
The sample scripts being imported are generated with bench/generate_samples.py.
"""

import platform
import shutil
import sys
import time
from pathlib import Path
Expand All @@ -24,20 +25,6 @@ def __exit__(self, *exc_info: object):
self.elapsed = time.perf_counter() - self.elapsed


def remove_pycaches() -> None:
"""Remove all cached Python bytecode files from the current working directory."""

for file in Path().rglob("*.py[co]"):
file.unlink()

for dir_ in Path().rglob("__pycache__"):
# Sometimes, files with atypical names are still in these.
for file in dir_.iterdir():
if file.is_file():
file.unlink()
dir_.rmdir()


def bench_regular() -> float:
with CatchTime() as ct:
import bench.sample_regular
Expand All @@ -51,51 +38,32 @@ def bench_slothy() -> float:


def bench_defer_imports_local() -> float:
with defer_imports.install_import_hook(), CatchTime() as ct:
import bench.sample_defer_local
with CatchTime() as ct: # noqa: SIM117
with defer_imports.install_import_hook(uninstall_after=True):
import bench.sample_defer_local
return ct.elapsed


def bench_defer_imports_global() -> float:
with CatchTime() as ct, defer_imports.install_import_hook(apply_all=True):
import bench.sample_defer_global
with CatchTime() as ct: # noqa: SIM117
with defer_imports.install_import_hook(uninstall_after=True, apply_all=True):
import bench.sample_defer_global
return ct.elapsed


BENCH_FUNCS = {
"regular": bench_regular,
"slothy": bench_slothy,
"defer_imports (local)": bench_defer_imports_local,
"defer_imports (global)": bench_defer_imports_global,
}


def main() -> None:
import argparse
def remove_pycaches() -> None:
"""Remove all cached Python bytecode files from the current working directory."""

# Get arguments from user.
parser = argparse.ArgumentParser()
parser.add_argument(
"--exec-order",
action="extend",
nargs=4,
choices=BENCH_FUNCS.keys(),
type=str,
help="The order in which the influenced (or not influenced) imports are run",
)
args = parser.parse_args()
for dir_ in Path().rglob("__pycache__"):
shutil.rmtree(dir_)

# Do any remaining setup.
if sys.dont_write_bytecode:
remove_pycaches()
for file in Path().rglob("*.py[co]"):
file.unlink()

exec_order = args.exec_order or list(BENCH_FUNCS)

# Perform benchmarking.
results = {type_: BENCH_FUNCS[type_]() for type_ in exec_order}
minimum = min(results.values())
def pretty_print_results(results: dict[str, float], minimum: float) -> None:
"""Format and print results as an reST-style list table."""

# Format and print results as an reST-style list table.
impl_header = "Implementation"
impl_len = len(impl_header)
impl_divider = "=" * impl_len
Expand Down Expand Up @@ -136,5 +104,38 @@ def main() -> None:
print(divider)


BENCH_FUNCS = {
"regular": bench_regular,
"slothy": bench_slothy,
"defer_imports (local)": bench_defer_imports_local,
"defer_imports (global)": bench_defer_imports_global,
}


def main() -> None:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
"--exec-order",
action="extend",
nargs=4,
choices=BENCH_FUNCS.keys(),
type=str,
help="The order in which the influenced (or not influenced) imports are run",
)
args = parser.parse_args()

if sys.dont_write_bytecode:
remove_pycaches()

exec_order: list[str] = args.exec_order or list(BENCH_FUNCS)

results = {type_: BENCH_FUNCS[type_]() for type_ in exec_order}
minimum = min(results.values())

pretty_print_results(results, minimum)


if __name__ == "__main__":
raise SystemExit(main())
38 changes: 19 additions & 19 deletions bench/generate_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from pathlib import Path


# Mostly sourced from https://gist.github.com/indygreg/be1c229fa41ced5c76d912f7073f9de6.
STDLIB_IMPORTS = """\
# Modified from https://gist.github.com/indygreg/be1c229fa41ced5c76d912f7073f9de6.
_STDLIB_IMPORTS = """\
import __future__
# import _bootlocale # Doesn't exist on 3.11 on Windows
Expand Down Expand Up @@ -549,47 +549,47 @@
import test
"""

INDENTED_STDLIB_IMPORTS = "".join(
(f" {line}" if line.strip() else line) for line in STDLIB_IMPORTS.splitlines(keepends=True)
_INDENTED_STDLIB_IMPORTS = "".join(
(f'{" " * 4}{line}' if line.strip() else line) for line in _STDLIB_IMPORTS.splitlines(keepends=True)
)
PYRIGHT_IGNORE_DIRECTIVES = "# pyright: reportUnusedImport=none, reportMissingTypeStubs=none"
GENERATED_BY_COMMENT = "# Generated by benchmark/generate_samples.py"
_PYRIGHT_IGNORE_DIRECTIVES = "# pyright: reportUnusedImport=none, reportMissingTypeStubs=none"
_GENERATED_BY_COMMENT = "# Generated by bench/generate_samples.py"

CONTEXT_MANAGER_TEMPLATE = f"""\
{PYRIGHT_IGNORE_DIRECTIVES}
{GENERATED_BY_COMMENT}
_CONTEXT_MANAGER_TEMPLATE = f"""\
{_PYRIGHT_IGNORE_DIRECTIVES}
{_GENERATED_BY_COMMENT}
{{import_stmt}}
{{ctx_manager}}
{INDENTED_STDLIB_IMPORTS}\
{_INDENTED_STDLIB_IMPORTS}\
"""


def main() -> None:
bench_path = Path("bench").resolve()

# regular imports
regular_contents = "\n".join((PYRIGHT_IGNORE_DIRECTIVES, GENERATED_BY_COMMENT, STDLIB_IMPORTS))
# ---- regular imports
regular_contents = "\n".join((_PYRIGHT_IGNORE_DIRECTIVES, _GENERATED_BY_COMMENT, _STDLIB_IMPORTS))
regular_path = bench_path / "sample_regular.py"
regular_path.write_text(regular_contents, encoding="utf-8")

# defer_imports-instrumented and defer_imports-hooked imports (global)
# ---- defer_imports-instrumented and defer_imports-hooked imports (global)
shutil.copy(regular_path, regular_path.with_name("sample_defer_global.py"))

# defer_imports-instrumented and defer_imports-hooked imports (local)
defer_imports_contents = CONTEXT_MANAGER_TEMPLATE.format(
# ---- defer_imports-instrumented and defer_imports-hooked imports (local)
defer_imports_contents = _CONTEXT_MANAGER_TEMPLATE.format(
import_stmt="import defer_imports",
ctx_manager="with defer_imports.until_use:",
)
defer_imports_path = bench_path / "sample_defer_local.py"
defer_imports_path.write_text(defer_imports_contents, encoding="utf-8")

# defer_imports-influenced imports (local), but for a test in the tests directory
shutil.copy(defer_imports_path, bench_path.parent / "tests" / "sample_stdlib_imports.py")
# ---- defer_imports-influenced imports (local), but for a test in the tests directory
shutil.copy(defer_imports_path, bench_path.with_name("tests") / "sample_stdlib_imports.py")

# slothy-hooked imports
slothy_contents = CONTEXT_MANAGER_TEMPLATE.format(
# ---- slothy-hooked imports
slothy_contents = _CONTEXT_MANAGER_TEMPLATE.format(
import_stmt="from slothy import lazy_importing",
ctx_manager="with lazy_importing():",
)
Expand Down
2 changes: 1 addition & 1 deletion bench/sample_defer_global.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyright: reportUnusedImport=none, reportMissingTypeStubs=none
# Generated by benchmark/generate_samples.py
# Generated by bench/generate_samples.py
import __future__

# import _bootlocale # Doesn't exist on 3.11 on Windows
Expand Down
2 changes: 1 addition & 1 deletion bench/sample_defer_local.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyright: reportUnusedImport=none, reportMissingTypeStubs=none
# Generated by benchmark/generate_samples.py
# Generated by bench/generate_samples.py
import defer_imports


Expand Down
2 changes: 1 addition & 1 deletion bench/sample_regular.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyright: reportUnusedImport=none, reportMissingTypeStubs=none
# Generated by benchmark/generate_samples.py
# Generated by bench/generate_samples.py
import __future__

# import _bootlocale # Doesn't exist on 3.11 on Windows
Expand Down
2 changes: 1 addition & 1 deletion bench/sample_slothy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyright: reportUnusedImport=none, reportMissingTypeStubs=none
# Generated by benchmark/generate_samples.py
# Generated by bench/generate_samples.py
from slothy import lazy_importing


Expand Down
22 changes: 10 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ features = ["bench"]
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]

[tool.hatch.envs.bench.scripts]
bench = "python -m bench.bench_samples"
raw_import = 'python -X importtime -c "import defer_imports"'
stdlib = "python -m bench.bench_samples"
import-time = 'python -X importtime -c "import {args:defer_imports}"'
simple-import-time = 'python -m timeit -n 1 -r 1 -- "import {args:defer_imports}"'


# -------- Test config
Expand All @@ -78,12 +79,10 @@ defer_imports = ["src"]
[tool.coverage.run]
plugins = ["covdefaults"]
source = ["defer_imports", "tests"]
omit = [
"src/defer_imports/_typing_compat.py", # Has a module-level __getattr__ that isn't invoked at runtime.
]

[tool.coverage.report]
fail_under = 90
exclude_lines = ["^\\s*(?:el)?if TYPE_CHECKING:$"]


# -------- Linter config
Expand Down Expand Up @@ -140,6 +139,7 @@ extend-ignore = [
"PD011", # Erroneous issue that triggers for any .values attribute access at all.
"PLR2004", # "Magic number" depends on the use case.
"RUF002", # "Ambiguous character" depends on the use case.
"RUF003", # "Ambiguous character" depends on the use case.

# ---- Recommended by Ruff when using Ruff format
"E111",
Expand All @@ -152,13 +152,12 @@ extend-ignore = [
"ISC002",

# ---- Project-specific rules
"RET505", # Returns in both parts of if-else are fine.
"SIM108", # if-else instead of a ternary is fine.
"RET505", # Returns in both parts of if-else can be more readable.
"SIM108", # if-else instead of a ternary can be more readable.
]
unfixable = [
"ERA", # Prevent unlikely erroneous deletion.
]
typing-modules = ["defer_imports._typing_compat"]

[tool.ruff.lint.isort]
lines-after-imports = 2
Expand All @@ -170,15 +169,14 @@ keep-runtime-typing = true
[tool.ruff.lint.per-file-ignores]
# ---- Package code
"src/defer_imports/*.py" = [
"A002", # Allow some shadowing of builtins by parameter names.
"PLW0603", # "global" is used to update variables at global scope.
"A002", # Allow some shadowing of builtins by parameter names.
]

# ---- Test code
"tests/**/test_*.py" = [
"T201", # Printing is fine.
"T203", # Pretty printing is fine.
# Don't need some annotations in tests.
"T203", # Pretty-printing is fine.
# Don't need return annotations in tests.
"ANN201",
"ANN202",
"S102", # exec is used to test for NameError within a module's namespace.
Expand Down
Loading

0 comments on commit efc9e7e

Please sign in to comment.