Skip to content

Commit

Permalink
Update docs and error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mar10 committed Nov 11, 2021
1 parent c45456b commit f6c5719
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 59 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ npm-debug.log
pip-wheel-metadata/
wsgidav.yaml
wsgidav_2x.yaml
wsgidav_custom.yaml
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,46 @@
[![Released with: Yabs](https://img.shields.io/badge/released%20with-yabs-yellowgreen)](https://github.com/mar10/yabs)
[![StackOverflow: WsgiDAV](https://img.shields.io/badge/StackOverflow-WsgiDAV-blue.svg)](https://stackoverflow.com/questions/tagged/WsgiDAV)

Experimental: [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/mar10/wsgidav)

A generic and extendable [WebDAV](http://www.ietf.org/rfc/rfc4918.txt) server
written in Python and based on [WSGI](http://www.python.org/dev/peps/pep-3333/).

Main features:

- WsgiDAV is a stand-alone WebDAV server with SSL support, that can be
installed and run as Python command line script on Linux, OSX, and Windows:<br>
```
```bash
$ pip install wsgidav cheroot
$ wsgidav --host=0.0.0.0 --port=8080 --root=/tmp --auth=anonymous
WARNING: share '/' will allow anonymous access.
Running WsgiDAV/2.2.2 Cheroot/5.5.0 Python/3.4.2
Serving on http://0.0.0.0:8080 ...
$ wsgidav --host=0.0.0.0 --port=80 --root=/tmp --auth=anonymous
Running without configuration file.
10:54:16.597 - INFO : WsgiDAV/4.0.0-a1 Python/3.9.1 macOS-12.0.1-x86_64-i386-64bit
10:54:16.598 - INFO : Registered DAV providers by route:
10:54:16.598 - INFO : - '/:dir_browser': FilesystemProvider for path '/Users/martin/prj/git/wsgidav/wsgidav/dir_browser/htdocs' (Read-Only) (anonymous)
10:54:16.599 - INFO : - '/': FilesystemProvider for path '/tmp' (Read-Write) (anonymous)
10:54:16.599 - WARNING : Basic authentication is enabled: It is highly recommended to enable SSL.
10:54:16.599 - WARNING : Share '/' will allow anonymous write access.
10:54:16.813 - INFO : Running WsgiDAV/4.0.0-a1 Cheroot/8.5.2 Python 3.9.1
10:54:16.813 - INFO : Serving on http://0.0.0.0:80 ...
```
Run `wsgidav --help` for a list of available options.<br>

- **Note:** python-pam is needed if using pam-login on Linux or OSX:
```
- python-pam is needed if using pam-login on Linux or OSX:
```bash
$ pip install python-pam
$ wsgidav --auth=pam-login --host=0.0.0.0 --port=8080 --root=/tmp
$ wsgidav --host=0.0.0.0 --port=8080 --root=/tmp --auth=pam-login
```

- **Note:** Windows users may prefer the
[MSI Installer](https://github.com/mar10/wsgidav/releases/latest)
(see <kbd>Assets</kbd> section).

- WebDAV is a superset of HTTP, so WsgiDAV is also a performant, multi-threaded
web server with SSL support.

- WsgiDAV is also a Python library that implements the WSGI protocol and can
be run behind any WSGI compliant web server.<br>

- WsgiDAV is implemented as a configurable stack of WSGI middleware
applications.<br>
Its open architecture allows to extend the functionality and integrate
Expand All @@ -50,7 +61,7 @@ Main features:
[![Latest Version](https://img.shields.io/pypi/v/wsgidav.svg)](https://pypi.python.org/pypi/WsgiDAV/)
See the ([change log](https://github.com/mar10/wsgidav/blob/master/CHANGELOG.md)) for details.

**Note:** Release 3.0 introduces some refactorings and breaking changes.<br>
**Note:** Release 4.0 introduces some refactorings and breaking changes.<br>
See the ([change log](https://github.com/mar10/wsgidav/blob/master/CHANGELOG.md)) for details.


Expand Down
48 changes: 24 additions & 24 deletions docs/sphinx/user_guide_cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@ Command Line Interface

The WsgiDAV server was tested with these platforms

* Mac OS X 10.9 - 10.13
* Ubuntu 13 - 16
* Windows (Win 7 - 10, Vista, XP)
* Mac OS X
* Ubuntu
* Windows

To serve the ``/tmp`` folder as WebDAV ``/`` share, simply run::

$ wsgidav --host=0.0.0.0 --port=80 --root=/tmp --auth=anonymous
2019-01-03 20:47:52.034 - INFO : WsgiDAV/3.0.0a8 Python/3.7.0 Darwin-18.2.0-x86_64-i386-64bit
2019-01-03 20:47:52.034 - INFO : Lock manager: LockManager(LockStorageDict)
2019-01-03 20:47:52.034 - INFO : Property manager: None
2019-01-03 20:47:52.034 - INFO : Domain controller: SimpleDomainController()
2019-01-03 20:47:52.034 - INFO : Registered DAV providers by route:
2019-01-03 20:47:52.035 - INFO : - '/:dir_browser': FilesystemProvider for path '/Users/martin/prj/git/wsgidav/wsgidav/dir_browser/htdocs' (Read-Only) (anonymous)
2019-01-03 20:47:52.035 - INFO : - '/': FilesystemProvider for path '/tmp' (Read-Write) (anonymous)
2019-01-03 20:47:52.035 - WARNING : Basic authentication is enabled: It is highly recommended to enable SSL.
2019-01-03 20:47:52.035 - WARNING : Share '/' will allow anonymous write access.
2019-01-03 20:47:52.035 - WARNING : Share '/:dir_browser' will allow anonymous read access.
2019-01-03 20:47:52.042 - INFO : Running WsgiDAV/3.0.0a8 Cheroot/6.5.4.dev43+gab92fb97 Python/3.7.0
2019-01-03 20:47:52.042 - INFO : Serving on http://0.0.0.0:80 ...
$ wsgidav --host 0.0.0.0 --port 80 --root /tmp --auth anonymous
Running without configuration file.
10:54:16.597 - INFO : WsgiDAV/4.0.0-a1 Python/3.9.1 macOS-12.0.1-x86_64-i386-64bit
10:54:16.598 - INFO : Lock manager: LockManager(LockStorageDict)
10:54:16.598 - INFO : Property manager: None
10:54:16.598 - INFO : Domain controller: SimpleDomainController()
10:54:16.598 - INFO : Registered DAV providers by route:
10:54:16.598 - INFO : - '/:dir_browser': FilesystemProvider for path '/Users/martin/prj/git/wsgidav/wsgidav/dir_browser/htdocs' (Read-Only) (anonymous)
10:54:16.599 - INFO : - '/': FilesystemProvider for path '/tmp' (Read-Write) (anonymous)
10:54:16.599 - WARNING : Basic authentication is enabled: It is highly recommended to enable SSL.
10:54:16.599 - WARNING : Share '/' will allow anonymous write access.
10:54:16.599 - WARNING : Share '/:dir_browser' will allow anonymous read access.
10:54:16.599 - WARNING : Could not import lxml: using xml instead (up to 10% slower). Consider `pip install lxml`(see https://pypi.python.org/pypi/lxml).
10:54:16.813 - INFO : Running WsgiDAV/4.0.0-a1 Cheroot/8.5.2 Python 3.9.1
10:54:16.813 - INFO : Serving on http://0.0.0.0:80 ...

.. warning::
Here, WsgiDAV will publish the folder for anonymous access.
Expand All @@ -36,11 +38,8 @@ CLI Options
Use the ``--help`` or ``-h`` argument to get help::

$ wsgidav --help
usage: wsgidav [-h] [-p PORT] [-H HOST] [-r ROOT_PATH]
[--auth {anonymous,nt,pam-login}]
[--server {paste,gevent,cheroot,cherrypy,ext-wsgiutils,flup-fcgi,flup-fcgi_fork,wsgiref,gunicorn}]
[--ssl-adapter {builtin,pyopenssl}] [-v | -q]
[-c CONFIG_FILE | --no-config] [-V]
usage: wsgidav [-h] [-p PORT] [-H HOST] [-r ROOT_PATH] [--auth {anonymous,nt,pam-login}] [--server {cheroot,ext-wsgiutils,gevent,gunicorn,paste,uvicorn,wsgiref}]
[--ssl-adapter {builtin,pyopenssl}] [-v | -q] [-c CONFIG_FILE | --no-config] [--browse] [-V]

Run a WEBDAV server to share file system folders.

Expand All @@ -67,15 +66,16 @@ Use the ``--help`` or ``-h`` argument to get help::
path to a file system folder to publish as share '/'.
--auth {anonymous,nt,pam-login}
quick configuration of a domain controller when no config file is used
--server {paste,gevent,cheroot,cherrypy,ext-wsgiutils,flup-fcgi,flup-fcgi_fork,wsgiref,gunicorn}
--server {cheroot,ext-wsgiutils,gevent,gunicorn,paste,uvicorn,wsgiref}
type of pre-installed WSGI server to use (default: cheroot).
--ssl-adapter {builtin,pyopenssl}
used by 'cheroot' server if SSL certificates are configured (default: builtin).
-v, --verbose increment verbosity by one (default: 3, range: 0..5)
-q, --quiet decrement verbosity by one
-c CONFIG_FILE, --config CONFIG_FILE
configuration file (default: ('wsgidav.yaml', 'wsgidav.json', 'wsgidav.conf') in current directory)
--no-config do not try to load default ('wsgidav.yaml', 'wsgidav.json', 'wsgidav.conf')
configuration file (default: ('wsgidav.yaml', 'wsgidav.json') in current directory)
--no-config do not try to load default ('wsgidav.yaml', 'wsgidav.json')
--browse open browser on start
-V, --version print version info and exit (may be combined with --verbose)

Licensed under the MIT license.
Expand Down
10 changes: 10 additions & 0 deletions docs/sphinx/user_guide_configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ should be explicitly listed::
- wsgidav.dir_browser.WsgiDavDirBrowser
- wsgidav.request_resolver.RequestResolver

It is also possible to pass options as named args (i.e. 'kwargs')::

...
middleware_stack:
...
- dozer.Profiler:
app: "${application}"
profile_path: /tmp
...

Note that the external middleware must be available, for example by calling
``pip install Doze``, so this will not be possible if WsgiDAV is running from
the MSI installer.
Expand Down
2 changes: 1 addition & 1 deletion wsgidav/debug_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class WsgiDavDebugFilter(BaseMiddleware):
def __init__(self, wsgidav_app, next_app, config):
super(WsgiDavDebugFilter, self).__init__(wsgidav_app, next_app, config)
self._config = config
log_opts = config.get("logging", {})
log_opts = config.get("logging") or {}
# self.out = sys.stdout
self.passedLitmus = {}
# These methods boost verbose=2 to verbose=3
Expand Down
6 changes: 4 additions & 2 deletions wsgidav/default_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

# Use these settings, if config file does not define them (or is totally missing)
DEFAULT_VERBOSE = 3
DEFAULT_LOGGER_DATE_FORMAT = "%H:%M:%S"
DEFAULT_LOGGER_FORMAT = "%(asctime)s.%(msecs)03d - %(levelname)-8s: %(message)s"

DEFAULT_CONFIG = {
"server": "cheroot",
Expand Down Expand Up @@ -73,8 +75,8 @@
"verbose": DEFAULT_VERBOSE,
#: Log options
"logging": {
"logger_date_format": "%H:%M:%S",
"logger_format": "%(asctime)s.%(msecs)03d - %(levelname)-8s: %(message)s",
"logger_date_format": DEFAULT_LOGGER_DATE_FORMAT,
"logger_format": DEFAULT_LOGGER_FORMAT,
"enable_loggers": [],
"debug_methods": [],
},
Expand Down
4 changes: 2 additions & 2 deletions wsgidav/request_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1551,8 +1551,8 @@ def _send_resource(self, environ, start_response, is_head_method):
self._fail(
HTTP_FORBIDDEN,
"Directory browsing is not enabled."
"(to enable it put WsgiDavDirBrowser into middleware_stack"
"option and set dir_browser -> enabled = True option.)",
"(to enable it put WsgiDavDirBrowser into the middleware_stack "
"option and set dir_browser.enabled = True option.)",
)

self._evaluate_if_headers(res, environ)
Expand Down
46 changes: 29 additions & 17 deletions wsgidav/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,19 +234,23 @@ def init_logging(config):
+---------+--------+-------------+------------------------+------------------------+
"""
from wsgidav.default_conf import DEFAULT_LOGGER_DATE_FORMAT, DEFAULT_LOGGER_FORMAT

verbose = config.get("verbose", 3)
log_opts = config.get("logging", {})
log_opts = config.get("logging") or {}

enable_loggers = log_opts.get("enable_loggers", [])
if enable_loggers is None:
enable_loggers = []

logger_date_format = log_opts.get("logger_date_format", DEFAULT_LOGGER_DATE_FORMAT)
logger_format = log_opts.get("logger_format", DEFAULT_LOGGER_FORMAT)
# Verbose format by default (but wsgidav.util.DEFAULT_CONFIG defines a short format)
logger_date_format = log_opts.get("logger_date_format", "%Y-%m-%d %H:%M:%S")
logger_format = log_opts.get(
"logger_format",
"%(asctime)s.%(msecs)03d - <%(thread)d> %(name)-27s %(levelname)-8s: %(message)s",
)
# logger_date_format = log_opts.get("logger_date_format", "%Y-%m-%d %H:%M:%S")
# logger_format = log_opts.get(
# "logger_format",
# "%(asctime)s.%(msecs)03d - <%(thread)d> %(name)-27s %(levelname)-8s: %(message)s",
# )

formatter = logging.Formatter(logger_format, logger_date_format)

Expand Down Expand Up @@ -338,12 +342,12 @@ def dynamic_import_class(name):
return the_class


def dynamic_instantiate_middleware(name, args, expand=None):
def dynamic_instantiate_middleware(name, options, expand=None):
"""Import a class and instantiate with custom args.
Example:
Examples:
name = "my.module.Foo"
args_dict = {
options = {
"bar": 42,
"baz": "qux"
}
Expand All @@ -361,17 +365,25 @@ def _expand(v):
try:
the_class = dynamic_import_class(name)
inst = None
if type(args) in (tuple, list):
args = tuple(map(_expand, args))
inst = the_class(*args)
pos_args = []
kwargs = {}
if type(options) in (tuple, list):
pos_args = tuple(map(_expand, options))
elif type(options) is dict:
kwargs = {k: _expand(v) for k, v in options.items()}
else:
assert type(args) is dict
args = {k: _expand(v) for k, v in args.items()}
inst = the_class(**args)
raise ValueError(f"Unexpected options format: {options}")

_logger.debug("Instantiate {}({}) => {}".format(name, args, inst))
inst = the_class(*pos_args, **kwargs)

disp_args = [f"{o}" for o in pos_args] + [
f"{k}={v!r}" for k, v in kwargs.items()
]
_logger.debug(
"Instantiate {}({}) => {}".format(name, ", ".join(disp_args), inst)
)
except Exception:
_logger.exception("ERROR: Instantiate {}({}) => {}".format(name, args, inst))
_logger.exception("ERROR: Instantiate {}({}) => {}".format(name, options, inst))

return inst

Expand Down
10 changes: 6 additions & 4 deletions wsgidav/wsgidav_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,17 @@ def __init__(self, config):
elif type(mw) is dict:
# If a dict with one entry is passed, use the key as module/class name
# and the value as constructor arguments (positional or kwargs).
assert len(mw) == 1
if len(mw) != 1:
raise ValueError(f"Invalid middleware opts: {mw}")
name, args = list(mw.items())[0]
if type(args) not in (dict, list, tuple):
raise ValueError(f"Invalid middleware opts for {name}: {args}")
expand = {"${application}": self.application}
app = dynamic_instantiate_middleware(name, args, expand)
elif inspect.isclass(mw):
# If a class is passed, assume BaseMiddleware (or compatible)
assert issubclass(
mw, BaseMiddleware
) # TODO: remove this assert with 3.0
# TODO: remove this assert with 3.0
assert issubclass(mw, BaseMiddleware)
app = mw(self, self.application, config)
else:
# Otherwise assume an initialized middleware instance
Expand Down

0 comments on commit f6c5719

Please sign in to comment.