Skip to content

Commit

Permalink
Switch to PEP 691 for version and release information
Browse files Browse the repository at this point in the history
Add testing on Pyton 3.11 and 3.12, fix README
Improve logging consistency
Print versions to be deleted prior to authentication
Move timeout pause after the authentication immediately
prior to destructive action
Increase timeout pause from 3 to 5s
Remove Python 3.6 support as it's gone from testing images

fixes #21
  • Loading branch information
arcivanov committed Feb 27, 2024
1 parent dfac33a commit 7095030
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 22 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/pypi-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ jobs:
os:
- ubuntu-latest
python-version:
- '3.11.0-rc.1'
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
- '3.7'
- '3.6'
env:
DEPLOY_PYTHONS: "3.10"
DEPLOY_PYTHONS: "3.12"
DEPLOY_OSES: "Linux"
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![PyPI Cleanup Version](https://img.shields.io/pypi/v/pypi-cleanup?logo=pypi)](https://pypi.org/project/pypi-cleanup/)
[![PyPI Cleanup Python Versions](https://img.shields.io/pypi/pyversions/pypi-cleanup?logo=pypi)](https://pypi.org/project/pypi-cleanup/)
[![Build Status](https://img.shields.io/github/workflow/status/arcivanov/pypi-cleanup/pypi-cleanup/master)](https://github.com/arcivanov/pypi-cleanup/actions/workflows/pypi-cleanup.yml)
[![Build Status](https://img.shields.io/github/actions/workflow/status/arcivanov/pypi-cleanup/pypi-cleanup.yml?branch=master)](https://github.com/arcivanov/pypi-cleanup/actions/workflows/pypi-cleanup.yml)
[![PyPI Cleanup Downloads Per Day](https://img.shields.io/pypi/dd/pypi-cleanup?logo=pypi)](https://pypi.org/project/pypi-cleanup/)
[![PyPI Cleanup Downloads Per Week](https://img.shields.io/pypi/dw/pypi-cleanup?logo=pypi)](https://pypi.org/project/pypi-cleanup/)
[![PyPI Cleanup Downloads Per Month](https://img.shields.io/pypi/dm/pypi-cleanup?logo=pypi)](https://pypi.org/project/pypi-cleanup/)
Expand Down
5 changes: 3 additions & 2 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"Documentation": "https://github.com/arcivanov/pypi-cleanup"
}

requires_python = ">=3.6"
requires_python = ">=3.7"

default_task = ["analyze", "publish"]

Expand Down Expand Up @@ -67,11 +67,12 @@ def set_properties(project):
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
Expand Down
44 changes: 28 additions & 16 deletions src/main/python/pypi_cleanup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def __init__(self, url, username, package, do_it, patterns, verbose, days, **_):
self.package = package
self.patterns = patterns or DEFAULT_PATTERNS
self.verbose = verbose
self.date = datetime.datetime.now() - datetime.timedelta(days=days)
self.date = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=days)

def run(self):
csrf = None
Expand All @@ -89,29 +89,31 @@ def run(self):
logging.root.setLevel(logging.DEBUG)

if self.do_it:
logging.warning("!!! WILL ACTUALLY DELETE THINGS !!!")
logging.warning("Will sleep for 3 seconds - Ctrl-C to abort!")
time.sleep(3.0)
logging.warning("!!! POSSIBLE DESTRUCTIVE OPERATION !!!")
else:
logging.info("Running in DRY RUN mode")

logging.info(f"Will use the following patterns {self.patterns} on package {self.package}")
logging.info(f"Will use the following patterns {self.patterns} on package {self.package!r}")

with requests.Session() as s:
with s.get(f"{self.url}/pypi/{self.package}/json") as r:
with s.get(f"{self.url}/simple/{self.package}/",
headers={"Accept": "application/vnd.pypi.simple.v1+json"}) as r:
try:
r.raise_for_status()
except RequestException as e:
logging.error(f"Unable to find package {repr(self.package)}", exc_info=e)
logging.error(f"Unable to find package {self.package!r}", exc_info=e)
return 1

project_info = r.json()
releases_by_date = {}
for release, files in r.json()["releases"].items():
releases_by_date[release] = max([datetime.datetime.strptime(f["upload_time"],
'%Y-%m-%dT%H:%M:%S') for f in files])
for version in project_info["versions"]:
releases_by_date[version] = max(
[datetime.datetime.strptime(f["upload-time"], '%Y-%m-%dT%H:%M:%S.%f%z')
for f in project_info["files"]
if f["filename"].lower().startswith(f"{self.package}-{version}")])

if not releases_by_date:
logging.info(f"No releases for package {self.package} have been found")
logging.info(f"No releases for package {self.package!r} have been found")
return

pkg_vers = list(filter(lambda k:
Expand All @@ -120,14 +122,15 @@ def run(self):
releases_by_date.keys()))

if not pkg_vers:
logging.info(f"No releases were found matching specified patterns and dates in package {self.package}")
logging.info(f"No releases were found matching specified patterns "
f"and dates in package {self.package!r}")
return

if set(pkg_vers) == set(releases_by_date.keys()):
print(dedent(f"""
WARNING:
\tYou have selected the following patterns: {self.patterns}
\tThese patterns would delete all available released versions of `{self.package}`.
\tThese patterns would delete all available released versions of {self.package!r}.
\tThis will render your project/package permanently inaccessible.
\tSince the costs of an error are too high I'm refusing to do this.
\tGoodbye.
Expand All @@ -136,6 +139,10 @@ def run(self):
if not self.do_it:
return 3

logging.info("Found the following releases to delete:")
for pkg_ver in pkg_vers:
logging.info(f" {pkg_ver}")

password = os.getenv("PYPI_CLEANUP_PASSWORD")

if self.username is None:
Expand Down Expand Up @@ -201,9 +208,14 @@ def run(self):
logging.error(f"Authentication code {auth_code} is invalid")
return 1

if self.do_it:
logging.warning("!!! WILL ACTUALLY DELETE THINGS - LAST CHANCE TO CHANGE YOUR MIND !!!")
logging.warning("Sleeping for 5 seconds - Ctrl-C to abort!")
time.sleep(5.0)

for pkg_ver in pkg_vers:
if self.do_it:
logging.info(f"Deleting {self.package} version {pkg_ver}")
logging.info(f"Deleting {self.package!r} version {pkg_ver}")
form_action = f"/manage/project/{self.package}/release/{pkg_ver}/"
form_url = f"{self.url}{form_action}"
with s.get(form_url) as r:
Expand All @@ -222,9 +234,9 @@ def run(self):
headers={"referer": referer}) as r:
r.raise_for_status()

logging.info(f"Deleted {self.package} version {pkg_ver}")
logging.info(f"Deleted {self.package!r} version {pkg_ver}")
else:
logging.info(f"Would be deleting {self.package} version {pkg_ver}, but not doing it!")
logging.info(f"Would be deleting {self.package!r} version {pkg_ver}, but not doing it!")


def main():
Expand Down

0 comments on commit 7095030

Please sign in to comment.