Skip to content

Commit

Permalink
Bug fix for local pyapp source (#29)
Browse files Browse the repository at this point in the history
* bug fixes for local pyapp project

* ensure local source is used always if it exists, update pre-commit

* update docs
  • Loading branch information
trappitsch authored Mar 28, 2024
1 parent d74c8b0 commit adf7bfa
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 31 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.2.2
rev: v0.3.4
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.1
rev: v3.15.2
hooks:
- id: pyupgrade
args: [ --py38-plus ]
10 changes: 9 additions & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ If you would like to provide a local `PyApp` source,
you can do so by providing either the path to the local `.tar.gz` source
or to a local folder using the `-p`/`--pyapp-source` argument.
The local source will then be used instead of the latest release from GitHub.
It will be copied into the `build` folder before packaging.
It will be copied into the `build` folder before packaging in the `pyapp-local` folder.
Running only `box package` when a local folder is present will always use this local folder
and not download any releases.
An information about this will be printed.

To re-copy the local source into the `build` folder,
the `box` project needs to be cleaned first.
Then run `box package -p LOCAL_SOURCE` again,
where `LOCAL_SOURCE` is the path to the local source as described above.

## Cleaning your project

Expand Down
82 changes: 55 additions & 27 deletions src/box/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def _get_pyapp(self, local_source: Union[Path, str] = None):
:raises: `click.ClickException` if no pyapp source code is found
"""
tar_name = Path("pyapp-source.tar.gz")
local_source_destination = "pyapp-local"
local_source_exists = False

if isinstance(local_source, str):
local_source = Path(local_source)
Expand All @@ -116,24 +118,34 @@ def _get_pyapp(self, local_source: Union[Path, str] = None):
if local_source.suffix == ".gz" and local_source.is_file():
shutil.copy(local_source, tar_name)
elif local_source.is_dir():
shutil.copytree(
local_source, self._build_dir.joinpath(local_source.name)
)
if Path(local_source_destination).is_dir():
fmt.warning(
"Local source folder already copied. "
"If you want to copy again, please clean the project first."
)
else:
shutil.copytree(
local_source,
self._build_dir.joinpath(local_source_destination),
)
else:
raise click.ClickException(
"Error: invalid local pyapp source code. "
"Please provide a valid folder or a .tar.gz archive."
)

else: # no local source
if not tar_name.is_file():
if Path(local_source_destination).is_dir():
local_source_exists = True
fmt.info("Using existing local pyapp source.")
elif not tar_name.is_file():
urllib.request.urlretrieve(PYAPP_SOURCE, tar_name)

if not tar_name.is_file():
raise click.ClickException(
"Error: no pyapp source code found. "
"Please check your internet connection and try again."
)
if not tar_name.is_file():
raise click.ClickException(
"Error: no pyapp source code found. "
"Please check your internet connection and try again."
)

# check if pyapp source code is already extracted
all_pyapp_folders = []
Expand All @@ -142,30 +154,46 @@ def _get_pyapp(self, local_source: Union[Path, str] = None):
all_pyapp_folders.append(file)

# extract the source code if we didn't just copy a local folder
if not local_source or local_source.suffix == ".gz":
with tarfile.open(tar_name, "r:gz") as tar:
tarfile_members = tar.getmembers()

# only extract if the folder in archive does not already exist
folder_exists = False
new_folder = tarfile_members[0].name
for folder in all_pyapp_folders:
if folder.name == new_folder:
folder_exists = True
break

# extract the source with tarfile package
if not folder_exists:
tar.extractall()
if "pyapp-" in new_folder:
all_pyapp_folders.append(Path(new_folder))
if not local_source_exists:
if not local_source or local_source.suffix == ".gz":
with tarfile.open(tar_name, "r:gz") as tar:
tarfile_members = tar.getmembers()

# only extract if not existing and no local source!
folder_exists = False
new_folder = tarfile_members[0].name
for folder in all_pyapp_folders:
if (
folder.name == new_folder
or folder.name == local_source_destination
):
folder_exists = True
break

# extract the source with tarfile package
if not folder_exists:
tar.extractall()
if "pyapp-" in new_folder:
all_pyapp_folders.append(Path(new_folder))

# if local source, rename the extracted folder
if local_source:
shutil.move(
new_folder,
local_source_destination,
)

# find the name of the pyapp folder and return it
if len(all_pyapp_folders) == 1:
self._pyapp_path = all_pyapp_folders[0].absolute()
elif len(all_pyapp_folders) > 1:
all_pyapp_folders.sort(key=lambda x: x.stem)
self._pyapp_path = all_pyapp_folders[-1].absolute()
if Path(local_source_destination).is_dir():
self._pyapp_path = self._build_dir.joinpath(
local_source_destination
)
else:
self._pyapp_path = all_pyapp_folders[-1].absolute()
fmt.warning(
"Multiple pyapp versions were. "
f"Using {self._pyapp_path.name}. "
Expand Down
27 changes: 26 additions & 1 deletion tests/cli/test_cli_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,32 @@ def test_package_project_local_pyapp(rye_project, mocker, data_dir, pyapp_source
assert result.exit_code == 0
urllib_mock.assert_not_called()

# assert rye_project.joinpath("build/pyapp-v0.14.0/source.txt").is_file()
assert rye_project.joinpath("build/pyapp-local/source.txt").is_file()


def test_package_project_do_not_copy_local_folder_twice(rye_project, data_dir, mocker):
"""Re-copying local folder echos a warning and does nothing."""
pyapp_source_name = "pyapp-v0.14.0"

mocker.patch("box.packager.PackageApp._package_pyapp")
mocker.patch("box.packager.PackageApp.binary_name", return_value="pyapp")

# create dist folder and package
dist_folder = rye_project.joinpath("dist")
dist_folder.mkdir()
dist_folder.joinpath(f"{rye_project.name.replace('-', '_')}-v0.1.0.tar.gz").touch()

runner = CliRunner()
result = runner.invoke(cli, ["package", "-p", data_dir.joinpath(pyapp_source_name)])
result2 = runner.invoke(
cli, ["package", "-p", data_dir.joinpath(pyapp_source_name)]
)

assert result.exit_code == 0

assert rye_project.joinpath("build/pyapp-local/source.txt").is_file()

assert "Local source folder already copied." in result2.output


def test_cargo_not_found(rye_project, mocker):
Expand Down
19 changes: 19 additions & 0 deletions tests/func/test_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,25 @@ def test_get_pyapp_wrong_no_pyapp_folder(rye_project, mocker):
assert "Error: no pyapp source code folder found." in e.value.args[0]


def test_get_pyapp_use_local_folder(rye_project, mocker):
"""Use local source code if it already exists provided."""
urlretrieve_mock = mocker.patch.object(urllib.request, "urlretrieve")
tar_mock = mocker.patch("tarfile.open")

# create a fake source code file - tarfile is mocked
rye_project.joinpath("build/").mkdir()
local_source = rye_project.joinpath("build/pyapp-local")
local_source.mkdir(parents=True)

packager = PackageApp()
packager._get_pyapp()

urlretrieve_mock.assert_not_called()
tar_mock.assert_not_called()

assert local_source == packager._pyapp_path


def test_get_pyapp_local_wrong_file(rye_project):
"""Raise an error if local file is not a .tar.gz."""
rye_project.joinpath("build/").mkdir()
Expand Down

0 comments on commit adf7bfa

Please sign in to comment.