From cb34a607efab8c6666d94dd73284a1ea03376e61 Mon Sep 17 00:00:00 2001 From: Noble Saji Mathews Date: Mon, 17 Jun 2024 14:30:55 -0400 Subject: [PATCH] feat: integrate pycify (#22) Co-authored-by: Tushar Sadhwani --- setup.cfg | 3 +- setup.py | 1 + src/packaged/__init__.py | 15 +++++ src/packaged/__main__.py | 1 + src/packaged/cli.py | 14 ++++ src/packaged/config.py | 4 ++ tests/cli_test.py | 64 ++++++++++++++++++- tests/end_to_end/packaged_test.py | 11 +++- .../test_packages/configtest/setup.py | 1 + 9 files changed, 110 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index c0d6530..244e6b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = packaged -version = 0.5.3 +version = 0.6.0 description = The easiest way to ship python applications. long_description = file: README.md long_description_content_type = text/markdown @@ -28,6 +28,7 @@ install_requires = yen>=0.4.2 tomli>=1.1.0; python_version<'3.11' yaspin>=2.5.0 + pycify>=1.2.0 python_requires = >=3.8 package_dir = =src diff --git a/setup.py b/setup.py index 8bf1ba9..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,2 +1,3 @@ from setuptools import setup + setup() diff --git a/src/packaged/__init__.py b/src/packaged/__init__.py index d172b7f..cbb17ac 100644 --- a/src/packaged/__init__.py +++ b/src/packaged/__init__.py @@ -13,6 +13,8 @@ import yen.github from yaspin import yaspin +from pycify import replace_py_with_pyc + if TYPE_CHECKING: from yaspin.core import Yaspin @@ -48,6 +50,8 @@ def create_package( startup_command: str, python_version: str, quiet: bool = False, + pyc: bool = False, + ignore_file_patterns: list[str] | None = None, ) -> None: """Create the makeself executable, with the startup script in it.""" if os.path.exists(output_path): @@ -59,6 +63,17 @@ def create_package( if not os.path.isdir(source_directory): raise SourceDirectoryNotFound(source_directory) + if pyc: + created_pyc_files = replace_py_with_pyc( + source_directory, + python_version=python_version, + ignore_file_patterns=ignore_file_patterns, + ) + if not created_pyc_files: + print("No .pyc files were created.", file=sys.stderr) + else: + print(f"Created {len(created_pyc_files)} .pyc files.") + startup_script_name = "_packaged_startup.sh" startup_script_path = os.path.join(source_directory, startup_script_name) diff --git a/src/packaged/__main__.py b/src/packaged/__main__.py index 10bb471..0026f81 100644 --- a/src/packaged/__main__.py +++ b/src/packaged/__main__.py @@ -1,4 +1,5 @@ """Support executing the CLI by doing `python -m packaged`.""" + from __future__ import annotations from packaged.cli import cli diff --git a/src/packaged/cli.py b/src/packaged/cli.py index 72a4c02..99ed99a 100644 --- a/src/packaged/cli.py +++ b/src/packaged/cli.py @@ -74,6 +74,18 @@ def cli(argv: list[str] | None = None) -> int: action="store_true", default="CI" in os.environ, ) + parser.add_argument( + "--pyc", + help="Replace .py files with .pyc files", + action="store_true", + default=False, + ) + parser.add_argument( + "--ignore-file-patterns", + help="List of file patterns to ignore when using --pyc", + nargs="+", + default=["setup.py"], + ) args = parser.parse_args(argv) config = Config(**vars(args)) @@ -85,6 +97,8 @@ def cli(argv: list[str] | None = None) -> int: config.startup_command, config.python_version, config.quiet, + config.pyc, + config.ignore_file_patterns, ) except SourceDirectoryNotFound as exc: error(f"Folder {exc.directory_path!r} does not exist.") diff --git a/src/packaged/config.py b/src/packaged/config.py index 8bd251c..8e88b34 100644 --- a/src/packaged/config.py +++ b/src/packaged/config.py @@ -26,6 +26,8 @@ class Config: startup_command: str python_version: str quiet: bool + pyc: bool + ignore_file_patterns: list[str] | None CONFIG_NAME = "./packaged.toml" @@ -56,6 +58,8 @@ def parse_config(source_directory: str) -> Config: config_data["startup_command"], config_data.get("python_version", "3.12"), config_data.get("quiet", "CI" in os.environ), + config_data.get("pyc", False), + config_data.get("ignore_file_patterns"), ) except KeyError as exc: key = exc.args[0] diff --git a/tests/cli_test.py b/tests/cli_test.py index 58da223..ac3fe9d 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -22,6 +22,8 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: "python -m foo", packaged.DEFAULT_PYTHON_VERSION, False, + False, + ["setup.py"], ) with mock.patch.object(packaged.cli, "create_package") as mocked: @@ -31,7 +33,14 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: # specified python version mocked.assert_called_with( - None, "./baz", "pip install baz", "python -m baz", "3.10", False + None, + "./baz", + "pip install baz", + "python -m baz", + "3.10", + False, + False, + ["setup.py"], ) with mock.patch.object(packaged.cli, "create_package") as mocked: @@ -52,6 +61,8 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: "python src/mypackage/cli.py", packaged.DEFAULT_PYTHON_VERSION, False, + False, + ["setup.py"], ) args = mocked.call_args[0] assert args[0].endswith("/mypackage") @@ -70,6 +81,8 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: "python some.py", packaged.DEFAULT_PYTHON_VERSION, True, + False, + ["setup.py"], ) # Test --quiet when CI is true, regardless of if the flag is passed @@ -85,6 +98,8 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: "python some.py", packaged.DEFAULT_PYTHON_VERSION, True, + False, + ["setup.py"], ) monkeypatch.setattr(os, "environ", {"CI": "1"}) with mock.patch.object(packaged.cli, "create_package") as mocked: @@ -100,4 +115,51 @@ def test_cli(monkeypatch: MonkeyPatch) -> None: "python some.py", packaged.DEFAULT_PYTHON_VERSION, True, + False, + ["setup.py"], + ) + + # unset CI + monkeypatch.setattr(os, "environ", {}) + with mock.patch.object(packaged.cli, "create_package") as mocked: + packaged.cli.cli( + ["./some", "pip install some", "python some.py", "./some.bin", "--pyc"] + ) + + # pyc is True + mocked.assert_called_with( + mock.ANY, + "./some", + "pip install some", + "python some.py", + packaged.DEFAULT_PYTHON_VERSION, + False, + True, + ["setup.py"], + ) + + with mock.patch.object(packaged.cli, "create_package") as mocked: + packaged.cli.cli( + [ + "./some", + "pip install some", + "python some.py", + "./some.bin", + "--pyc", + "--ignore-file-patterns", + "foo.py", + "test/*.py", + ] + ) + + # pyc is True and ignore_file_patterns is ['foo.py', 'test/*.py'] + mocked.assert_called_with( + mock.ANY, + "./some", + "pip install some", + "python some.py", + packaged.DEFAULT_PYTHON_VERSION, + False, + True, + ["foo.py", "test/*.py"], ) diff --git a/tests/end_to_end/packaged_test.py b/tests/end_to_end/packaged_test.py index ec7b083..46d7ec3 100644 --- a/tests/end_to_end/packaged_test.py +++ b/tests/end_to_end/packaged_test.py @@ -18,6 +18,8 @@ def build_package( output_path: str, build_command: str, startup_command: str, + *, + create_pyc: bool = False, ) -> Iterator[None]: """Builds the package, but also delete it afterwards.""" try: @@ -27,6 +29,7 @@ def build_package( build_command, startup_command, python_version=packaged.DEFAULT_PYTHON_VERSION, + pyc=create_pyc, ) yield finally: @@ -50,14 +53,18 @@ def test_just_python() -> None: def test_numpy_pandas() -> None: - """Packages `numpy_pandas` to test packaging the math stack.""" + """ + Packages `numpy_pandas` to test packaging the math stack. + Also pass `--pyc` to ensure that works too. + """ package_path = os.path.join(TEST_PACKAGES, "numpy_pandas") executable_path = "./numpy_pandas.bin" with build_package( package_path, executable_path, "pip install numpy pandas", - "python somefile.py", + "python somefile.pyc", + create_pyc=True, ): assert "0 -2.222222\ndtype: float64" in get_output(executable_path) diff --git a/tests/end_to_end/test_packages/configtest/setup.py b/tests/end_to_end/test_packages/configtest/setup.py index 8bf1ba9..6068493 100644 --- a/tests/end_to_end/test_packages/configtest/setup.py +++ b/tests/end_to_end/test_packages/configtest/setup.py @@ -1,2 +1,3 @@ from setuptools import setup + setup()