Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added package fam removal func, and tests #1252

Merged
merged 3 commits into from
Mar 22, 2022
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
43 changes: 42 additions & 1 deletion src/rez/cli/rm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ def setup_parser(parser, completions=False):
"-p", "--package",
help="remove the specified package (eg 'foo-1.2.3'). This will work "
"even if the package is currently ignored.")
group.add_argument(
"-f", "--family",
help="remove the specified package family (eg 'python'). This is only "
"supported if the family is empty")
group.add_argument(
"--force-family", action="store_true",
help="like -f, but delete package family even if not empty")
group.add_argument(
"-i", "--ignored-since", type=int, metavar="DAYS",
help="remove all packages that have been ignored for >= DAYS")
Expand All @@ -24,7 +31,8 @@ def setup_parser(parser, completions=False):
help="dry run mode")
parser.add_argument(
"PATH", nargs='?',
help="the repository containing the package(s) to remove.")
help="the repository containing the package(s) or package family "
" to remove.")


def remove_package(opts, parser):
Expand All @@ -46,6 +54,35 @@ def remove_package(opts, parser):
sys.exit(1)


def remove_package_family(opts, parser, force=False):
from rez.vendor.version.requirement import VersionedObject
from rez.package_remove import remove_package_family
from rez.exceptions import PackageRepositoryError

if opts.dry_run:
parser.error("--dry-run is not supported with --family")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dry run could probably at least tell you if it would allow proceeding with an archival if it weren't a dry run?


if not opts.PATH:
parser.error("Must specify PATH with --family")

obj = VersionedObject(opts.family)
if obj.version:
parser.error("Expected package name, not version")

success = False
try:
success = remove_package_family(obj.name, opts.PATH, force=force)
except PackageRepositoryError as e:
print("Error: %s" % e, file=sys.stderr)
sys.exit(1)

if success:
print("Package family removed.")
else:
print("Package family not found.", file=sys.stderr)
sys.exit(1)


def remove_ignored_since(opts, parser):
from rez.package_remove import remove_packages_ignored_since

Expand Down Expand Up @@ -73,6 +110,10 @@ def remove_ignored_since(opts, parser):
def command(opts, parser, extra_arg_groups=None):
if opts.package:
remove_package(opts, parser)
elif opts.family:
remove_package_family(opts, parser)
elif opts.force_family:
remove_package_family(opts, parser, force=True)
elif opts.ignored_since is not None:
remove_ignored_since(opts, parser)
else:
Expand Down
1 change: 1 addition & 0 deletions src/rez/data/tests/packages/py_packages/empty/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A deliberately empty package family.
18 changes: 18 additions & 0 deletions src/rez/package_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
basestring = six.string_types[0]


def remove_package_family(name, path, force=False):
"""Remove a package family from its repository.
A family can only be deleted if it contains no packages, hidden or
otherwise, unless `force` is True.
Args:
name (str): Name of package family.
path (str): Package repository path containing the package family.
force (bool): If True, delete family even if not empty.
Returns:
bool: True if the package family was removed, False if not found.
"""
repo = package_repository_manager.get_repository(path)
return repo.remove_package_family(name, force=force)


def remove_package(name, version, path):
"""Remove a package from its repository.
Expand Down
12 changes: 12 additions & 0 deletions src/rez/package_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,18 @@ def remove_package(self, pkg_name, pkg_version):
"""
raise NotImplementedError

def remove_package_family(self, pkg_name, force=False):
"""Remove an empty package family.

Args:
pkg_name (str): Package name
force (bool): If Trur, delete even if not empty.

Returns:
bool: True if the family was removed, False if it wasn't found.
"""
raise NotImplementedError

def remove_ignored_since(self, days, dry_run=False, verbose=False):
"""Remove packages ignored for >= specified number of days.

Expand Down
18 changes: 18 additions & 0 deletions src/rez/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,24 @@ def get_package(name, version, paths=None):
return None


def get_package_family_from_repository(name, path):
"""Get a package family from a repository.
Args:
name (str): Name of the package, eg 'maya'.
Returns:
`PackageFamily` object, or None if the family was not found.
"""
repo = package_repository_manager.get_repository(path)

family_resource = repo.get_package_family(name)
if family_resource is None:
return None

return PackageFamily(family_resource)


def get_package_from_repository(name, version, path):
"""Get a package from a repository.
Expand Down
54 changes: 51 additions & 3 deletions src/rez/tests/test_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
"""
from rez.packages import iter_package_families, iter_packages, get_package, \
create_package, get_developer_package, get_variant_from_uri, \
get_package_from_uri, get_package_from_repository
get_package_from_uri, get_package_from_repository, \
get_package_family_from_repository
from rez.exceptions import PackageRepositoryError
from rez.package_py_utils import expand_requirement
from rez.package_resources import package_release_keys
from rez.package_move import move_package
from rez.package_remove import remove_package, remove_packages_ignored_since
from rez.package_remove import remove_package, remove_packages_ignored_since, \
remove_package_family
from rez.package_repository import package_repository_manager
from rez.tests.util import TestBase, TempdirMixin
from rez.utils.formatting import PackageRequest
Expand Down Expand Up @@ -58,7 +61,10 @@
])


ALL_FAMILIES = set(x.split('-')[0] for x in ALL_PACKAGES)
ALL_FAMILIES = set(
[x.split('-')[0] for x in ALL_PACKAGES]
+ ["empty"]
)


def _to_names(it):
Expand Down Expand Up @@ -544,6 +550,48 @@ def test_package_remove(self):
i = repo.unignore_package(pkg_name, pkg_version)
self.assertEqual(i, -1)

def test_package_family_remove(self):
"""Test package family remove."""
pkg_name = "pydad"

# copy packages to a temp repo
repo_path = os.path.join(self.root, "tmp6_packages")
shutil.copytree(self.solver_packages_path, repo_path)

# verify that source fam exists
src_fam = get_package_family_from_repository(pkg_name, repo_path)
self.assertNotEqual(src_fam, None)

# remove it, will fail as not empty
with self.assertRaises(PackageRepositoryError):
remove_package_family(pkg_name, repo_path)

# remove all pydad packages
versions = [pkg.version for pkg in src_fam.iter_packages()]
for version in versions:
self.assertTrue(remove_package(pkg_name, version, repo_path))

# now it should remove successfully
self.assertTrue(remove_package_family(pkg_name, repo_path))

# verify that the fam no longer exists
self.assertFalse(remove_package_family(pkg_name, repo_path))

pkg_name2 = "pymum"

# get another fam
src_fam2 = get_package_family_from_repository(pkg_name2, repo_path)
self.assertNotEqual(src_fam2, None)

# force remove another fam
self.assertTrue(remove_package_family(pkg_name2, repo_path, force=True))

# verify that the fam no longer exists
self.assertEqual(
get_package_family_from_repository(pkg_name2, repo_path),
None
)

def test_remove_packages_ignored_since(self):
pkg_name = "pydad"
pkg_version = Version("2")
Expand Down
34 changes: 33 additions & 1 deletion src/rezplugins/package_repository/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,36 @@ def remove_package(self, pkg_name, pkg_version):

return True

def remove_package_family(self, pkg_name, force=False):
# get a non-cached copy and see if fam exists
repo_copy = self._copy(
disable_pkg_ignore=True,
disable_memcache=True
)

fam = repo_copy.get_package_family(pkg_name)
if fam is None:
return False

# check that the pkg fam is empty
if not force:
empty = True
for _ in repo_copy.iter_packages(fam):
empty = False
break

if not empty:
raise PackageRepositoryError(
"Cannot remove non-empty package family %r" % pkg_name
)

# delete the fam dir
fam_dir = os.path.join(self.location, pkg_name)
shutil.rmtree(fam_dir)

self._on_changed(pkg_name)
return True

def remove_ignored_since(self, days, dry_run=False, verbose=False):
now = int(time.time())
num_removed = 0
Expand Down Expand Up @@ -1464,7 +1494,9 @@ def _on_changed(self, pkg_name):
# stats are required to determine if a resolve cache entry is stale.
#
family_path = os.path.join(self.location, pkg_name)
os.utime(family_path, None)

if os.path.exists(family_path):
os.utime(family_path, None)

# clear internal caches, otherwise change may not be visible
self.clear_caches()
Expand Down