1
0
mirror of https://github.com/pypiserver/pypiserver synced 2024-11-09 16:45:51 +01:00

init: Continue de-duplication of config-options.

+ Move default-opts and class into `pypiserver` pkg, to be used by
python-clients.
+ main() reuses default-opts class. 
+ Also apply default options to paste-factory (UNTESTED).
+ Setup loggers with `__file__`.
+ Document and relate main-options & config-params.
This commit is contained in:
ankostis on tokoti 2015-12-21 00:18:27 +02:00
parent 2fe615f22e
commit 24eddb292a
3 changed files with 145 additions and 98 deletions

@ -1,4 +1,5 @@
import re as _re import re as _re
version = __version__ = "1.1.9-dev.3" version = __version__ = "1.1.9-dev.3"
__version_info__ = tuple(_re.split('[.-]', __version__)) __version_info__ = tuple(_re.split('[.-]', __version__))
__updated__ = "2015-12-20" __updated__ = "2015-12-20"
@ -8,7 +9,57 @@ __summary__ = "A minimal PyPI server for use with pip/easy_install."
__uri__ = "https://github.com/pypiserver/pypiserver" __uri__ = "https://github.com/pypiserver/pypiserver"
class Configuration(object):
"""
.. see:: config-options: :func:`pypiserver.configure()`
"""
def __init__(self, **kwds):
vars(self).update(kwds)
def __repr__(self, *args, **kwargs):
return 'Configuration(**%s)' % vars(self)
def __str__(self, *args, **kwargs):
return 'Configuration:\n%s' % '\n'.join('%20s = %s' % (k, v)
for k, v in sorted(vars(self).items()))
def update(self, props):
d = props if isinstance(props, dict) else vars(props)
vars(self).update(props)
DEFAULT_SERVER = "auto"
def default_config():
c = Configuration(
VERSION=version,
root=None,
host = "0.0.0.0",
port = 8080,
server = DEFAULT_SERVER,
redirect_to_fallback = True,
fallback_url = None,
authenticated = ['update'],
password_file = None,
overwrite = False,
verbosity = 1,
log_file = None,
log_frmt = "%(asctime)s|%(levelname)s|%(thread)d|%(message)s",
log_req_frmt = "%(bottle.request)s",
log_res_frmt = "%(status)s",
log_err_frmt = "%(body)s: %(exception)s \n%(traceback)s",
welcome_file = None,
cache_control = None,
)
return c
def app(**kwds): def app(**kwds):
"""
:param dict kwds:
May use ``**vars(default_config())`.
"""
from . import core, _app, bottle from . import core, _app, bottle
bottle.debug(True) bottle.debug(True)
@ -23,26 +74,26 @@ def app(**kwds):
def paste_app_factory(global_config, **local_conf): def paste_app_factory(global_config, **local_conf):
import os import os
def upd_bool_attr_from_dict_str_item(conf, attr, sdict):
def str2bool(s, default):
if s is not None and s != '':
return s.lower() not in ("no", "off", "0", "false")
return default
setattr(conf, attr, str2bool(sdict.pop(attr, None), getattr(conf, attr)))
def _make_root(root): def _make_root(root):
root = root.strip() root = root.strip()
if root.startswith("~"): if root.startswith("~"):
return os.path.expanduser(root) return os.path.expanduser(root)
return root return root
c = default_config()
root = local_conf.get("root") root = local_conf.get("root")
if root: if root:
roots = [_make_root(x) for x in root.split("\n") if x.strip()] c.root = [_make_root(x) for x in root.split("\n") if x.strip()]
else:
roots = None
def str2bool(s, default): upd_bool_attr_from_dict_str_item(c, 'redirect_to_fallback', local_conf)
if s is not None and s != '': upd_bool_attr_from_dict_str_item(c, 'overwrite', local_conf)
return s.lower() not in ("no", "off", "0", "false")
return default
redirect_to_fallback = str2bool( return app(**vars(c))
local_conf.pop('redirect_to_fallback', None), True)
overwrite = str2bool(local_conf.get('overwrite', None), False)
return app(root=roots,
redirect_to_fallback=redirect_to_fallback, overwrite=overwrite,
**local_conf)

@ -14,8 +14,6 @@ import logging
import warnings import warnings
import textwrap import textwrap
DEFAULT_SERVER = "auto"
warnings.filterwarnings("ignore", "Python 2.5 support may be dropped in future versions of Bottle") warnings.filterwarnings("ignore", "Python 2.5 support may be dropped in future versions of Bottle")
log = logging.getLogger('pypiserver.main') log = logging.getLogger('pypiserver.main')
@ -145,32 +143,18 @@ def usage():
def main(argv=None): def main(argv=None):
import pypiserver
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
global packages
command = "serve" command = "serve"
host = "0.0.0.0"
port = 8080
server = DEFAULT_SERVER
redirect_to_fallback = True
fallback_url = None
authed_ops_list = ['update']
password_file = None
overwrite = False
verbosity = 1
log_file = None
log_frmt = "%(asctime)s|%(levelname)s|%(thread)d|%(message)s"
log_req_frmt = "%(bottle.request)s"
log_res_frmt = "%(status)s"
log_err_frmt = "%(body)s: %(exception)s \n%(traceback)s"
welcome_file = None
cache_control = None
update_dry_run = True c = pypiserver.default_config()
update_directory = None
update_stable_only = True update_dry_run = True,
update_directory = None,
update_stable_only = True,
try: try:
opts, roots = getopt.getopt(argv[1:], "i:p:a:r:d:P:Uuvxoh", [ opts, roots = getopt.getopt(argv[1:], "i:p:a:r:d:P:Uuvxoh", [
@ -200,36 +184,35 @@ def main(argv=None):
for k, v in opts: for k, v in opts:
if k in ("-p", "--port"): if k in ("-p", "--port"):
try: try:
port = int(v) c.port = int(v)
except Exception as ex: except Exception as ex:
sys.exit("Invalid port(%r)!" % v) sys.exit("Invalid port(%r)!" % v)
elif k in ("-a", "--authenticate"): elif k in ("-a", "--authenticate"):
authed_ops_list = [a.lower() c.authenticated = [a.lower()
for a in re.split("[, ]+", v.strip(" ,")) for a in re.split("[, ]+", v.strip(" ,"))
if a] if a]
if authed_ops_list == ['.']: if c.authenticated == ['.']:
authed_ops_list = [] c.authenticated = []
else: else:
actions = ("list", "download", "update") actions = ("list", "download", "update")
for a in authed_ops_list: for a in c.authenticated:
if a not in actions: if a not in actions:
errmsg = "Action '%s' for option `%s` not one of %s!" errmsg = "Action '%s' for option `%s` not one of %s!"
sys.exit(errmsg % (a, k, actions)) sys.exit(errmsg % (a, k, actions))
elif k in ("-i", "--interface"): elif k in ("-i", "--interface"):
host = v c.host = v
elif k in ("-r", "--root"): elif k in ("-r", "--root"):
roots.append(v) roots.append(v)
elif k == "--disable-fallback": elif k == "--disable-fallback":
redirect_to_fallback = False c.redirect_to_fallback = False
elif k == "--fallback-url": elif k == "--fallback-url":
fallback_url = v c.fallback_url = v
elif k == "--server": elif k == "--server":
server = v c.server = v
elif k == "--welcome": elif k == "--welcome":
welcome_file = v c.welcome_file = v
elif k == "--version": elif k == "--version":
from pypiserver import __version__ print("pypiserver %s\n" % pypiserver.__version__)
print("pypiserver %s\n" % __version__)
return return
elif k == "-U": elif k == "-U":
command = "update" command = "update"
@ -240,75 +223,63 @@ def main(argv=None):
elif k == "-d": elif k == "-d":
update_directory = v update_directory = v
elif k in ("-P", "--passwords"): elif k in ("-P", "--passwords"):
password_file = v c.password_file = v
elif k in ("-o", "--overwrite"): elif k in ("-o", "--overwrite"):
overwrite = True c.overwrite = True
elif k == "--log-file": elif k == "--log-file":
log_file = v c.log_file = v
elif k == "--log-frmt": elif k == "--log-frmt":
log_frmt = v c.log_frmt = v
elif k == "--log-req-frmt": elif k == "--log-req-frmt":
log_req_frmt = v c.log_req_frmt = v
elif k == "--log-res-frmt": elif k == "--log-res-frmt":
log_res_frmt = v c.log_res_frmt = v
elif k == "--log-err-frmt": elif k == "--log-err-frmt":
log_err_frmt = v c.log_err_frmt = v
elif k == "--cache-control": elif k == "--cache-control":
cache_control = v c.cache_control = v
elif k == "-v": elif k == "-v":
verbosity += 1 c.verbosity += 1
elif k in ("-h", "--help"): elif k in ("-h", "--help"):
print(usage()) print(usage())
sys.exit(0) sys.exit(0)
if (not authed_ops_list and password_file != '.' or if (not c.authenticated and c.password_file != '.' or
authed_ops_list and password_file == '.'): c.authenticated and c.password_file == '.'):
auth_err = "When auth-ops-list is empty (-a=.), password-file (-P=%r) must also be empty ('.')!" auth_err = "When auth-ops-list is empty (-a=.), password-file (-P=%r) must also be empty ('.')!"
sys.exit(auth_err % password_file) sys.exit(auth_err % c.password_file)
if len(roots) == 0: if len(roots) == 0:
roots.append(os.path.expanduser("~/packages")) roots.append(os.path.expanduser("~/packages"))
roots=[os.path.abspath(x) for x in roots] roots=[os.path.abspath(x) for x in roots]
c.root = roots
verbose_levels=[ verbose_levels=[
logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET] logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET]
log_level=list(zip(verbose_levels, range(verbosity)))[-1][0] log_level=list(zip(verbose_levels, range(c.verbosity)))[-1][0]
init_logging(level=log_level, filename=log_file, frmt=log_frmt) init_logging(level=log_level, filename=c.log_file, frmt=c.log_frmt)
if command == "update": if command == "update":
from pypiserver.manage import update_all_packages from pypiserver.manage import update_all_packages
update_all_packages( update_all_packages(roots, update_directory,
roots, update_directory, update_dry_run, stable_only=update_stable_only) dry_run=update_dry_run, stable_only=update_stable_only)
return return
# Fixes #49: # Fixes #49:
# The gevent server adapter needs to patch some # The gevent server adapter needs to patch some
# modules BEFORE importing bottle! # modules BEFORE importing bottle!
if server and server.startswith('gevent'): if c.server and c.server.startswith('gevent'):
import gevent.monkey # @UnresolvedImport import gevent.monkey # @UnresolvedImport
gevent.monkey.patch_all() gevent.monkey.patch_all()
from pypiserver.bottle import server_names, run from pypiserver.bottle import server_names, run
if server not in server_names: if c.server not in server_names:
sys.exit("unknown server %r. choose one of %s" % ( sys.exit("unknown server %r. choose one of %s" % (
server, ", ".join(server_names.keys()))) c.server, ", ".join(server_names.keys())))
from pypiserver import __version__, app app = pypiserver.app(**vars(c))
app = app( run(app=app, host=c.host, port=c.port, server=c.server)
root=roots,
redirect_to_fallback=redirect_to_fallback,
authenticated=authed_ops_list,
password_file=password_file,
fallback_url=fallback_url,
overwrite=overwrite,
log_req_frmt=log_req_frmt, log_res_frmt=log_res_frmt, log_err_frmt=log_err_frmt,
welcome_file=welcome_file,
cache_control=cache_control,
)
log.info("This is pypiserver %s serving %r on http://%s:%s\n\n",
__version__, ", ".join(roots), host, port)
run(app=app, host=host, port=port, server=server)
if __name__ == "__main__": if __name__ == "__main__":

@ -12,18 +12,9 @@ import re
import sys import sys
import pkg_resources import pkg_resources
from . import Configuration
log = logging.getLogger(__file__)
class Configuration(object):
def __init__(self, **kwds):
vars(self).update(kwds)
def __repr__(self, *args, **kwargs):
return 'Configuration(**%s)' % vars(self)
def __str__(self, *args, **kwargs):
return 'Configuration:\n%s' % '\n'.join('%16s = %s' % (k, v)
for k, v in sorted(vars(self).items()))
def configure(root=None, def configure(root=None,
redirect_to_fallback=True, redirect_to_fallback=True,
@ -31,14 +22,31 @@ def configure(root=None,
authenticated=None, authenticated=None,
password_file=None, password_file=None,
overwrite=False, overwrite=False,
log_file=None,
log_frmt=None,
log_req_frmt=None, log_req_frmt=None,
log_res_frmt=None, log_res_frmt=None,
log_err_frmt=None, log_err_frmt=None,
welcome_file=None, welcome_file=None,
cache_control=None, cache_control=None,
auther=None auther=None,
host=None, port=None, server=None, verbosity=None, VERSION=None
): ):
""" """
:param root:
A list of paths, derived from the packages specified on cmd-line.
:param redirect_to_fallback:
see :option:`--disable-fallback`
:param authenticated:
see :option:`--authenticate`
:param password_file:
see :option:`--passwords`
:param log_file:
see :option:`--log-file`
Not used, passed here for logging it.
:param log_frmt:
see :option:`--log-frmt`
Not used, passed here for logging it.
:param callable auther: :param callable auther:
An API-only options that if it evaluates to a callable, An API-only options that if it evaluates to a callable,
it is invoked to allow access to protected operations it is invoked to allow access to protected operations
@ -47,11 +55,25 @@ def configure(root=None,
auther(username, password): bool auther(username, password): bool
When defined, `password_file` is ignored. When defined, `password_file` is ignored.
:param host:
see :option:`--interface`
Not used, passed here for logging it.
:param port:
see :option:`--port`
Not used, passed here for logging it.
:param server:
see :option:`--server`
Not used, passed here for logging it.
:param verbosity:
see :option:`-v`
Not used, passed here for logging it.
:param VERSION:
Not used, passed here for logging it.
:return: a 2-tuple (Configure, package-list) :return: a 2-tuple (Configure, package-list)
""" """
log.info("+++Invoked with: %s", Configuration( log.info("+++Pypiserver invoked with: %s", Configuration(
root=root, root=root,
redirect_to_fallback=redirect_to_fallback, redirect_to_fallback=redirect_to_fallback,
fallback_url=fallback_url, fallback_url=fallback_url,
@ -59,11 +81,15 @@ def configure(root=None,
password_file=password_file, password_file=password_file,
overwrite=overwrite, overwrite=overwrite,
welcome_file=welcome_file, welcome_file=welcome_file,
log_file=log_file,
log_frmt=log_frmt,
log_req_frmt=log_req_frmt, log_req_frmt=log_req_frmt,
log_res_frmt=log_res_frmt, log_res_frmt=log_res_frmt,
log_err_frmt=log_err_frmt, log_err_frmt=log_err_frmt,
cache_control=cache_control, cache_control=cache_control,
auther=auther auther=auther,
host=host, port=port, server=server,
verbosity=verbosity, VERSION=VERSION
)) ))
@ -128,7 +154,7 @@ def configure(root=None,
cache_control=cache_control, cache_control=cache_control,
auther=auther auther=auther
) )
log.info("+++Starting with: %s", config) log.info("+++Pypiserver started with: %s", config)
return config, packages return config, packages
@ -144,7 +170,6 @@ def auth_by_htpasswd_file(htPsswdFile, username, password):
mimetypes.add_type("application/octet-stream", ".egg") mimetypes.add_type("application/octet-stream", ".egg")
mimetypes.add_type("application/octet-stream", ".whl") mimetypes.add_type("application/octet-stream", ".whl")
log = logging.getLogger('pypiserver.core')
# --- the following two functions were copied from distribute's pkg_resources module # --- the following two functions were copied from distribute's pkg_resources module
component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)