forked from github.com/pypiserver
Restore ability to drop hashing in new config (#347)
Thanks @elfjes for pointing out that I'd missed this! I also went ahead and bumped the version in the README to 2.0.0dev1, so that it's clear that what's in master shouldn't be what people expect from pypi or in the docker image.
This commit is contained in:
parent
8014fa56fc
commit
47d6efe196
@ -9,7 +9,7 @@ pypiserver - minimal PyPI server for use with pip/easy_install
|
||||
==============================================================================
|
||||
|pypi-ver| |travis-status| |dependencies| |python-ver| |proj-license|
|
||||
|
||||
:Version: 1.4.2
|
||||
:Version: 2.0.0dev1
|
||||
:Date: 2020-10-10
|
||||
:Source: https://github.com/pypiserver/pypiserver
|
||||
:PyPI: https://pypi.org/project/pypiserver/
|
||||
|
@ -2,9 +2,9 @@ import os
|
||||
import re as _re
|
||||
import sys
|
||||
|
||||
version = __version__ = "1.4.2"
|
||||
version = __version__ = "2.0.0dev1"
|
||||
__version_info__ = tuple(_re.split("[.-]", __version__))
|
||||
__updated__ = "2020-10-10 08:15:56"
|
||||
__updated__ = "2020-10-11 11:23:15"
|
||||
|
||||
__title__ = "pypiserver"
|
||||
__summary__ = "A minimal PyPI server for use with pip/easy_install."
|
||||
|
@ -32,16 +32,24 @@ into a dict by the argument parser.
|
||||
import argparse
|
||||
import contextlib
|
||||
import hashlib
|
||||
import itertools
|
||||
import io
|
||||
import pkg_resources
|
||||
import re
|
||||
import textwrap
|
||||
import sys
|
||||
import typing as t
|
||||
from distutils.util import strtobool as strtoint
|
||||
|
||||
from pypiserver import __version__
|
||||
|
||||
|
||||
# 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
|
||||
# to update things in multiple places if a default changes.
|
||||
class DEFAULTS:
|
||||
@ -74,21 +82,37 @@ def auth_arg(arg: str) -> t.List[str]:
|
||||
)
|
||||
# The "no authentication" option must be specified in isolation.
|
||||
if "." in items and len(items) > 1:
|
||||
raise ValueError(
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Invalid authentication options. `.` (no authentication) "
|
||||
"must be specified alone."
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def hash_algo_arg(arg: str) -> t.Callable:
|
||||
def hash_algo_arg(arg: str) -> t.Optional[str]:
|
||||
"""Parse a hash algorithm from the string."""
|
||||
if arg not in hashlib.algorithms_available:
|
||||
raise ValueError(
|
||||
f"Hash algorithm {arg} is not available. Please select one "
|
||||
f"of {hashlib.algorithms_available}"
|
||||
)
|
||||
return getattr(hashlib, arg)
|
||||
# The standard hashing algorithms are all made available via fully
|
||||
# lowercase names, along with (sometimes) variously cased versions
|
||||
# as well.
|
||||
arg = arg.lower()
|
||||
if arg in hashlib.algorithms_available:
|
||||
return arg
|
||||
try:
|
||||
if not strtobool(arg):
|
||||
return None
|
||||
except ValueError:
|
||||
# strtobool raises if the string doesn't seem like a truthiness-
|
||||
# indicating string. We do want to raise in this case, but we want
|
||||
# to raise our own custom message rather than raising the ValueError
|
||||
# raised by strtobool.
|
||||
pass
|
||||
# At this point we either had an invalid hash algorithm or a truthy string
|
||||
# like 'yes' or 'true'. In either case, we want to throw an error.
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"Hash algorithm '{arg}' is not available. Please select one "
|
||||
f"of {hashlib.algorithms_available}, or turn off hashing by "
|
||||
"setting --hash-algo to 'off', '0', or 'false'"
|
||||
)
|
||||
|
||||
|
||||
def html_file_arg(arg: t.Optional[str]) -> str:
|
||||
@ -129,7 +153,7 @@ def log_stream_arg(arg: str) -> t.Optional[t.IO]:
|
||||
return sys.stdout
|
||||
if val == "stderr":
|
||||
return _ORIG_STDERR
|
||||
raise ValueError(
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Invalid option for --log-stream. Value must be one of stdout, "
|
||||
"stderr, or none."
|
||||
)
|
||||
@ -315,7 +339,7 @@ def get_parser() -> argparse.ArgumentParser:
|
||||
run_parser.add_argument(
|
||||
"--hash-algo",
|
||||
default=DEFAULTS.HASH_ALGO,
|
||||
choices=hashlib.algorithms_available,
|
||||
type=hash_algo_arg,
|
||||
help=(
|
||||
"Any `hashlib` available algorithm to use for generating fragments "
|
||||
"on package links. Can be disabled with one of (0, no, off, false)."
|
||||
@ -481,7 +505,7 @@ class RunConfig(_ConfigCommon):
|
||||
self.fallback_url: str = namespace.fallback_url
|
||||
self.server_method: str = namespace.server
|
||||
self.overwrite: bool = namespace.overwrite
|
||||
self.hash_algo: t.Callable = namespace.hash_algo
|
||||
self.hash_algo: t.Optional[str] = namespace.hash_algo
|
||||
self.welcome_msg: str = namespace.welcome
|
||||
self.cache_control: t.Optional[int] = namespace.cache_control
|
||||
self.log_req_frmt: str = namespace.log_req_frmt
|
||||
|
@ -380,6 +380,16 @@ _CONFIG_TEST_PARAMS: t.Tuple[ConfigTestCase, ...] = (
|
||||
)
|
||||
for algo in hashlib.algorithms_available
|
||||
),
|
||||
*(
|
||||
ConfigTestCase(
|
||||
case="Run: hash-algo disabled",
|
||||
args=["run", "--hash-algo", off_value],
|
||||
legacy_args=["--hash-algo", off_value],
|
||||
exp_config_type=RunConfig,
|
||||
exp_config_values={"hash_algo": None},
|
||||
)
|
||||
for off_value in ("0", "off", "false", "no", "NO")
|
||||
),
|
||||
# welcome file
|
||||
ConfigTestCase(
|
||||
case="Run: welcome file unspecified",
|
||||
@ -559,6 +569,36 @@ CONFIG_TEST_PARAMS = (i[1:] for i in _CONFIG_TEST_PARAMS)
|
||||
CONFIG_TEST_IDS = (i.case for i in _CONFIG_TEST_PARAMS)
|
||||
|
||||
|
||||
class ConfigErrorCase(t.NamedTuple):
|
||||
"""Configuration arguments that should cause errors.
|
||||
|
||||
The cases include a case descrpition, a list of arguments,
|
||||
and, if desired, expected text that should be part of what
|
||||
is printed out to stderr. If no text is provided, the content
|
||||
of stderr will not be checked.
|
||||
"""
|
||||
|
||||
case: str
|
||||
args: t.List[str]
|
||||
exp_txt: t.Optional[str]
|
||||
|
||||
|
||||
_CONFIG_ERROR_CASES = (
|
||||
*(
|
||||
ConfigErrorCase(
|
||||
case=f"Invalid hash algo: {val}",
|
||||
args=["run", "--hash-algo", val],
|
||||
exp_txt=f"Hash algorithm '{val}' is not available",
|
||||
)
|
||||
for val in ("true", "foo", "1", "md6")
|
||||
),
|
||||
)
|
||||
# pylint: disable=unsubscriptable-object
|
||||
CONFIG_ERROR_PARAMS = (i[1:] for i in _CONFIG_ERROR_CASES)
|
||||
# pylint: enable=unsubscriptable-object
|
||||
CONFIG_ERROR_IDS = (i.case for i in _CONFIG_ERROR_CASES)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, legacy_args, exp_config_type, exp_config_values",
|
||||
CONFIG_TEST_PARAMS,
|
||||
@ -591,6 +631,27 @@ def test_config(
|
||||
assert conf == conf_legacy
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, exp_txt",
|
||||
CONFIG_ERROR_PARAMS,
|
||||
ids=CONFIG_TEST_IDS,
|
||||
)
|
||||
def test_config_error(
|
||||
args: t.List[str],
|
||||
exp_txt: t.Optional[str],
|
||||
capsys,
|
||||
) -> None:
|
||||
"""Validate error cases."""
|
||||
with pytest.raises(SystemExit):
|
||||
Config.from_args(args)
|
||||
# Unfortunatley the error text is printed before the SystemExit is
|
||||
# raised, rather than being raised _with_ the systemexit, so we
|
||||
# need to capture stderr and check it for our expected text, if
|
||||
# any was specified in the test case.
|
||||
if exp_txt is not None:
|
||||
assert exp_txt in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_argv_conf():
|
||||
"""Config uses argv if no args are provided."""
|
||||
orig_args = list(sys.argv)
|
||||
|
Loading…
Reference in New Issue
Block a user