Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions
7a026f88e2 chore(rc-changes): update CHANGES.rst 2024-03-01 00:34:55 +00:00
17 changed files with 114 additions and 300 deletions

@ -12,26 +12,13 @@ on:
# Allowing to run on fork and other pull requests
pull_request:
env:
LAST_SUPPORTED_PYTHON: "3.12"
jobs:
test-python:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# make sure to align the `python-version`s in the Matrix with env.LAST_SUPPORTED_PYTHON
python-version: [
"3.7",
"3.8",
"3.9",
"3.10",
"pypy3.9",
"3.11",
"3.12",
"3.x", # make sure to test the current stable Python version
]
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy3.9", "3.11"] # "3.12-dev"
steps:
- uses: actions/checkout@v3
@ -56,7 +43,7 @@ jobs:
uses: actions/setup-python@v4
with:
# Use the current version of Python
python-version: ${{ env.LAST_SUPPORTED_PYTHON }}
python-version: "3.x"
- name: Install dependencies
run: |
pip install -r "requirements/dev.pip"
@ -103,7 +90,7 @@ jobs:
uses: actions/setup-python@v4
with:
# Use the current version of Python
python-version: ${{ env.LAST_SUPPORTED_PYTHON }}
python-version: "3.x"
- name: Install test dependencies
run: pip install -r "requirements/test.pip"
- name: Install package
@ -129,15 +116,14 @@ jobs:
runs-on: ubuntu-latest
needs:
- "tests"
# only if a tag is pushed
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@master
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.LAST_SUPPORTED_PYTHON }}
- name: Install dev dependencies
run: pip install -r "requirements/dev.pip"
python-version: 3.x
- name: Build distribution _wheel_.
run: |
@ -145,8 +131,6 @@ jobs:
- name: Publish distribution 📦 to PyPI.
uses: pypa/gh-action-pypi-publish@release/v1
# Push to PyPi only if a tag is pushed
if: startsWith(github.event.ref, 'refs/tags/v')
with:
password: ${{ secrets.PYPI_API_TOKEN }}
print-hash: true
@ -170,7 +154,7 @@ jobs:
- uses: "actions/setup-python@v4"
with:
python-version: ${{ env.LAST_SUPPORTED_PYTHON }}
python-version: "3.x"
# This script prints a JSON array of needed docker tags, depending on the
# ref. That array is then used to construct the matrix of the
@ -211,31 +195,27 @@ jobs:
${{ runner.os }}-buildx-
- name: "Login to Docker Hub"
uses: "docker/login-action@v3"
uses: "docker/login-action@v1"
with:
username: "${{ secrets.DOCKER_HUB_USER }}"
password: "${{ secrets.DOCKER_HUB_TOKEN }}"
- name: "Login to GitHub Container Registry"
uses: "docker/login-action@v3"
uses: "docker/login-action@v2"
with:
registry: "ghcr.io"
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: "Set up QEMU"
uses: "docker/setup-qemu-action@v3"
- name: "Set up Docker Buildx"
id: "buildx"
uses: "docker/setup-buildx-action@v3"
uses: "docker/setup-buildx-action@v1"
- name: "Build and push"
id: "docker_build"
uses: "docker/build-push-action@v5"
uses: "docker/build-push-action@v2"
with:
context: "./"
platforms: linux/amd64,linux/arm64
file: "./Dockerfile"
builder: "${{ steps.buildx.outputs.name }}"
push: true

@ -22,16 +22,14 @@ jobs:
if: ${{ github.ref_name == 'master' }}
runs-on: ubuntu-latest
env:
CHANGE_FILE: CHANGES.rst
CHANGES_FILE: CHANGES.rst
EXPECTED_DIFF_COUNT: 1
steps:
- uses: actions/checkout@v3
- id: get-version
run: |
CHANGE_FILE=${{ env.CHANGE_FILE }}
LAST_VERSION=$(grep -m1 -E ' \([0-9]+-[0-9]+-[0-9]+\)$' ${CHANGE_FILE} | awk '{ print $1 }')
echo "👀 Version detected: ${LAST_VERSION}"
echo "LAST_VERSION=${LAST_VERSION}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-python@v4
@ -47,7 +45,7 @@ jobs:
run: |
echo ${{ inputs.dryrun && '💡 Running in dry-run mode' || 'Preparing release...' }}
CHANGE_FILE=${{ env.CHANGE_FILE }}
CHANGE_FILE=${{ env.CHANGES_FILE }}
LAST_VERSION=${{ steps.get-version.outputs.LAST_VERSION }}
git config user.name github-actions
git config user.email github-actions@github.com

@ -4,21 +4,11 @@ Changelog
3.0.0 (tbd)
-----------
2.1.1 (2024-04-24)
2.0.2rc03-01-2024 (__rc__)
--------------------------
- 31c9cf1 FIX: deprecated `setuptools.py` when building in `package.sh` (#568)
- 2619c17 FIX: use the right env variables in `release-tag` workflow (#569)
2.1.0 (2024-04-24)
--------------------------
- d588913 ENH: Bump github action versions and add multiarch support (#553)
- a558dbc ENH: Handle tar.xz archives (#536)
- 2f0a56c FIX: support Python 3.12 (#539)
- 84bf12c MAINT: make the last supported python version explicit in `ci.yaml` (#558)
- 946fbfe MAINT: Update setuptools requirement from <62.0.0,>=40.0 to >=40.0,<70.0.0 in /requirements (#557)
- 50c7a78 MAINT: add tar xz test case (#538)
- 50c7a78 chore: add tar xz test case (#538)
- a558dbc Handle tar.xz archives (#536)
2.0.1 (2023-10-01)
--------------------------

110
README.md

@ -9,14 +9,14 @@
| name | description |
| :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Version | 2.1.1 |
| Date: | 2024-04-25 |
| Source | <https://github.com/pypiserver/pypiserver> |
| PyPI | <https://pypi.org/project/pypiserver/> |
| Tests | <https://github.com/pypiserver/pypiserver/actions> |
| Version | 2.0.1 |
| Date: | 2023-10-01 |
| Source | https://github.com/pypiserver/pypiserver |
| PyPI | https://pypi.org/project/pypiserver/ |
| Tests | https://github.com/pypiserver/pypiserver/actions |
| Maintainers | Kostis Anagnostopoulos <ankostis@gmail.com>, Matthew Planchard <mplanchard@gmail.com>, Dmitrii Orlov <dmtree.dev@yahoo.com>, **Someone new?** We are looking for new maintainers! [#397](https://github.com/pypiserver/pypiserver/issues/397) |
| License | zlib/libpng + MIT |
| Community | <https://pypiserver.zulipchat.com> |
| Community | https://pypiserver.zulipchat.com |
Chat with us on [Zulip](https://pypiserver.zulipchat.com)!
@ -44,47 +44,47 @@ making it much easier to get a running index server.
Table of Contents
- [pypiserver](#pypiserver)
- [Quickstart Installation and Usage](#quickstart-installation-and-usage)
- [More details about pypi server run](#more-details-about-pypi-server-run)
- [More details about pypi-server update](#more-details-about-pypi-server-update)
- [Client-Side Configurations](#client-side-configurations)
- [Configuring pip](#configuring-pip)
- [Configuring easy_install](#configuring-easy_install)
- [Uploading Packages Remotely](#uploading-packages-remotely)
- [Apache Like Authentication (htpasswd)](#apache-like-authentication-htpasswd)
- [Upload with setuptools](#upload-with-setuptools)
- [Upload with twine](#upload-with-twine)
- [Using the Docker Image](#using-the-docker-image)
- [Alternative Installation Methods](#alternative-installation-methods)
- [Installing the Very Latest Version](#installing-the-very-latest-version)
- [Recipes](#recipes)
- [Managing the Package Directory](#managing-the-package-directory)
- [Serving Thousands of Packages](#serving-thousands-of-packages)
- [Managing Automated Startup](#managing-automated-startup)
- [Running As a systemd Service](#running-as-a-systemd-service)
- [Launching through supervisor](#launching-through-supervisor)
- [Running As a service with NSSM](#running-as-a-service-with-nssm)
- [Using a Different WSGI Server](#using-a-different-wsgi-server)
- [Apache](#apache)
- [gunicorn](#gunicorn)
- [paste](#paste)
- [Behind a Reverse Proxy](#behind-a-reverse-proxy)
- [Nginx](#nginx)
- [Supporting HTTPS](#supporting-https)
- [Traefik](#traefik)
- [Utilizing the API](#utilizing-the-api)
- [Using Ad-Hoc Authentication Providers](#using-ad-hoc-authentication-providers)
- [Use with MicroPython](#use-with-micropython)
- [Custom Health Check Endpoint](#custom-health-check-endpoint)
- [Configure a custom health endpoint by CLI arguments](#configure-a-custom-health-endpoint-by-cli-arguments)
- [Configure a custom health endpoint by script](#configure-a-custom-health-endpoint-by-script)
- [Sources](#sources)
- [Known Limitations](#known-limitations)
- [Similar Projects](#similar-projects)
- [Unmaintained or archived](#unmaintained-or-archived)
- [Related Software](#related-software)
- [Licensing](#licensing)
- [pypiserver - minimal PyPI server for use with pip/easy_install](#pypiserver---minimal-pypi-server-for-use-with-pipeasy_install)
- [Quickstart Installation and Usage](#Quickstart-Installation-and-Usage)
- [More details about pypi-server run](#More-details-about-pypi-server-run)
- [More details about pypi-server update](#More-details-about-pypi-server-update)
- [Client-Side Configurations](#Client-Side-Configurations)
- [Configuring pip](#Configuring-pip)
- [Configuring easy_install](#Configuring-easy_install)
- [Uploading Packages Remotely](#Uploading-Packages-Remotely)
- [Apache like Authentication ( htpasswd )](#Apache-like-Authentication)
- [Upload with setuptools](#Upload-with-setuptools)
- [Upload with twine](#Upload-with-twine)
- [Using the Docker Image](#Using-the-Docker-Image)
- [Alternative Installation methods](#Alternative-Installation-methods)
- [Installing the Very Latest Version](#Installing-the-Very-Latest-Version)
- [Recipes](#Recipes)
- [Managing the Package Directory](#Managing-the-Package-Directory)
- [Serving Thousands of Packages](#Serving-Thousands-of-Packages)
- [Managing Automated Startup](#Managing-Automated-Startup)
- [Running as a systemd service](#Running-as-a-systemd-service)
- [Launching through supervisor](#Launching-through-supervisor)
- [Running as a service with NSSM (Windows)](#Running-as-a-service-with-NSSM)
- [Using a Different WSGI Server](#Using-a-Different-WSGI-Server)
- [Apache](#Apache)
- [Gunicorn](#Gunicorn)
- [Paste](#Paste)
- [Behind a Reverse Proxy](#Behind-a-Reverse-Proxy)
- [Nginx](#Nginx)
- [Supporting HTTPS](#Supporting-HTTPS)
- [Traefik](#Traefik)
- [Utilizing the API](#Utilizing-the-API)
- [Using Ad-Hoc Authentication Providers](#Using-Ad-Hoc-Authentication-Providers)
- [Use with MicroPython](#Use-with-MicroPython)
- [Custom Health Check Endpoint](#Custom-Health-Check-Endpoint)
- [Configure a custom health check by CLI arguments](#Configure-a-custom-health-check-by-CLI-arguments)
- [Configure a custom health endpoint by script](#Configure-a-custom-health-endpoint-by-script)
- [Sources](#Sources)
- [Known Limitations](#known-limitations)
- [Similar Projects](#similar-projects)
- [Unmaintained or archived](#unmaintained-or-archived)
- [Related Projects](#related-projects)
- [License](#license)
## Quickstart Installation and Usage
@ -134,7 +134,7 @@ See also [Alternative Installation methods](<>)
# Note that pip search does not currently work with the /simple/ endpoint.
```
See also [Client-side configurations](#client-side-configurations) for avoiding tedious typing.
See also [Client-side configurations](#Client-Side-Configurations) for avoiding tedious typing.
4. Enter **pypi-server -h** in the cmd-line to print a detailed usage message
@ -461,7 +461,7 @@ Please see `Using Ad-hoc authentication providers`\_ for more information.
password: <some_passwd>
```
1. Then from within the directory of the python-project you wish to upload,
2. Then from within the directory of the python-project you wish to upload,
issue this command:
```shell
@ -693,7 +693,7 @@ Adjusting the paths and adding this file as **pypiserver.service** into your
**systemctl**, e.g. **systemctl start pypiserver**.
More useful information about *systemd* can be found at
<https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units>
https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
#### Launching through supervisor
@ -716,7 +716,7 @@ From there, the process can be managed via **supervisord** using **supervisorctl
#### Running As a service with NSSM
For Windows download NSSM from <https://nssm.cc> unzip to a desired location such as Program Files. Decide whether you are going
For Windows download NSSM from https://nssm.cc unzip to a desired location such as Program Files. Decide whether you are going
to use win32 or win64, and add that exe to environment PATH.
Create a start_pypiserver.bat
@ -762,7 +762,7 @@ Other useful commands
```
For detailed information please visit <https://nssm.cc>
For detailed information please visit https://nssm.cc
### Using a Different WSGI Server
@ -1057,7 +1057,7 @@ these steps:
3. Invoke the python-script to start-up **pypiserver**
```shell
python pypiserver-start.py
$ python pypiserver-start.py
```
Note
@ -1092,7 +1092,7 @@ Installing packages from the REPL of an embedded device works in this way:
upip.install("micropython-foobar")
```
Further information on micropython-packaging can be found here: <https://docs.micropython.org/en/latest/reference/packages.html>
Further information on micropython-packaging can be found here: https://docs.micropython.org/en/latest/reference/packages.html
### Custom Health Check Endpoint
@ -1127,7 +1127,7 @@ Run pypiserver with **--health-endpoint** argument:
bottle.run(app=app, host="0.0.0.0", port=8080, server="auto")
````
Try **curl <http://localhost:8080/action/health>**
Try **curl http://localhost:8080/action/health**
## Sources

@ -6,4 +6,4 @@ my_dir=`dirname "$0"`
cd $my_dir/..
rm -r build/* dist/* || echo "no build/* or dist/* folder is found"
python3 -m build
python3 setup.py bdist_wheel sdist

@ -396,7 +396,6 @@ class TestBasics:
"-m",
"pip",
"install",
"--force-reinstall",
"--index-url",
f"http://localhost:{container.port}/simple",
TEST_DEMO_PIP_PACKAGE,
@ -563,7 +562,6 @@ class TestAuthed:
"-m",
"pip",
"install",
"--force-reinstall",
"--index-url",
f"http://a:a@localhost:{self.HOST_PORT}/simple",
TEST_DEMO_PIP_PACKAGE,
@ -583,10 +581,9 @@ class TestAuthed:
"-m",
"pip",
"install",
"--force-reinstall",
"--no-cache",
"--index-url",
f"http://foo:bar@localhost:{self.HOST_PORT}/simple",
f"http://localhost:{self.HOST_PORT}/simple",
TEST_DEMO_PIP_PACKAGE,
check_code=lambda c: c != 0,
)

@ -7,9 +7,9 @@ import typing as t
from pypiserver.bottle import Bottle
from pypiserver.config import Config, RunConfig, strtobool
version = __version__ = "2.1.1"
version = __version__ = "2.0.1"
__version_info__ = tuple(_re.split("[.-]", __version__))
__updated__ = "2024-04-25 01:23:25"
__updated__ = "2023-10-01 16:14:10"
__title__ = "pypiserver"
__summary__ = "A minimal PyPI server for use with pip/easy_install."

@ -14,7 +14,6 @@ from urllib.parse import urljoin, urlparse
from pypiserver.config import RunConfig
from . import __version__
from . import core
from . import mirror_cache
from .bottle import (
static_file,
redirect,
@ -287,9 +286,7 @@ def simple(project):
key=lambda x: (x.parsed_version, x.relfn),
)
if not packages:
if config.mirror:
return mirror_cache.MirrorCache.add(project=project, config=config)
elif not config.disable_fallback:
if not config.disable_fallback:
return redirect(f"{config.fallback_url.rstrip('/')}/{project}/")
return HTTPError(404, f"Not Found ({normalized} does not exist)\n\n")
@ -367,8 +364,7 @@ def server_static(filename):
"Cache-Control", f"public, max-age={config.cache_control}"
)
return response
if config.mirror and mirror_cache.MirrorCache.has_project(filename):
return mirror_cache.MirrorCache.get_static_file(filename=filename, config=config)
return HTTPError(404, f"Not Found ({filename} does not exist)\n\n")

@ -43,26 +43,9 @@ import re
import sys
import textwrap
import typing as t
from distutils.util import strtobool as strtoint
try:
# `importlib_resources` is required for Python versions below 3.12
# See more in the package docs: https://pypi.org/project/importlib-resources/
try:
from importlib_resources import files as import_files
except ImportError:
from importlib.resources import files as import_files
def get_resource_bytes(package: str, resource: str) -> bytes:
ref = import_files(package).joinpath(resource)
return ref.read_bytes()
except ImportError:
# The `pkg_resources` is deprecated in Python 3.12
import pkg_resources
def get_resource_bytes(package: str, resource: str) -> bytes:
return pkg_resources.resource_string(package, resource)
import pkg_resources
from pypiserver.backend import (
SimpleFileBackend,
@ -80,29 +63,10 @@ except ImportError:
HtpasswdFile = None
def legacy_strtoint(val: str) -> int:
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
The "strtobool" function in distutils does a nice job at parsing strings,
but returns an integer. This just wraps it in a boolean call so that we
get a bool.
Borrowed from deprecated distutils.
"""
val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return 1
elif val in ("n", "no", "f", "false", "off", "0"):
return 0
else:
raise ValueError("invalid truth value {!r}".format(val))
strtobool: t.Callable[[str], bool] = lambda val: bool(legacy_strtoint(val))
# The "strtobool" function in distutils does a nice job at parsing strings,
# but returns an integer. This just wraps it in a boolean call so that we
# get a bool.
strtobool: t.Callable[[str], bool] = lambda val: bool(strtoint(val))
# Specify defaults here so that we can use them in tests &c. and not need
@ -187,7 +151,9 @@ def health_endpoint_arg(arg: str) -> str:
def html_file_arg(arg: t.Optional[str]) -> str:
"""Parse the provided HTML file and return its contents."""
if arg is None or arg == "pypiserver/welcome.html":
return get_resource_bytes(__name__, "welcome.html").decode("utf-8")
return pkg_resources.resource_string(__name__, "welcome.html").decode(
"utf-8"
)
with open(arg, "r", encoding="utf-8") as f:
msg = f.read()
return msg
@ -517,14 +483,6 @@ def get_parser() -> argparse.ArgumentParser:
"to '%%s' to see them all."
),
)
run_parser.add_argument(
"--mirror",
default=0,
action="count",
help=(
"Mirror packages to local disk"
),
)
update_parser = subparsers.add_parser(
"update",
@ -728,7 +686,6 @@ class RunConfig(_ConfigCommon):
overwrite: bool,
welcome_msg: str,
cache_control: t.Optional[int],
mirror: bool,
log_req_frmt: str,
log_res_frmt: str,
log_err_frmt: str,
@ -754,7 +711,6 @@ class RunConfig(_ConfigCommon):
# Derived properties
self._derived_properties = self._derived_properties + ("auther",)
self.auther = self.get_auther(auther)
self.mirror = mirror
@classmethod
def kwargs_from_namespace(
@ -774,7 +730,6 @@ class RunConfig(_ConfigCommon):
"overwrite": namespace.overwrite,
"welcome_msg": namespace.welcome,
"cache_control": namespace.cache_control,
"mirror": namespace.mirror,
"log_req_frmt": namespace.log_req_frmt,
"log_res_frmt": namespace.log_res_frmt,
"log_err_frmt": namespace.log_err_frmt,

@ -5,8 +5,7 @@ from __future__ import absolute_import, print_function, unicode_literals
import itertools
import os
import sys
from packaging.version import parse as packaging_parse
from distutils.version import LooseVersion
from pathlib import Path
from subprocess import call
from xmlrpc.client import Server
@ -113,14 +112,12 @@ class PipCmd:
@staticmethod
def update_root(pip_version):
"""Yield an appropriate root command depending on pip version.
Use `pip install` for `pip` 9 or lower, and `pip download` otherwise.
"""
legacy_pip = packaging_parse(pip_version).major < 10
pip_command = "install" if legacy_pip else "download"
for part in ("pip", "-q", pip_command):
"""Yield an appropriate root command depending on pip version."""
# legacy_pip = StrictVersion(pip_version) < StrictVersion('10.0')
legacy_pip = LooseVersion(pip_version) < LooseVersion("10.0")
for part in ("pip", "-q"):
yield part
yield "install" if legacy_pip else "download"
@staticmethod
def update(

@ -1,91 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
from collections import OrderedDict
from pypiserver.bottle import HTTPError, redirect
from pypiserver.config import RunConfig
log = logging.getLogger(__name__)
try:
import requests
from bs4 import BeautifulSoup
import_ok = True
except ImportError:
import_ok = False
logging.error("mirror_cache import dependencies error")
class CacheElement:
def __init__(self, project: str):
self.project = project
self.html = ""
self.cache = dict()
def add(self, href: str):
targz = href.split("/")[-1]
pkg_name = targz.split("#")[0]
self.cache[f"{self.project}/{pkg_name}"] = href
return f"/packages/{self.project}/{targz}"
class MirrorCache:
cache: OrderedDict[str, CacheElement] = dict()
cache_limit = 10
@classmethod
def add(cls, project: str, config: RunConfig) -> str:
if not import_ok:
return redirect(f"{config.fallback_url.rstrip('/')}/{project}/")
if project in cls.cache:
log.info(f"mirror_cache serve html from cache {project}")
return cls.cache[project].html
element = CacheElement(project=project)
resp = requests.get(f"{config.fallback_url.rstrip('/')}/{project}/")
soup = BeautifulSoup(resp.content, "html.parser")
links = soup.find_all("a")
for link in links:
# new href with mapping to old href for later
new_href = element.add(href=link["href"])
# create new link
new_link = soup.new_tag("a")
new_link.string = link.text.strip()
new_link["href"] = new_href
link.replace_with(new_link)
element.html = str(soup)
cls.cache[project] = element
log.info(f"mirror_cache add project '{project}' to cache")
# purge
if len(cls.cache) > cls.cache_limit:
item = cls.cache.popitem(last=False)
log.info(f"mirror_cache limit '{cls.cache_limit}' exceeded, purged last item - {item}")
return element.html
@classmethod
def has_project(cls, filename):
project = filename.split("/")[0]
return project in cls.cache
@classmethod
def get_static_file(cls, filename, config: RunConfig):
if not import_ok:
return HTTPError(404, f"Not Found ({filename} does not exist)\n\n")
project = filename.split("/")[0]
element = cls.cache[project]
if filename in element.cache:
href = element.cache[filename]
resp = requests.get(href)
cls.add_to_cache(filename=filename, resp=resp, config=config)
return resp
log.info(f"mirror_cache not found in cache {filename} ")
return HTTPError(404, f"Not Found ({filename} does not exist)\n\n")
@classmethod
def add_to_cache(cls, filename: str, resp: requests.Response, config: RunConfig):
project = filename.split("/")[0]
os.makedirs(os.path.join(config.package_root, project), exist_ok=True)
log.info(f"mirror_cache add file '{filename}' to cache")
with open(f"{config.package_root}/{filename}", "wb+") as f:
f.write(resp.content)

@ -18,7 +18,6 @@ with the following keyword arguments
In the future, the plugin callable may be called with additional keyword
arguments, so a plugin should accept a **kwargs variadic keyword argument.
"""
from pypiserver.backend import SimpleFileBackend, CachingFileBackend
from pypiserver import get_file_backend

@ -1,2 +0,0 @@
beautifulsoup4==4.12.3
requests==2.31.0

@ -4,12 +4,11 @@ pip
passlib>=1.6
pytest>=6.2.2
pytest-cov
setuptools>=40.0,<70.0.0
setuptools>=40.0,<62.0.0
tox
twine
webtest
wheel>=0.25.0
build>=1.2.0; python_version >= '3.8'
mdformat-gfm
mdformat-frontmatter
mdformat-footnote

@ -11,19 +11,10 @@ tests_require = [
"twine",
"passlib>=1.6",
"webtest",
"build>=1.2.0;python_version>='3.8'",
]
setup_requires = [
"setuptools",
"setuptools-git>=0.3",
"wheel>=0.25.0",
]
install_requires = [
"pip>=7",
"packaging>=23.2",
"importlib_resources;python_version>'3.8' and python_version<'3.12'",
]
setup_requires = ["setuptools", "setuptools-git >= 0.3", "wheel >= 0.25.0"]
install_requires = ["pip>=7"]
def read_file(rel_path: str):

@ -1,7 +1,6 @@
"""
Test module for app initialization
"""
# Standard library imports
import logging
import pathlib

@ -16,7 +16,6 @@ import itertools
import os
import shutil
import socket
import re
import sys
import time
import typing as t
@ -100,14 +99,10 @@ def build_url(port: t.Union[int, str], user: str = "", pswd: str = "") -> str:
return f"http://{auth}localhost:{port}"
def run_setup_py(path: Path, arguments: str) -> int:
def run_setup_py(path: Path, arguments: str):
return os.system(f"{sys.executable} {path / 'setup.py'} {arguments}")
def run_py_build(srcdir: Path, flags: str) -> int:
return os.system(f"{sys.executable} -m build {flags} {srcdir}")
# A test-distribution to check if
# bottle supports uploading 100's of packages,
# see: https://github.com/pypiserver/pypiserver/issues/82
@ -145,13 +140,8 @@ def server_root(tmp_path_factory):
@pytest.fixture(scope="module")
def wheel_file(project, tmp_path_factory):
distdir = tmp_path_factory.mktemp("dist")
if re.match("^3\.7", sys.version):
assert run_setup_py(project, f"bdist_wheel -d {distdir}") == 0
else:
assert run_py_build(project, f"--wheel --outdir {distdir}") == 0
wheels = list(distdir.glob("centodeps*.whl"))
assert len(wheels) > 0
return wheels[0]
assert run_setup_py(project, f"bdist_wheel -d {distdir}") == 0
return list(distdir.glob("centodeps*.whl"))[0]
@pytest.fixture()
@ -327,6 +317,22 @@ def test_pip_install_authed_succeeds(authed_server, hosted_wheel_file, pipdir):
assert pipdir.joinpath(hosted_wheel_file.name).is_file()
@pytest.mark.parametrize("pkg_frmt", ["bdist", "bdist_wheel"])
@pytest.mark.parametrize(["server_fixture", "pypirc_fixture"], all_servers)
def test_setuptools_upload(
server_fixture, pypirc_fixture, project, pkg_frmt, server_root, request
):
request.getfixturevalue(server_fixture)
request.getfixturevalue(pypirc_fixture)
assert len(list(server_root.iterdir())) == 0
for i in range(5):
print(f"++Attempt #{i}")
assert run_setup_py(project, f"-vvv {pkg_frmt} upload -r test") == 0
assert len(list(server_root.iterdir())) == 1
def test_partial_authed_open_download(partial_authed_server):
"""Validate that partial auth still allows downloads."""
url = build_url(partial_authed_server.port) + "/simple"